Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5179544fd6 | |||
| 160d6b49e9 | |||
| 9007762390 | |||
| 506007984c | |||
| 7a6d1a44f2 | |||
| 3960d746f4 | |||
| cc942ca818 | |||
| 83a2d25062 | |||
| c8429cfde1 | |||
| 1502039d70 |
@@ -30,6 +30,9 @@ jobs:
|
|||||||
needs: test
|
needs: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: (github.ref == 'refs/heads/master' && github.event_name == 'push') || startsWith(github.ref, 'refs/tags/')
|
if: (github.ref == 'refs/heads/master' && github.event_name == 'push') || startsWith(github.ref, 'refs/tags/')
|
||||||
|
concurrency:
|
||||||
|
group: publish-${{ github.sha }}
|
||||||
|
cancel-in-progress: false
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
> **Abandoned** — This adapter is no longer maintained. The Kubernetes execution capability has moved to the sandbox plugin at [`farhoodlabs/paperclip-plugin-k8s`](https://github.com/farhoodlabs/paperclip-plugin-k8s) (`@farhoodlabs/paperclip-plugin-k8s` on npm).
|
||||||
|
|
||||||
# Claude (Kubernetes) Paperclip Adapter Plugin
|
# Claude (Kubernetes) Paperclip Adapter Plugin
|
||||||
|
|
||||||
Paperclip adapter plugin that runs Claude Code agents as isolated Kubernetes Jobs instead of inside the main Paperclip process.
|
Paperclip adapter plugin that runs Claude Code agents as isolated Kubernetes Jobs instead of inside the main Paperclip process.
|
||||||
|
|||||||
Vendored
+141
-137
@@ -1,148 +1,152 @@
|
|||||||
"use strict";
|
/**
|
||||||
var __defProp = Object.defineProperty;
|
* Self-contained stdout parser for Claude stream-json output.
|
||||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
* Zero external imports — required by the Paperclip adapter plugin UI parser contract.
|
||||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
*/
|
||||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
||||||
var __export = (target, all) => {
|
|
||||||
for (var name in all)
|
|
||||||
__defProp(target, name, { get: all[name], enumerable: true });
|
|
||||||
};
|
|
||||||
var __copyProps = (to, from, except, desc) => {
|
|
||||||
if (from && typeof from === "object" || typeof from === "function") {
|
|
||||||
for (let key of __getOwnPropNames(from))
|
|
||||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
||||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
||||||
}
|
|
||||||
return to;
|
|
||||||
};
|
|
||||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
||||||
|
|
||||||
// src/ui-parser.ts
|
|
||||||
var ui_parser_exports = {};
|
|
||||||
__export(ui_parser_exports, {
|
|
||||||
parseStdoutLine: () => parseStdoutLine
|
|
||||||
});
|
|
||||||
module.exports = __toCommonJS(ui_parser_exports);
|
|
||||||
function asRecord(value) {
|
function asRecord(value) {
|
||||||
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
||||||
return value;
|
return null;
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
function asNumber(value) {
|
function asNumber(value) {
|
||||||
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
||||||
}
|
}
|
||||||
function errorText(value) {
|
function errorText(value) {
|
||||||
if (typeof value === "string") return value;
|
if (typeof value === "string")
|
||||||
const rec = asRecord(value);
|
return value;
|
||||||
if (!rec) return "";
|
const rec = asRecord(value);
|
||||||
const msg = typeof rec.message === "string" && rec.message || typeof rec.error === "string" && rec.error || typeof rec.code === "string" && rec.code || "";
|
if (!rec)
|
||||||
if (msg) return msg;
|
return "";
|
||||||
try {
|
const msg = (typeof rec.message === "string" && rec.message) ||
|
||||||
return JSON.stringify(rec);
|
(typeof rec.error === "string" && rec.error) ||
|
||||||
} catch {
|
(typeof rec.code === "string" && rec.code) ||
|
||||||
return "";
|
"";
|
||||||
}
|
if (msg)
|
||||||
|
return msg;
|
||||||
|
try {
|
||||||
|
return JSON.stringify(rec);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function safeJsonParse(text) {
|
function safeJsonParse(text) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(text);
|
return JSON.parse(text);
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function parseStdoutLine(line, ts) {
|
|
||||||
const parsed = asRecord(safeJsonParse(line));
|
|
||||||
if (!parsed) {
|
|
||||||
return [{ kind: "stdout", ts, text: line }];
|
|
||||||
}
|
|
||||||
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
||||||
if (type === "system" && parsed.subtype === "init") {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
kind: "init",
|
|
||||||
ts,
|
|
||||||
model: typeof parsed.model === "string" ? parsed.model : "unknown",
|
|
||||||
sessionId: typeof parsed.session_id === "string" ? parsed.session_id : ""
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (type === "assistant") {
|
|
||||||
const message = asRecord(parsed.message) ?? {};
|
|
||||||
const content = Array.isArray(message.content) ? message.content : [];
|
|
||||||
const entries = [];
|
|
||||||
for (const blockRaw of content) {
|
|
||||||
const block = asRecord(blockRaw);
|
|
||||||
if (!block) continue;
|
|
||||||
const blockType = typeof block.type === "string" ? block.type : "";
|
|
||||||
if (blockType === "text") {
|
|
||||||
const text = typeof block.text === "string" ? block.text : "";
|
|
||||||
if (text) entries.push({ kind: "assistant", ts, text });
|
|
||||||
} else if (blockType === "thinking") {
|
|
||||||
const text = typeof block.thinking === "string" ? block.thinking : "";
|
|
||||||
if (text) entries.push({ kind: "thinking", ts, text });
|
|
||||||
} else if (blockType === "tool_use") {
|
|
||||||
entries.push({
|
|
||||||
kind: "tool_call",
|
|
||||||
ts,
|
|
||||||
name: typeof block.name === "string" ? block.name : "unknown",
|
|
||||||
toolUseId: typeof block.id === "string" ? block.id : typeof block.tool_use_id === "string" ? block.tool_use_id : void 0,
|
|
||||||
input: block.input ?? {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return entries.length > 0 ? entries : [{ kind: "stdout", ts, text: line }];
|
catch {
|
||||||
}
|
return null;
|
||||||
if (type === "user") {
|
}
|
||||||
const message = asRecord(parsed.message) ?? {};
|
}
|
||||||
const content = Array.isArray(message.content) ? message.content : [];
|
export function parseStdoutLine(line, ts) {
|
||||||
const entries = [];
|
const parsed = asRecord(safeJsonParse(line));
|
||||||
for (const blockRaw of content) {
|
if (!parsed) {
|
||||||
const block = asRecord(blockRaw);
|
return [{ kind: "stdout", ts, text: line }];
|
||||||
if (!block) continue;
|
}
|
||||||
const blockType = typeof block.type === "string" ? block.type : "";
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
||||||
if (blockType === "text") {
|
if (type === "system" && parsed.subtype === "init") {
|
||||||
const text = typeof block.text === "string" ? block.text : "";
|
return [
|
||||||
if (text) entries.push({ kind: "user", ts, text });
|
{
|
||||||
} else if (blockType === "tool_result") {
|
kind: "init",
|
||||||
const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
|
ts,
|
||||||
const isError = block.is_error === true;
|
model: typeof parsed.model === "string" ? parsed.model : "unknown",
|
||||||
let text = "";
|
sessionId: typeof parsed.session_id === "string" ? parsed.session_id : "",
|
||||||
if (typeof block.content === "string") {
|
},
|
||||||
text = block.content;
|
];
|
||||||
} else if (Array.isArray(block.content)) {
|
}
|
||||||
const parts = [];
|
if (type === "assistant") {
|
||||||
for (const part of block.content) {
|
const message = asRecord(parsed.message) ?? {};
|
||||||
const p = asRecord(part);
|
const content = Array.isArray(message.content) ? message.content : [];
|
||||||
if (p && typeof p.text === "string") parts.push(p.text);
|
const entries = [];
|
||||||
}
|
for (const blockRaw of content) {
|
||||||
text = parts.join("\n");
|
const block = asRecord(blockRaw);
|
||||||
|
if (!block)
|
||||||
|
continue;
|
||||||
|
const blockType = typeof block.type === "string" ? block.type : "";
|
||||||
|
if (blockType === "text") {
|
||||||
|
const text = typeof block.text === "string" ? block.text : "";
|
||||||
|
if (text)
|
||||||
|
entries.push({ kind: "assistant", ts, text });
|
||||||
|
}
|
||||||
|
else if (blockType === "thinking") {
|
||||||
|
const text = typeof block.thinking === "string" ? block.thinking : "";
|
||||||
|
if (text)
|
||||||
|
entries.push({ kind: "thinking", ts, text });
|
||||||
|
}
|
||||||
|
else if (blockType === "tool_use") {
|
||||||
|
entries.push({
|
||||||
|
kind: "tool_call",
|
||||||
|
ts,
|
||||||
|
name: typeof block.name === "string" ? block.name : "unknown",
|
||||||
|
toolUseId: typeof block.id === "string"
|
||||||
|
? block.id
|
||||||
|
: typeof block.tool_use_id === "string"
|
||||||
|
? block.tool_use_id
|
||||||
|
: undefined,
|
||||||
|
input: block.input ?? {},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
entries.push({ kind: "tool_result", ts, toolUseId, content: text, isError });
|
return entries.length > 0 ? entries : [{ kind: "stdout", ts, text: line }];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (entries.length > 0) return entries;
|
if (type === "user") {
|
||||||
}
|
const message = asRecord(parsed.message) ?? {};
|
||||||
if (type === "result") {
|
const content = Array.isArray(message.content) ? message.content : [];
|
||||||
const usage = asRecord(parsed.usage) ?? {};
|
const entries = [];
|
||||||
const inputTokens = asNumber(usage.input_tokens);
|
for (const blockRaw of content) {
|
||||||
const outputTokens = asNumber(usage.output_tokens);
|
const block = asRecord(blockRaw);
|
||||||
const cachedTokens = asNumber(usage.cache_read_input_tokens);
|
if (!block)
|
||||||
const costUsd = asNumber(parsed.total_cost_usd);
|
continue;
|
||||||
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
const blockType = typeof block.type === "string" ? block.type : "";
|
||||||
const isError = parsed.is_error === true;
|
if (blockType === "text") {
|
||||||
const errors = Array.isArray(parsed.errors) ? parsed.errors.map(errorText).filter(Boolean) : [];
|
const text = typeof block.text === "string" ? block.text : "";
|
||||||
const text = typeof parsed.result === "string" ? parsed.result : "";
|
if (text)
|
||||||
return [{
|
entries.push({ kind: "user", ts, text });
|
||||||
kind: "result",
|
}
|
||||||
ts,
|
else if (blockType === "tool_result") {
|
||||||
text,
|
const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
|
||||||
inputTokens,
|
const isError = block.is_error === true;
|
||||||
outputTokens,
|
let text = "";
|
||||||
cachedTokens,
|
if (typeof block.content === "string") {
|
||||||
costUsd,
|
text = block.content;
|
||||||
subtype,
|
}
|
||||||
isError,
|
else if (Array.isArray(block.content)) {
|
||||||
errors
|
const parts = [];
|
||||||
}];
|
for (const part of block.content) {
|
||||||
}
|
const p = asRecord(part);
|
||||||
return [{ kind: "stdout", ts, text: line }];
|
if (p && typeof p.text === "string")
|
||||||
|
parts.push(p.text);
|
||||||
|
}
|
||||||
|
text = parts.join("\n");
|
||||||
|
}
|
||||||
|
entries.push({ kind: "tool_result", ts, toolUseId, content: text, isError });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entries.length > 0)
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
if (type === "result") {
|
||||||
|
const usage = asRecord(parsed.usage) ?? {};
|
||||||
|
const inputTokens = asNumber(usage.input_tokens);
|
||||||
|
const outputTokens = asNumber(usage.output_tokens);
|
||||||
|
const cachedTokens = asNumber(usage.cache_read_input_tokens);
|
||||||
|
const costUsd = asNumber(parsed.total_cost_usd);
|
||||||
|
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
||||||
|
const isError = parsed.is_error === true;
|
||||||
|
const errors = Array.isArray(parsed.errors) ? parsed.errors.map(errorText).filter(Boolean) : [];
|
||||||
|
const text = typeof parsed.result === "string" ? parsed.result : "";
|
||||||
|
return [{
|
||||||
|
kind: "result",
|
||||||
|
ts,
|
||||||
|
text,
|
||||||
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
|
cachedTokens,
|
||||||
|
costUsd,
|
||||||
|
subtype,
|
||||||
|
isError,
|
||||||
|
errors,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [{ kind: "stdout", ts, text: line }];
|
||||||
}
|
}
|
||||||
|
//# sourceMappingURL=ui-parser.js.map
|
||||||
Generated
+7
-7
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "paperclip-adapter-claude-k8s",
|
"name": "paperclip-adapter-claude-k8s",
|
||||||
"version": "0.2.1",
|
"version": "0.2.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "paperclip-adapter-claude-k8s",
|
"name": "paperclip-adapter-claude-k8s",
|
||||||
"version": "0.2.1",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kubernetes/client-node": "^1.0.0",
|
"@kubernetes/client-node": "^1.0.0",
|
||||||
"picocolors": "^1.1.1"
|
"picocolors": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@paperclipai/adapter-utils": "2026.415.0-canary.7",
|
"@paperclipai/adapter-utils": "^2026.428.0",
|
||||||
"@types/node": "^24.6.0",
|
"@types/node": "^24.6.0",
|
||||||
"@vitest/coverage-v8": "^4.1.4",
|
"@vitest/coverage-v8": "^4.1.4",
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.24.0",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"vitest": "^4.1.4"
|
"vitest": "^4.1.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@paperclipai/adapter-utils": ">=2026.415.0-canary.7"
|
"@paperclipai/adapter-utils": ">=2026.428.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
@@ -649,9 +649,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@paperclipai/adapter-utils": {
|
"node_modules/@paperclipai/adapter-utils": {
|
||||||
"version": "2026.415.0-canary.7",
|
"version": "2026.428.0",
|
||||||
"resolved": "https://registry.npmjs.org/@paperclipai/adapter-utils/-/adapter-utils-2026.415.0-canary.7.tgz",
|
"resolved": "https://registry.npmjs.org/@paperclipai/adapter-utils/-/adapter-utils-2026.428.0.tgz",
|
||||||
"integrity": "sha512-VNzIZmu1lrK6QM8Ad9WkOihZItfkj21NHKQf+artDcbwFT2hHbDAD9hdW2W9NMVxYdFvvnws3w76FI/BUbCMbQ==",
|
"integrity": "sha512-kGHpE7rhePPCbnG3OwXbNuHZZuI+XyuFgNSiDnrEeiSbkI2c5XHM2WnWDCZ/NGHULfJW3lWhSxGMFoYqiy38vQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paperclip-adapter-claude-k8s",
|
"name": "paperclip-adapter-claude-k8s",
|
||||||
"version": "0.2.1",
|
"version": "0.2.5",
|
||||||
"description": "Paperclip adapter plugin that runs Claude Code agents as Kubernetes Jobs",
|
"description": "Paperclip adapter plugin that runs Claude Code agents as Kubernetes Jobs",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
"picocolors": "^1.1.1"
|
"picocolors": "^1.1.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@paperclipai/adapter-utils": ">=2026.415.0-canary.7"
|
"@paperclipai/adapter-utils": ">=2026.428.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@paperclipai/adapter-utils": "2026.415.0-canary.7",
|
"@paperclipai/adapter-utils": "^2026.428.0",
|
||||||
"@types/node": "^24.6.0",
|
"@types/node": "^24.6.0",
|
||||||
"@vitest/coverage-v8": "^4.1.4",
|
"@vitest/coverage-v8": "^4.1.4",
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.24.0",
|
||||||
|
|||||||
@@ -1234,7 +1234,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
waitForJobCompletion(namespace, jobName, completionTimeoutMs, kubeconfigPath, jobObserver).then(r => { logStopSignal.stopped = true; return r; }),
|
waitForJobCompletion(namespace, jobName, completionTimeoutMs, kubeconfigPath, jobObserver).then(r => { logStopSignal.stopped = true; return r; }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const stdout = tailResult.status === "fulfilled" ? tailResult.value : "";
|
stdout = tailResult.status === "fulfilled" ? tailResult.value : "";
|
||||||
|
|
||||||
if (completionResult.status === "fulfilled") {
|
if (completionResult.status === "fulfilled") {
|
||||||
jobTimedOut = completionResult.value.timedOut;
|
jobTimedOut = completionResult.value.timedOut;
|
||||||
|
|||||||
@@ -301,8 +301,7 @@ describe("buildJobManifest", () => {
|
|||||||
const init = job.spec?.template?.spec?.initContainers?.[0];
|
const init = job.spec?.template?.spec?.initContainers?.[0];
|
||||||
expect(init?.command?.[0]).toBe("sh");
|
expect(init?.command?.[0]).toBe("sh");
|
||||||
expect(init?.command?.[1]).toBe("-c");
|
expect(init?.command?.[1]).toBe("-c");
|
||||||
expect(init?.command?.[2]).toContain("mkdir -p /paperclip/instances/default/run-logs/");
|
expect(init?.command?.[2]).toBe("printf '%s' \"$PROMPT_CONTENT\" > /tmp/prompt/prompt.txt");
|
||||||
expect(init?.command?.[2]).toContain("printf '%s' \"$PROMPT_CONTENT\" > /tmp/prompt/prompt.txt");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("write-prompt mounts prompt volume", () => {
|
it("write-prompt mounts prompt volume", () => {
|
||||||
@@ -808,28 +807,26 @@ describe("buildJobManifest", () => {
|
|||||||
const { job } = buildJobManifest({ ctx, selfPod });
|
const { job } = buildJobManifest({ ctx, selfPod });
|
||||||
const cmd = job.spec?.template?.spec?.containers[0]?.command?.[2] ?? "";
|
const cmd = job.spec?.template?.spec?.containers[0]?.command?.[2] ?? "";
|
||||||
expect(cmd).toContain("| tee");
|
expect(cmd).toContain("| tee");
|
||||||
expect(cmd).toContain("/paperclip/instances/default/run-logs/");
|
expect(cmd).toContain("/paperclip/instances/default/data/run-logs/");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("podLogPath is returned from buildJobManifest", () => {
|
it("podLogPath is returned from buildJobManifest", () => {
|
||||||
const result = buildJobManifest({ ctx, selfPod });
|
const result = buildJobManifest({ ctx, selfPod });
|
||||||
expect(result.podLogPath).toBe(
|
expect(result.podLogPath).toBe(
|
||||||
"/paperclip/instances/default/run-logs/co1/agent-abc/run-abc12345.pod.ndjson",
|
"/paperclip/instances/default/data/run-logs/co1/agent-abc/run-abc12345.pod.ndjson",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("buildPodLogPath returns correctly formatted path", () => {
|
it("buildPodLogPath returns correctly formatted path", () => {
|
||||||
expect(buildPodLogPath("co1", "agent-abc", "run-abc12345")).toBe(
|
expect(buildPodLogPath("co1", "agent-abc", "run-abc12345")).toBe(
|
||||||
"/paperclip/instances/default/run-logs/co1/agent-abc/run-abc12345.pod.ndjson",
|
"/paperclip/instances/default/data/run-logs/co1/agent-abc/run-abc12345.pod.ndjson",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("init container creates log directory", () => {
|
it("init container does not create log directory (server pre-creates it on shared PVC)", () => {
|
||||||
const { job } = buildJobManifest({ ctx, selfPod });
|
const { job } = buildJobManifest({ ctx, selfPod });
|
||||||
const initCmd = job.spec?.template?.spec?.initContainers?.[0]?.command;
|
const initCmd = job.spec?.template?.spec?.initContainers?.[0]?.command;
|
||||||
expect(initCmd?.[0]).toBe("sh");
|
expect(initCmd?.[2]).not.toContain("mkdir -p /paperclip");
|
||||||
expect(initCmd?.[1]).toBe("-c");
|
|
||||||
expect(initCmd?.[2]).toContain("mkdir -p /paperclip/instances/default/run-logs/");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sanitizes companyId with / to valid path component for log path", () => {
|
it("sanitizes companyId with / to valid path component for log path", () => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function sanitizeForK8sPath(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildPodLogPath(companyId: string, agentId: string, runId: string): string {
|
export function buildPodLogPath(companyId: string, agentId: string, runId: string): string {
|
||||||
return `/paperclip/instances/default/run-logs/${companyId}/${agentId}/${runId}.pod.ndjson`;
|
return `/paperclip/instances/default/data/run-logs/${companyId}/${agentId}/${runId}.pod.ndjson`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Prompts above this size (bytes) are staged via a Secret instead of an
|
/** Prompts above this size (bytes) are staged via a Secret instead of an
|
||||||
@@ -504,7 +504,7 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult {
|
|||||||
name: "write-prompt",
|
name: "write-prompt",
|
||||||
image: "busybox:1.36",
|
image: "busybox:1.36",
|
||||||
imagePullPolicy: "IfNotPresent",
|
imagePullPolicy: "IfNotPresent",
|
||||||
command: ["sh", "-c", `mkdir -p /paperclip/instances/default/run-logs/${agent.companyId}/${agent.id} && cp /tmp/prompt-secret/prompt.txt /tmp/prompt/prompt.txt`],
|
command: ["sh", "-c", "cp /tmp/prompt-secret/prompt.txt /tmp/prompt/prompt.txt"],
|
||||||
volumeMounts: [
|
volumeMounts: [
|
||||||
{ name: "prompt", mountPath: "/tmp/prompt" },
|
{ name: "prompt", mountPath: "/tmp/prompt" },
|
||||||
{ name: "prompt-secret", mountPath: "/tmp/prompt-secret", readOnly: true },
|
{ name: "prompt-secret", mountPath: "/tmp/prompt-secret", readOnly: true },
|
||||||
@@ -519,7 +519,7 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult {
|
|||||||
name: "write-prompt",
|
name: "write-prompt",
|
||||||
image: "busybox:1.36",
|
image: "busybox:1.36",
|
||||||
imagePullPolicy: "IfNotPresent",
|
imagePullPolicy: "IfNotPresent",
|
||||||
command: ["sh", "-c", `mkdir -p /paperclip/instances/default/run-logs/${agent.companyId}/${agent.id} && printf '%s' "$PROMPT_CONTENT" > /tmp/prompt/prompt.txt`],
|
command: ["sh", "-c", `printf '%s' "$PROMPT_CONTENT" > /tmp/prompt/prompt.txt`],
|
||||||
env: [{ name: "PROMPT_CONTENT", value: prompt }],
|
env: [{ name: "PROMPT_CONTENT", value: prompt }],
|
||||||
volumeMounts: [{ name: "prompt", mountPath: "/tmp/prompt" }],
|
volumeMounts: [{ name: "prompt", mountPath: "/tmp/prompt" }],
|
||||||
securityContext,
|
securityContext,
|
||||||
|
|||||||
Reference in New Issue
Block a user