@botbotgo/runtime 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +1 -0
- package/.github/workflows/release.yml +63 -0
- package/config/examples/runtime.yaml +14 -0
- package/config/examples/tool.yaml +1 -1
- package/dist/config/resolveRuntimeConfig.d.ts +3 -1
- package/dist/config/resolveRuntimeConfig.d.ts.map +1 -1
- package/dist/config/resolveRuntimeConfig.js +2 -0
- package/dist/config/resolveRuntimeConfig.js.map +1 -1
- package/dist/config/resources.d.ts +17 -0
- package/dist/config/resources.d.ts.map +1 -1
- package/dist/config/resources.js.map +1 -1
- package/dist/runtime/bootstrap/runtimeFactory.d.ts.map +1 -1
- package/dist/runtime/bootstrap/runtimeFactory.js +4 -0
- package/dist/runtime/bootstrap/runtimeFactory.js.map +1 -1
- package/dist/runtime/execution/agentRunExecutor.d.ts +1 -0
- package/dist/runtime/execution/agentRunExecutor.d.ts.map +1 -1
- package/dist/runtime/execution/agentRunExecutor.js +3 -0
- package/dist/runtime/execution/agentRunExecutor.js.map +1 -1
- package/dist/runtime/execution/agentRunExecutor.types.d.ts +2 -0
- package/dist/runtime/execution/agentRunExecutor.types.d.ts.map +1 -1
- package/dist/runtime/middleware/agentToolMiddleware.d.ts +2 -0
- package/dist/runtime/middleware/agentToolMiddleware.d.ts.map +1 -1
- package/dist/runtime/middleware/agentToolMiddleware.js +17 -4
- package/dist/runtime/middleware/agentToolMiddleware.js.map +1 -1
- package/dist/runtime/middleware/commandPolicy.d.ts +2 -1
- package/dist/runtime/middleware/commandPolicy.d.ts.map +1 -1
- package/dist/runtime/middleware/commandPolicy.js +14 -11
- package/dist/runtime/middleware/commandPolicy.js.map +1 -1
- package/dist/runtime/middleware/frameworkPrompt.d.ts.map +1 -1
- package/dist/runtime/middleware/frameworkPrompt.js +2 -3
- package/dist/runtime/middleware/frameworkPrompt.js.map +1 -1
- package/dist/runtime/middleware/toolArgsNormalizer.d.ts +1 -0
- package/dist/runtime/middleware/toolArgsNormalizer.d.ts.map +1 -1
- package/dist/runtime/middleware/toolArgsNormalizer.js +32 -0
- package/dist/runtime/middleware/toolArgsNormalizer.js.map +1 -1
- package/dist/runtime/middleware/toolCallGuard.d.ts +3 -1
- package/dist/runtime/middleware/toolCallGuard.d.ts.map +1 -1
- package/dist/runtime/middleware/toolCallGuard.js +24 -4
- package/dist/runtime/middleware/toolCallGuard.js.map +1 -1
- package/dist/runtime/middleware/types.d.ts +2 -0
- package/dist/runtime/middleware/types.d.ts.map +1 -1
- package/dist/runtime/middleware/types.js.map +1 -1
- package/dist/runtime/runtimeService.d.ts +2 -0
- package/dist/runtime/runtimeService.d.ts.map +1 -1
- package/dist/runtime/runtimeService.js +1 -0
- package/dist/runtime/runtimeService.js.map +1 -1
- package/dist/runtime/stream/runArtifacts.d.ts.map +1 -1
- package/dist/runtime/stream/runArtifacts.js +3 -1
- package/dist/runtime/stream/runArtifacts.js.map +1 -1
- package/dist/state/runState.d.ts +1 -0
- package/dist/state/runState.d.ts.map +1 -1
- package/dist/state/runState.js +18 -1
- package/dist/state/runState.js.map +1 -1
- package/dist/state/workspaceState.d.ts +2 -0
- package/dist/state/workspaceState.d.ts.map +1 -1
- package/dist/state/workspaceState.js +12 -10
- package/dist/state/workspaceState.js.map +1 -1
- package/example/config/model.yaml +2 -2
- package/example/config/runtime.yaml +19 -1
- package/example/package.json +0 -1
- package/package.json +1 -1
- package/src/config/resolveRuntimeConfig.ts +5 -0
- package/src/config/resources.ts +19 -0
- package/src/runtime/bootstrap/runtimeFactory.ts +7 -0
- package/src/runtime/execution/agentRunExecutor.ts +3 -0
- package/src/runtime/execution/agentRunExecutor.types.ts +2 -0
- package/src/runtime/middleware/agentToolMiddleware.ts +19 -3
- package/src/runtime/middleware/commandPolicy.ts +22 -10
- package/src/runtime/middleware/frameworkPrompt.ts +2 -3
- package/src/runtime/middleware/toolArgsNormalizer.ts +36 -0
- package/src/runtime/middleware/toolCallGuard.ts +37 -3
- package/src/runtime/middleware/types.ts +2 -0
- package/src/runtime/runtimeService.ts +3 -0
- package/src/runtime/stream/runArtifacts.ts +3 -1
- package/src/state/runState.ts +19 -1
- package/src/state/workspaceState.ts +19 -11
- package/test/unit/config/loader.test.ts +10 -0
- package/test/unit/runtime/agentToolMiddleware.test.ts +51 -0
- package/test/unit/runtime/toolArgsNormalizer.test.ts +34 -0
- package/test/unit/runtime/toolCallGuard.test.ts +71 -0
- package/test/unit/runtime/workspaceState.test.ts +94 -0
- package/example/.tsbuildinfo +0 -1
- package/example/build/.tsbuildinfo +0 -1
- package/example/serve-output.mjs +0 -52
|
@@ -20,7 +20,12 @@ export interface AgentWorkspaceState {
|
|
|
20
20
|
todosFile: string;
|
|
21
21
|
runStateFile: string;
|
|
22
22
|
artifactFiles: Record<string, string>;
|
|
23
|
-
prepareRun(params: {
|
|
23
|
+
prepareRun(params: {
|
|
24
|
+
prompt: string;
|
|
25
|
+
resumeInterruptedRun?: boolean;
|
|
26
|
+
fallbackThreadId?: string;
|
|
27
|
+
resumeHint: string;
|
|
28
|
+
}): Promise<PreparedWorkspaceRun>;
|
|
24
29
|
readRunState(): Promise<AgentRunState | null>;
|
|
25
30
|
getThreadId(previousState: AgentRunState | null, fallbackThreadId: string): string;
|
|
26
31
|
buildResumePrompt(basePrompt: string, previousState: AgentRunState | null, resumeHint: string): string;
|
|
@@ -89,22 +94,24 @@ export class AgentWorkspaceStateManager implements AgentWorkspaceState {
|
|
|
89
94
|
|
|
90
95
|
public async prepareRun(params: {
|
|
91
96
|
prompt: string;
|
|
97
|
+
resumeInterruptedRun?: boolean;
|
|
92
98
|
fallbackThreadId?: string;
|
|
93
99
|
resumeHint: string;
|
|
94
100
|
}): Promise<PreparedWorkspaceRun> {
|
|
95
101
|
const previousState = await this.runState.read();
|
|
96
|
-
const
|
|
102
|
+
const effectivePreviousState = params.resumeInterruptedRun ? previousState : null;
|
|
103
|
+
const threadId = this.runState.getThreadId(effectivePreviousState, params.fallbackThreadId ?? `t-${Date.now()}`);
|
|
97
104
|
this.events?.emit({
|
|
98
105
|
name: "agent.runtime2.workspace.prepare",
|
|
99
106
|
from: "agent-runtime2.runtime",
|
|
100
107
|
to: "agent-runtime2.workspace",
|
|
101
|
-
payload: { threadId, previousState },
|
|
108
|
+
payload: { threadId, previousState: effectivePreviousState },
|
|
102
109
|
});
|
|
103
|
-
await this.resetForFreshRun(
|
|
110
|
+
await this.resetForFreshRun(effectivePreviousState);
|
|
104
111
|
return WorkspaceRunSessionFactory.create({
|
|
105
112
|
prompt: params.prompt,
|
|
106
113
|
threadId,
|
|
107
|
-
previousState,
|
|
114
|
+
previousState: effectivePreviousState,
|
|
108
115
|
resumeHint: params.resumeHint,
|
|
109
116
|
runState: this.runState,
|
|
110
117
|
events: this.events,
|
|
@@ -176,13 +183,14 @@ export class AgentWorkspaceStateManager implements AgentWorkspaceState {
|
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
public async resetForFreshRun(previousState: AgentRunState | null): Promise<void> {
|
|
179
|
-
if (previousState?.status
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
if (previousState?.status === "running") return;
|
|
187
|
+
await Promise.all([
|
|
188
|
+
unlink(this.todosFile).catch(() => {}),
|
|
189
|
+
unlink(this.legacyTodosFile).catch(() => {}),
|
|
190
|
+
...(!this.legacyOutputState ? [
|
|
183
191
|
unlink(this.legacyRunStateFile).catch(() => {}),
|
|
184
|
-
])
|
|
185
|
-
|
|
192
|
+
] : []),
|
|
193
|
+
]);
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
public async persistTodos(todos: unknown): Promise<void> {
|
|
@@ -95,6 +95,12 @@ spec:
|
|
|
95
95
|
malformedToolCallMaxRetries: 1
|
|
96
96
|
idleTimeoutMaxRetries: 1
|
|
97
97
|
heartbeatIntervalMs: 10000
|
|
98
|
+
middleware:
|
|
99
|
+
forbidScriptSourceRead: true
|
|
100
|
+
forbidAbsolutePathsOutsideWorkspace: true
|
|
101
|
+
commandPolicy:
|
|
102
|
+
blockDirectNetworkFetch: true
|
|
103
|
+
blockShellDashLc: true
|
|
98
104
|
debug:
|
|
99
105
|
run: true
|
|
100
106
|
workspace: true
|
|
@@ -130,6 +136,8 @@ describe("config/loader", () => {
|
|
|
130
136
|
assert.strictEqual(r.spec.systemPrompt, "You are a runtime test agent.");
|
|
131
137
|
assert.strictEqual(r.spec.backend?.type, "local_shell");
|
|
132
138
|
assert.strictEqual(r.spec.malformedToolCallMaxRetries, 1);
|
|
139
|
+
assert.strictEqual(r.spec.middleware?.forbidScriptSourceRead, true);
|
|
140
|
+
assert.strictEqual(r.spec.middleware?.commandPolicy?.blockDirectNetworkFetch, true);
|
|
133
141
|
assert.strictEqual(r.spec.debug?.toolCall, true);
|
|
134
142
|
assert.strictEqual(r.spec.debug?.stream, false);
|
|
135
143
|
assert.strictEqual(r.spec.eventLogLevel, "tools");
|
|
@@ -331,6 +339,8 @@ describe("deepagents", () => {
|
|
|
331
339
|
assert.strictEqual(resolved.runtime.systemPrompt, "You are a runtime test agent.");
|
|
332
340
|
assert.strictEqual(resolved.runtime.llmIdleTimeoutMs, 300000);
|
|
333
341
|
assert.strictEqual(resolved.runtime.heartbeatIntervalMs, 10000);
|
|
342
|
+
assert.strictEqual(resolved.runtime.middleware?.forbidScriptSourceRead, true);
|
|
343
|
+
assert.strictEqual(resolved.runtime.middleware?.commandPolicy?.blockShellDashLc, true);
|
|
334
344
|
assert.strictEqual(resolved.runtime.debug?.run, true);
|
|
335
345
|
assert.strictEqual(resolved.runtime.debug?.stream, false);
|
|
336
346
|
assert.strictEqual(resolved.runtime.eventLogLevel, "tools");
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { AgentToolMiddlewareFactory } from "../../../src/runtime/middleware/agentToolMiddleware.ts";
|
|
4
|
+
|
|
5
|
+
class FakeToolMessage {
|
|
6
|
+
public readonly content: string;
|
|
7
|
+
public readonly tool_call_id: string;
|
|
8
|
+
|
|
9
|
+
public constructor(fields: { content: string; tool_call_id: string }) {
|
|
10
|
+
this.content = fields.content;
|
|
11
|
+
this.tool_call_id = fields.tool_call_id;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("AgentToolMiddleware emits blocked error details for policy violations", async () => {
|
|
16
|
+
const events: Array<{ name: string; payload?: Record<string, unknown> }> = [];
|
|
17
|
+
const middleware = new AgentToolMiddlewareFactory({
|
|
18
|
+
rootDir: "/workspace",
|
|
19
|
+
createMiddleware: (definition) => definition,
|
|
20
|
+
ToolMessage: FakeToolMessage,
|
|
21
|
+
events: {
|
|
22
|
+
emit: (event) => {
|
|
23
|
+
events.push(event);
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}).create() as {
|
|
27
|
+
wrapToolCall: (request: unknown, handler: (request: unknown) => unknown) => Promise<unknown>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const result = await middleware.wrapToolCall(
|
|
31
|
+
{
|
|
32
|
+
toolCall: {
|
|
33
|
+
id: "call-1",
|
|
34
|
+
name: "read_file",
|
|
35
|
+
args: { path: "/tmp/outside-workspace.sh" },
|
|
36
|
+
},
|
|
37
|
+
threadId: "thread-1",
|
|
38
|
+
},
|
|
39
|
+
async () => {
|
|
40
|
+
throw new Error("handler should not be called");
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
assert.ok(result instanceof FakeToolMessage);
|
|
45
|
+
assert.match(result.content, /Do not use absolute filesystem paths outside workspace root/);
|
|
46
|
+
|
|
47
|
+
const blocked = events.find((event) => event.name === "agent.runtime2.tool.call.blocked");
|
|
48
|
+
assert.ok(blocked);
|
|
49
|
+
assert.equal(blocked.payload?.reason, "policy_violation");
|
|
50
|
+
assert.match(String(blocked.payload?.error ?? ""), /Do not use absolute filesystem paths outside workspace root/);
|
|
51
|
+
});
|
|
@@ -76,6 +76,7 @@ test("FrameworkPrompt includes active skill mapping in framework system prompt",
|
|
|
76
76
|
assert.match(prompt, /other-skill => \/cache\/skills\/other-skill/);
|
|
77
77
|
assert.match(prompt, /\$\{SKILL_PATH:<skill-id>\}/);
|
|
78
78
|
assert.match(prompt, /\$\{WORKSPACE\}/);
|
|
79
|
+
assert.doesNotMatch(prompt, /\bls\b|\bglob\b/);
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
test("buildSkillPathMap expands skills root directories into named skill paths", async () => {
|
|
@@ -134,3 +135,36 @@ test("ToolArgsNormalizer expands unnamed SKILL_PATH to the only active skill dir
|
|
|
134
135
|
|
|
135
136
|
await rm(root, { recursive: true, force: true });
|
|
136
137
|
});
|
|
138
|
+
|
|
139
|
+
test("ToolArgsNormalizer strips workspace path prefixes from mistaken repo-relative file and command paths", async () => {
|
|
140
|
+
const root = join(tmpdir(), `agent-runtime2-workspace-paths-${Date.now()}`, "framework", "runtime", "example");
|
|
141
|
+
const skillRoot = join(root, ".agent", "cache", "skills", "company-report");
|
|
142
|
+
await mkdir(join(skillRoot, "scripts"), { recursive: true });
|
|
143
|
+
await writeFile(join(skillRoot, "scripts", "merge-report.mjs"), "export {};\n", "utf8");
|
|
144
|
+
|
|
145
|
+
const normalizedWrite = ToolArgsNormalizer.normalizeToolArgs(
|
|
146
|
+
root,
|
|
147
|
+
[skillRoot],
|
|
148
|
+
"write_file",
|
|
149
|
+
{
|
|
150
|
+
file_path: "runtime/example/output/company-report.json",
|
|
151
|
+
content: "{}",
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
assert.equal(normalizedWrite.file_path, "output/company-report.json");
|
|
155
|
+
|
|
156
|
+
const normalizedExecute = ToolArgsNormalizer.normalizeToolArgs(
|
|
157
|
+
root,
|
|
158
|
+
[skillRoot],
|
|
159
|
+
"execute",
|
|
160
|
+
{
|
|
161
|
+
command: "node scripts/merge-report.mjs runtime/example/output/company-report.json runtime/example/output/company-report.html",
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
assert.equal(
|
|
165
|
+
normalizedExecute.command,
|
|
166
|
+
"node .agent/cache/skills/company-report/scripts/merge-report.mjs output/company-report.json output/company-report.html",
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
await rm(root, { recursive: true, force: true });
|
|
170
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { ToolCallGuard } from "../../../src/runtime/middleware/toolCallGuard.ts";
|
|
4
|
+
|
|
5
|
+
class FakeToolMessage {
|
|
6
|
+
public readonly content: string;
|
|
7
|
+
public readonly tool_call_id: string;
|
|
8
|
+
|
|
9
|
+
public constructor(fields: { content: string; tool_call_id: string }) {
|
|
10
|
+
this.content = fields.content;
|
|
11
|
+
this.tool_call_id = fields.tool_call_id;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("ToolCallGuard blocks script source reads by default", () => {
|
|
16
|
+
const result = ToolCallGuard.maybeBlock(
|
|
17
|
+
"read_file",
|
|
18
|
+
{ path: "skills/company-report/scripts/merge-report.mjs" },
|
|
19
|
+
"call-1",
|
|
20
|
+
FakeToolMessage,
|
|
21
|
+
"/workspace",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
assert.ok(result instanceof FakeToolMessage);
|
|
25
|
+
assert.match(result.content, /Do not inspect script source files/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("ToolCallGuard respects runtime middleware policy overrides", () => {
|
|
29
|
+
const readAllowed = ToolCallGuard.maybeBlock(
|
|
30
|
+
"read_file",
|
|
31
|
+
{ path: "skills/company-report/scripts/merge-report.mjs" },
|
|
32
|
+
"call-2",
|
|
33
|
+
FakeToolMessage,
|
|
34
|
+
"/workspace",
|
|
35
|
+
undefined,
|
|
36
|
+
[],
|
|
37
|
+
{
|
|
38
|
+
forbidScriptSourceRead: false,
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
assert.equal(readAllowed, null);
|
|
42
|
+
|
|
43
|
+
const executeAllowed = ToolCallGuard.maybeBlock(
|
|
44
|
+
"execute",
|
|
45
|
+
{ command: "curl https://example.com/data.json" },
|
|
46
|
+
"call-3",
|
|
47
|
+
FakeToolMessage,
|
|
48
|
+
"/workspace",
|
|
49
|
+
undefined,
|
|
50
|
+
[],
|
|
51
|
+
{
|
|
52
|
+
commandPolicy: {
|
|
53
|
+
blockDirectNetworkFetch: false,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
assert.equal(executeAllowed, null);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("ToolCallGuard blocks reads of internal runtime state files", () => {
|
|
61
|
+
const result = ToolCallGuard.maybeBlock(
|
|
62
|
+
"read_file",
|
|
63
|
+
{ path: ".agent/run-state.json" },
|
|
64
|
+
"call-4",
|
|
65
|
+
FakeToolMessage,
|
|
66
|
+
"/workspace",
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
assert.ok(result instanceof FakeToolMessage);
|
|
70
|
+
assert.match(result.content, /Do not inspect internal runtime state files under \.agent\//);
|
|
71
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { createAgentWorkspaceState } from "../../../src/state/workspaceState.ts";
|
|
7
|
+
|
|
8
|
+
test("workspace prepareRun starts fresh when resumeInterruptedRun is disabled", async () => {
|
|
9
|
+
const rootDir = join(tmpdir(), `agent-runtime2-workspace-${Date.now()}`);
|
|
10
|
+
const outputDir = join(rootDir, "output");
|
|
11
|
+
const agentDir = join(rootDir, ".agent");
|
|
12
|
+
await mkdir(outputDir, { recursive: true });
|
|
13
|
+
await mkdir(agentDir, { recursive: true });
|
|
14
|
+
await writeFile(
|
|
15
|
+
join(agentDir, "run-state.json"),
|
|
16
|
+
JSON.stringify({
|
|
17
|
+
prompt: "old",
|
|
18
|
+
threadId: "t-old",
|
|
19
|
+
status: "running",
|
|
20
|
+
startedAt: new Date().toISOString(),
|
|
21
|
+
updatedAt: new Date().toISOString(),
|
|
22
|
+
stepCount: 3,
|
|
23
|
+
lastSummary: "old summary",
|
|
24
|
+
artifacts: {},
|
|
25
|
+
}),
|
|
26
|
+
"utf8",
|
|
27
|
+
);
|
|
28
|
+
await writeFile(join(agentDir, "todos.json"), "[]\n", "utf8");
|
|
29
|
+
await writeFile(join(outputDir, "company-report.json"), "{\"stale\":true}\n", "utf8");
|
|
30
|
+
|
|
31
|
+
const workspace = createAgentWorkspaceState({
|
|
32
|
+
rootDir,
|
|
33
|
+
artifacts: [{ key: "companyReport", path: "output/company-report.json" }],
|
|
34
|
+
});
|
|
35
|
+
const prepared = await workspace.prepareRun({
|
|
36
|
+
prompt: "new prompt",
|
|
37
|
+
resumeInterruptedRun: false,
|
|
38
|
+
resumeHint: "resume",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
assert.notEqual(prepared.threadId, "t-old");
|
|
42
|
+
assert.equal(prepared.input.messages[0]?.content, "new prompt");
|
|
43
|
+
await assert.rejects(readFile(join(agentDir, "todos.json"), "utf8"));
|
|
44
|
+
|
|
45
|
+
await rm(rootDir, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("workspace prepareRun can explicitly resume an interrupted run with checkpoint context", async () => {
|
|
49
|
+
const rootDir = join(tmpdir(), `agent-runtime2-workspace-resume-${Date.now()}`);
|
|
50
|
+
const outputDir = join(rootDir, "output");
|
|
51
|
+
const agentDir = join(rootDir, ".agent");
|
|
52
|
+
await mkdir(outputDir, { recursive: true });
|
|
53
|
+
await mkdir(agentDir, { recursive: true });
|
|
54
|
+
await writeFile(
|
|
55
|
+
join(agentDir, "run-state.json"),
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
prompt: "old",
|
|
58
|
+
threadId: "t-old",
|
|
59
|
+
status: "running",
|
|
60
|
+
startedAt: new Date().toISOString(),
|
|
61
|
+
updatedAt: new Date().toISOString(),
|
|
62
|
+
stepCount: 3,
|
|
63
|
+
lastSummary: "Tool call: yahooFinanceNews",
|
|
64
|
+
artifacts: {
|
|
65
|
+
companyReportPresent: true,
|
|
66
|
+
companyReportHtmlPresent: false,
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
"utf8",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const workspace = createAgentWorkspaceState({
|
|
73
|
+
rootDir,
|
|
74
|
+
artifacts: [
|
|
75
|
+
{ key: "companyReport", path: "output/company-report.json" },
|
|
76
|
+
{ key: "companyReportHtml", path: "output/company-report.html" },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
const prepared = await workspace.prepareRun({
|
|
80
|
+
prompt: "new prompt",
|
|
81
|
+
resumeInterruptedRun: true,
|
|
82
|
+
resumeHint: "Continue from the checkpoint instead of restarting.",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
assert.equal(prepared.threadId, "t-old");
|
|
86
|
+
assert.match(prepared.input.messages[0]?.content ?? "", /RESUME_CHECKPOINT/);
|
|
87
|
+
assert.match(prepared.input.messages[0]?.content ?? "", /"threadId": "t-old"/);
|
|
88
|
+
assert.match(prepared.input.messages[0]?.content ?? "", /"lastSummary": "Tool call: yahooFinanceNews"/);
|
|
89
|
+
assert.match(prepared.input.messages[0]?.content ?? "", /"companyReport": "ready"/);
|
|
90
|
+
assert.match(prepared.input.messages[0]?.content ?? "", /"companyReportHtml": "missing"/);
|
|
91
|
+
assert.match(prepared.input.messages[0]?.content ?? "", /Do not read \.agent\/\*/);
|
|
92
|
+
|
|
93
|
+
await rm(rootDir, { recursive: true, force: true });
|
|
94
|
+
});
|