@clinebot/core 0.0.0 → 0.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/README.md +7 -7
- package/dist/default-tools/definitions.d.ts +1 -1
- package/dist/default-tools/executors/index.d.ts +1 -1
- package/dist/default-tools/index.d.ts +2 -1
- package/dist/default-tools/model-tool-routing.d.ts +33 -0
- package/dist/default-tools/schemas.d.ts +13 -7
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.js +220 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +47 -47
- package/dist/index.node.d.ts +36 -0
- package/dist/index.node.js +622 -0
- package/dist/providers/local-provider-service.d.ts +37 -0
- package/dist/session/default-session-manager.d.ts +3 -1
- package/dist/session/session-host.d.ts +2 -2
- package/dist/session/session-manager.d.ts +8 -0
- package/dist/session/unified-session-persistence-service.d.ts +1 -1
- package/dist/session/utils/helpers.d.ts +11 -0
- package/dist/session/utils/types.d.ts +42 -0
- package/dist/session/utils/usage.d.ts +9 -0
- package/dist/storage/provider-settings-manager.d.ts +2 -0
- package/dist/types/config.d.ts +8 -1
- package/dist/types.d.ts +1 -1
- package/package.json +11 -32
- package/src/default-tools/definitions.test.ts +130 -1
- package/src/default-tools/definitions.ts +7 -3
- package/src/default-tools/executors/editor.ts +10 -9
- package/src/default-tools/executors/file-read.test.ts +1 -1
- package/src/default-tools/executors/file-read.ts +11 -6
- package/src/default-tools/executors/index.ts +1 -1
- package/src/default-tools/index.ts +6 -1
- package/src/default-tools/model-tool-routing.test.ts +86 -0
- package/src/default-tools/model-tool-routing.ts +132 -0
- package/src/default-tools/schemas.ts +49 -52
- package/src/index.browser.ts +1 -0
- package/src/{server/index.ts → index.node.ts} +51 -109
- package/src/index.ts +41 -2
- package/src/input/file-indexer.ts +28 -2
- package/src/providers/local-provider-service.ts +591 -0
- package/src/runtime/runtime-builder.test.ts +69 -0
- package/src/runtime/runtime-builder.ts +20 -0
- package/src/runtime/runtime-parity.test.ts +20 -9
- package/src/session/default-session-manager.e2e.test.ts +11 -1
- package/src/session/default-session-manager.test.ts +270 -0
- package/src/session/default-session-manager.ts +109 -191
- package/src/session/index.ts +7 -2
- package/src/session/session-host.ts +30 -18
- package/src/session/session-manager.ts +11 -0
- package/src/session/unified-session-persistence-service.ts +11 -5
- package/src/session/utils/helpers.ts +148 -0
- package/src/session/utils/types.ts +46 -0
- package/src/session/utils/usage.ts +32 -0
- package/src/storage/provider-settings-legacy-migration.test.ts +3 -3
- package/src/storage/provider-settings-manager.test.ts +34 -0
- package/src/storage/provider-settings-manager.ts +22 -1
- package/src/types/config.ts +13 -0
- package/src/types.ts +1 -0
- package/dist/server/index.d.ts +0 -47
- package/dist/server/index.js +0 -641
|
@@ -74,6 +74,75 @@ describe("DefaultRuntimeBuilder", () => {
|
|
|
74
74
|
expect(names).not.toContain("editor");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
it("uses apply_patch instead of editor for codex/gpt model IDs in act mode", () => {
|
|
78
|
+
const runtime = new DefaultRuntimeBuilder().build({
|
|
79
|
+
config: {
|
|
80
|
+
providerId: "openai",
|
|
81
|
+
modelId: "openai/gpt-5.4",
|
|
82
|
+
apiKey: "key",
|
|
83
|
+
systemPrompt: "test",
|
|
84
|
+
cwd: process.cwd(),
|
|
85
|
+
mode: "act",
|
|
86
|
+
enableTools: true,
|
|
87
|
+
enableSpawnAgent: false,
|
|
88
|
+
enableAgentTeams: false,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const names = runtime.tools.map((tool) => tool.name);
|
|
93
|
+
expect(names).toContain("apply_patch");
|
|
94
|
+
expect(names).not.toContain("editor");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("keeps editor for non-codex/non-gpt model IDs in act mode", () => {
|
|
98
|
+
const runtime = new DefaultRuntimeBuilder().build({
|
|
99
|
+
config: {
|
|
100
|
+
providerId: "anthropic",
|
|
101
|
+
modelId: "claude-sonnet-4-6",
|
|
102
|
+
apiKey: "key",
|
|
103
|
+
systemPrompt: "test",
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
mode: "act",
|
|
106
|
+
enableTools: true,
|
|
107
|
+
enableSpawnAgent: false,
|
|
108
|
+
enableAgentTeams: false,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const names = runtime.tools.map((tool) => tool.name);
|
|
113
|
+
expect(names).toContain("editor");
|
|
114
|
+
expect(names).not.toContain("apply_patch");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("applies custom tool routing rules from session config", () => {
|
|
118
|
+
const runtime = new DefaultRuntimeBuilder().build({
|
|
119
|
+
config: {
|
|
120
|
+
providerId: "anthropic",
|
|
121
|
+
modelId: "claude-sonnet-4-6",
|
|
122
|
+
apiKey: "key",
|
|
123
|
+
systemPrompt: "test",
|
|
124
|
+
cwd: process.cwd(),
|
|
125
|
+
mode: "act",
|
|
126
|
+
enableTools: true,
|
|
127
|
+
enableSpawnAgent: false,
|
|
128
|
+
enableAgentTeams: false,
|
|
129
|
+
toolRoutingRules: [
|
|
130
|
+
{
|
|
131
|
+
mode: "act",
|
|
132
|
+
providerIdIncludes: ["anthropic"],
|
|
133
|
+
modelIdIncludes: ["claude"],
|
|
134
|
+
enableTools: ["apply_patch"],
|
|
135
|
+
disableTools: ["editor"],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const names = runtime.tools.map((tool) => tool.name);
|
|
142
|
+
expect(names).toContain("apply_patch");
|
|
143
|
+
expect(names).not.toContain("editor");
|
|
144
|
+
});
|
|
145
|
+
|
|
77
146
|
it("omits builtin tools when disabled", () => {
|
|
78
147
|
const runtime = new DefaultRuntimeBuilder().build({
|
|
79
148
|
config: {
|
|
@@ -16,9 +16,12 @@ import {
|
|
|
16
16
|
} from "../agents";
|
|
17
17
|
import {
|
|
18
18
|
createBuiltinTools,
|
|
19
|
+
DEFAULT_MODEL_TOOL_ROUTING_RULES,
|
|
20
|
+
resolveToolRoutingConfig,
|
|
19
21
|
type SkillsExecutor,
|
|
20
22
|
type ToolExecutors,
|
|
21
23
|
ToolPresets,
|
|
24
|
+
type ToolRoutingRule,
|
|
22
25
|
} from "../default-tools";
|
|
23
26
|
import { SqliteTeamStore } from "../storage/sqlite-team-store";
|
|
24
27
|
import type { CoreAgentMode, CoreSessionConfig } from "../types/config";
|
|
@@ -47,15 +50,26 @@ export function createTeamName(): string {
|
|
|
47
50
|
|
|
48
51
|
function createBuiltinToolsList(
|
|
49
52
|
cwd: string,
|
|
53
|
+
providerId: string,
|
|
50
54
|
mode: CoreAgentMode,
|
|
55
|
+
modelId: string,
|
|
56
|
+
toolRoutingRules: ToolRoutingRule[] | undefined,
|
|
51
57
|
skillsExecutor?: SkillsExecutorWithMetadata,
|
|
52
58
|
executorOverrides?: Partial<ToolExecutors>,
|
|
53
59
|
): Tool[] {
|
|
54
60
|
const preset =
|
|
55
61
|
mode === "plan" ? ToolPresets.readonly : ToolPresets.development;
|
|
62
|
+
const toolRoutingConfig = resolveToolRoutingConfig(
|
|
63
|
+
providerId,
|
|
64
|
+
modelId,
|
|
65
|
+
mode,
|
|
66
|
+
toolRoutingRules ?? DEFAULT_MODEL_TOOL_ROUTING_RULES,
|
|
67
|
+
);
|
|
68
|
+
|
|
56
69
|
return createBuiltinTools({
|
|
57
70
|
cwd,
|
|
58
71
|
...preset,
|
|
72
|
+
...toolRoutingConfig,
|
|
59
73
|
enableSkills: !!skillsExecutor,
|
|
60
74
|
executors: {
|
|
61
75
|
...(skillsExecutor
|
|
@@ -338,7 +352,10 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
338
352
|
tools.push(
|
|
339
353
|
...createBuiltinToolsList(
|
|
340
354
|
config.cwd,
|
|
355
|
+
config.providerId,
|
|
341
356
|
normalized.mode,
|
|
357
|
+
config.modelId,
|
|
358
|
+
config.toolRoutingRules,
|
|
342
359
|
skillsExecutor,
|
|
343
360
|
defaultToolExecutors,
|
|
344
361
|
),
|
|
@@ -416,7 +433,10 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
416
433
|
? () =>
|
|
417
434
|
createBuiltinToolsList(
|
|
418
435
|
config.cwd,
|
|
436
|
+
config.providerId,
|
|
419
437
|
normalized.mode,
|
|
438
|
+
config.modelId,
|
|
439
|
+
config.toolRoutingRules,
|
|
420
440
|
skillsExecutor,
|
|
421
441
|
defaultToolExecutors,
|
|
422
442
|
)
|
|
@@ -45,6 +45,13 @@ function legacyBuildRuntimeEnvironment(
|
|
|
45
45
|
return tools;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function normalizeParityToolNames(toolNames: string[]): string[] {
|
|
49
|
+
// Skills are discovered from user/workspace config and can appear in tests
|
|
50
|
+
// depending on the machine state. They are intentionally excluded from
|
|
51
|
+
// strict legacy parity checks.
|
|
52
|
+
return toolNames.filter((toolName) => toolName !== "skills");
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
function makeEmptyWorkspaceCwd(): string {
|
|
49
56
|
return mkdtempSync(join(tmpdir(), "runtime-parity-"));
|
|
50
57
|
}
|
|
@@ -71,8 +78,10 @@ describe("runtime tool parity", () => {
|
|
|
71
78
|
enableAgentTeams: false,
|
|
72
79
|
};
|
|
73
80
|
const createSpawnTool = makeSpawnTool;
|
|
74
|
-
const expected =
|
|
75
|
-
(
|
|
81
|
+
const expected = normalizeParityToolNames(
|
|
82
|
+
legacyBuildRuntimeEnvironment(config, createSpawnTool).map(
|
|
83
|
+
(tool) => tool.name,
|
|
84
|
+
),
|
|
76
85
|
);
|
|
77
86
|
const actual = new DefaultRuntimeBuilder()
|
|
78
87
|
.build({
|
|
@@ -81,7 +90,7 @@ describe("runtime tool parity", () => {
|
|
|
81
90
|
})
|
|
82
91
|
.tools.map((tool) => tool.name);
|
|
83
92
|
|
|
84
|
-
expect(actual).toEqual(expected);
|
|
93
|
+
expect(normalizeParityToolNames(actual)).toEqual(expected);
|
|
85
94
|
});
|
|
86
95
|
|
|
87
96
|
it("matches legacy tool list when only spawn is enabled", () => {
|
|
@@ -96,8 +105,10 @@ describe("runtime tool parity", () => {
|
|
|
96
105
|
enableAgentTeams: false,
|
|
97
106
|
};
|
|
98
107
|
const createSpawnTool = makeSpawnTool;
|
|
99
|
-
const expected =
|
|
100
|
-
(
|
|
108
|
+
const expected = normalizeParityToolNames(
|
|
109
|
+
legacyBuildRuntimeEnvironment(config, createSpawnTool).map(
|
|
110
|
+
(tool) => tool.name,
|
|
111
|
+
),
|
|
101
112
|
);
|
|
102
113
|
const actual = new DefaultRuntimeBuilder()
|
|
103
114
|
.build({
|
|
@@ -106,7 +117,7 @@ describe("runtime tool parity", () => {
|
|
|
106
117
|
})
|
|
107
118
|
.tools.map((tool) => tool.name);
|
|
108
119
|
|
|
109
|
-
expect(actual).toEqual(expected);
|
|
120
|
+
expect(normalizeParityToolNames(actual)).toEqual(expected);
|
|
110
121
|
});
|
|
111
122
|
|
|
112
123
|
it("matches legacy tool list when tools+spawn are disabled", () => {
|
|
@@ -120,13 +131,13 @@ describe("runtime tool parity", () => {
|
|
|
120
131
|
enableSpawnAgent: false,
|
|
121
132
|
enableAgentTeams: false,
|
|
122
133
|
};
|
|
123
|
-
const expected =
|
|
124
|
-
(tool) => tool.name,
|
|
134
|
+
const expected = normalizeParityToolNames(
|
|
135
|
+
legacyBuildRuntimeEnvironment(config).map((tool) => tool.name),
|
|
125
136
|
);
|
|
126
137
|
const actual = new DefaultRuntimeBuilder()
|
|
127
138
|
.build({ config })
|
|
128
139
|
.tools.map((tool) => tool.name);
|
|
129
140
|
|
|
130
|
-
expect(actual).toEqual(expected);
|
|
141
|
+
expect(normalizeParityToolNames(actual)).toEqual(expected);
|
|
131
142
|
});
|
|
132
143
|
});
|
|
@@ -155,14 +155,24 @@ class LocalFileSessionService {
|
|
|
155
155
|
persistSessionMessages(
|
|
156
156
|
sessionId: string,
|
|
157
157
|
messages: LlmsProviders.Message[],
|
|
158
|
+
systemPrompt?: string,
|
|
158
159
|
): void {
|
|
159
160
|
const row = this.rows.get(sessionId);
|
|
160
161
|
if (!row?.messages_path) {
|
|
161
162
|
throw new Error(`session not found: ${sessionId}`);
|
|
162
163
|
}
|
|
164
|
+
const payload: {
|
|
165
|
+
version: number;
|
|
166
|
+
updated_at: string;
|
|
167
|
+
systemPrompt?: string;
|
|
168
|
+
messages: LlmsProviders.Message[];
|
|
169
|
+
} = { version: 1, updated_at: nowIso(), messages };
|
|
170
|
+
if (systemPrompt !== undefined && systemPrompt !== "") {
|
|
171
|
+
payload.systemPrompt = systemPrompt;
|
|
172
|
+
}
|
|
163
173
|
writeFileSync(
|
|
164
174
|
row.messages_path,
|
|
165
|
-
`${JSON.stringify(
|
|
175
|
+
`${JSON.stringify(payload, null, 2)}\n`,
|
|
166
176
|
"utf8",
|
|
167
177
|
);
|
|
168
178
|
}
|
|
@@ -237,6 +237,75 @@ describe("DefaultSessionManager", () => {
|
|
|
237
237
|
});
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
+
it("persists rendered messages when a turn fails", async () => {
|
|
241
|
+
const sessionId = "sess-failed-turn";
|
|
242
|
+
const manifest = createManifest(sessionId);
|
|
243
|
+
const persistSessionMessages = vi.fn();
|
|
244
|
+
const sessionService = {
|
|
245
|
+
ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
|
|
246
|
+
createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
|
|
247
|
+
manifestPath: "/tmp/manifest-failed-turn.json",
|
|
248
|
+
transcriptPath: "/tmp/transcript-failed-turn.log",
|
|
249
|
+
hookPath: "/tmp/hook-failed-turn.log",
|
|
250
|
+
messagesPath: "/tmp/messages-failed-turn.json",
|
|
251
|
+
manifest,
|
|
252
|
+
}),
|
|
253
|
+
persistSessionMessages,
|
|
254
|
+
updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
|
|
255
|
+
writeSessionManifest: vi.fn(),
|
|
256
|
+
listSessions: vi.fn().mockResolvedValue([]),
|
|
257
|
+
deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
|
|
258
|
+
};
|
|
259
|
+
const runtimeBuilder = {
|
|
260
|
+
build: vi.fn().mockReturnValue({
|
|
261
|
+
tools: [],
|
|
262
|
+
shutdown: vi.fn(),
|
|
263
|
+
}),
|
|
264
|
+
};
|
|
265
|
+
const renderedMessages = [
|
|
266
|
+
{ role: "user", content: [{ type: "text", text: "hello" }] },
|
|
267
|
+
{ role: "assistant", content: [{ type: "text", text: "partial" }] },
|
|
268
|
+
];
|
|
269
|
+
const manager = new DefaultSessionManager({
|
|
270
|
+
distinctId,
|
|
271
|
+
sessionService: sessionService as never,
|
|
272
|
+
runtimeBuilder,
|
|
273
|
+
createAgent: () =>
|
|
274
|
+
({
|
|
275
|
+
run: vi.fn().mockRejectedValue(new Error("boom")),
|
|
276
|
+
continue: vi.fn(),
|
|
277
|
+
abort: vi.fn(),
|
|
278
|
+
restore: vi.fn(),
|
|
279
|
+
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
280
|
+
getMessages: vi
|
|
281
|
+
.fn()
|
|
282
|
+
.mockReturnValueOnce([])
|
|
283
|
+
.mockReturnValue(renderedMessages),
|
|
284
|
+
messages: [],
|
|
285
|
+
}) as never,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
await expect(
|
|
289
|
+
manager.start({
|
|
290
|
+
config: createConfig({ sessionId }),
|
|
291
|
+
prompt: "hello",
|
|
292
|
+
interactive: false,
|
|
293
|
+
}),
|
|
294
|
+
).rejects.toThrow("boom");
|
|
295
|
+
|
|
296
|
+
expect(persistSessionMessages).toHaveBeenCalledTimes(1);
|
|
297
|
+
expect(persistSessionMessages).toHaveBeenCalledWith(
|
|
298
|
+
sessionId,
|
|
299
|
+
renderedMessages,
|
|
300
|
+
"You are a test agent",
|
|
301
|
+
);
|
|
302
|
+
expect(sessionService.updateSessionStatus).toHaveBeenCalledWith(
|
|
303
|
+
sessionId,
|
|
304
|
+
"failed",
|
|
305
|
+
1,
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
240
309
|
it("uses run for first send then continue for subsequent sends", async () => {
|
|
241
310
|
const sessionId = "sess-2";
|
|
242
311
|
const manifest = createManifest(sessionId);
|
|
@@ -294,6 +363,123 @@ describe("DefaultSessionManager", () => {
|
|
|
294
363
|
expect(sessionService.persistSessionMessages).toHaveBeenCalledTimes(2);
|
|
295
364
|
});
|
|
296
365
|
|
|
366
|
+
it("tracks accumulated usage per session across turns", async () => {
|
|
367
|
+
const sessionId = "sess-usage";
|
|
368
|
+
const manifest = createManifest(sessionId);
|
|
369
|
+
const sessionService = {
|
|
370
|
+
ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
|
|
371
|
+
createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
|
|
372
|
+
manifestPath: "/tmp/manifest-usage.json",
|
|
373
|
+
transcriptPath: "/tmp/transcript-usage.log",
|
|
374
|
+
hookPath: "/tmp/hook-usage.log",
|
|
375
|
+
messagesPath: "/tmp/messages-usage.json",
|
|
376
|
+
manifest,
|
|
377
|
+
}),
|
|
378
|
+
persistSessionMessages: vi.fn(),
|
|
379
|
+
updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
|
|
380
|
+
writeSessionManifest: vi.fn(),
|
|
381
|
+
listSessions: vi.fn().mockResolvedValue([]),
|
|
382
|
+
deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
|
|
383
|
+
};
|
|
384
|
+
const runtimeBuilder = {
|
|
385
|
+
build: vi.fn().mockReturnValue({
|
|
386
|
+
tools: [],
|
|
387
|
+
shutdown: vi.fn(),
|
|
388
|
+
}),
|
|
389
|
+
};
|
|
390
|
+
const run = vi.fn().mockResolvedValue(
|
|
391
|
+
createResult({
|
|
392
|
+
text: "first",
|
|
393
|
+
usage: {
|
|
394
|
+
inputTokens: 10,
|
|
395
|
+
outputTokens: 3,
|
|
396
|
+
cacheReadTokens: 1,
|
|
397
|
+
cacheWriteTokens: 2,
|
|
398
|
+
totalCost: 0.11,
|
|
399
|
+
},
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
const continueFn = vi.fn().mockResolvedValue(
|
|
403
|
+
createResult({
|
|
404
|
+
text: "second",
|
|
405
|
+
usage: {
|
|
406
|
+
inputTokens: 8,
|
|
407
|
+
outputTokens: 4,
|
|
408
|
+
cacheReadTokens: 2,
|
|
409
|
+
cacheWriteTokens: 0,
|
|
410
|
+
totalCost: 0.09,
|
|
411
|
+
},
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
const manager = new DefaultSessionManager({
|
|
415
|
+
distinctId,
|
|
416
|
+
sessionService: sessionService as never,
|
|
417
|
+
runtimeBuilder,
|
|
418
|
+
createAgent: () =>
|
|
419
|
+
({
|
|
420
|
+
run,
|
|
421
|
+
continue: continueFn,
|
|
422
|
+
abort: vi.fn(),
|
|
423
|
+
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
424
|
+
getMessages: vi.fn().mockReturnValue([]),
|
|
425
|
+
messages: [],
|
|
426
|
+
}) as never,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await manager.start({
|
|
430
|
+
config: createConfig({ sessionId }),
|
|
431
|
+
interactive: true,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
await manager.send({ sessionId, prompt: "first" });
|
|
435
|
+
expect(await manager.getAccumulatedUsage(sessionId)).toEqual({
|
|
436
|
+
inputTokens: 10,
|
|
437
|
+
outputTokens: 3,
|
|
438
|
+
cacheReadTokens: 1,
|
|
439
|
+
cacheWriteTokens: 2,
|
|
440
|
+
totalCost: 0.11,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
await manager.send({ sessionId, prompt: "second" });
|
|
444
|
+
expect(await manager.getAccumulatedUsage(sessionId)).toEqual({
|
|
445
|
+
inputTokens: 18,
|
|
446
|
+
outputTokens: 7,
|
|
447
|
+
cacheReadTokens: 3,
|
|
448
|
+
cacheWriteTokens: 2,
|
|
449
|
+
totalCost: 0.2,
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("returns undefined accumulated usage for unknown sessions", async () => {
|
|
454
|
+
const manager = new DefaultSessionManager({
|
|
455
|
+
distinctId,
|
|
456
|
+
sessionService: {
|
|
457
|
+
ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
|
|
458
|
+
listSessions: vi.fn().mockResolvedValue([]),
|
|
459
|
+
deleteSession: vi.fn().mockResolvedValue({ deleted: false }),
|
|
460
|
+
} as never,
|
|
461
|
+
runtimeBuilder: {
|
|
462
|
+
build: vi.fn().mockReturnValue({
|
|
463
|
+
tools: [],
|
|
464
|
+
shutdown: vi.fn(),
|
|
465
|
+
}),
|
|
466
|
+
},
|
|
467
|
+
createAgent: () =>
|
|
468
|
+
({
|
|
469
|
+
run: vi.fn(),
|
|
470
|
+
continue: vi.fn(),
|
|
471
|
+
abort: vi.fn(),
|
|
472
|
+
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
473
|
+
getMessages: vi.fn().mockReturnValue([]),
|
|
474
|
+
messages: [],
|
|
475
|
+
}) as never,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
expect(
|
|
479
|
+
await manager.getAccumulatedUsage("missing-session"),
|
|
480
|
+
).toBeUndefined();
|
|
481
|
+
});
|
|
482
|
+
|
|
297
483
|
it("marks a failed single-run session as failed when run throws", async () => {
|
|
298
484
|
const sessionId = "sess-fail";
|
|
299
485
|
const manifest = createManifest(sessionId);
|
|
@@ -813,4 +999,88 @@ describe("DefaultSessionManager", () => {
|
|
|
813
999
|
0,
|
|
814
1000
|
);
|
|
815
1001
|
});
|
|
1002
|
+
|
|
1003
|
+
it("persists failed teammate task messages for team-task sub-sessions", async () => {
|
|
1004
|
+
const sessionId = "sess-team-task-failure-messages";
|
|
1005
|
+
const manifest = createManifest(sessionId);
|
|
1006
|
+
const sessionService = {
|
|
1007
|
+
ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
|
|
1008
|
+
createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
|
|
1009
|
+
manifestPath: "/tmp/manifest-team-task-failure-messages.json",
|
|
1010
|
+
transcriptPath: "/tmp/transcript-team-task-failure-messages.log",
|
|
1011
|
+
hookPath: "/tmp/hook-team-task-failure-messages.log",
|
|
1012
|
+
messagesPath: "/tmp/messages-team-task-failure-messages.json",
|
|
1013
|
+
manifest,
|
|
1014
|
+
}),
|
|
1015
|
+
persistSessionMessages: vi.fn(),
|
|
1016
|
+
updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
|
|
1017
|
+
writeSessionManifest: vi.fn(),
|
|
1018
|
+
listSessions: vi.fn().mockResolvedValue([]),
|
|
1019
|
+
deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
|
|
1020
|
+
onTeamTaskStart: vi.fn().mockResolvedValue(undefined),
|
|
1021
|
+
onTeamTaskEnd: vi.fn().mockResolvedValue(undefined),
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
let onTeamEvent: ((event: unknown) => void) | undefined;
|
|
1025
|
+
const runtimeBuilder = {
|
|
1026
|
+
build: vi
|
|
1027
|
+
.fn()
|
|
1028
|
+
.mockImplementation(
|
|
1029
|
+
(input: { onTeamEvent?: (event: unknown) => void }) => {
|
|
1030
|
+
onTeamEvent = input.onTeamEvent;
|
|
1031
|
+
return {
|
|
1032
|
+
tools: [],
|
|
1033
|
+
shutdown: vi.fn(),
|
|
1034
|
+
};
|
|
1035
|
+
},
|
|
1036
|
+
),
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
const failedMessages = [
|
|
1040
|
+
{ role: "user", content: [{ type: "text", text: "delegated prompt" }] },
|
|
1041
|
+
{ role: "assistant", content: [{ type: "text", text: "partial work" }] },
|
|
1042
|
+
];
|
|
1043
|
+
const manager = new DefaultSessionManager({
|
|
1044
|
+
distinctId,
|
|
1045
|
+
sessionService: sessionService as never,
|
|
1046
|
+
runtimeBuilder,
|
|
1047
|
+
createAgent: () =>
|
|
1048
|
+
({
|
|
1049
|
+
run: vi.fn().mockImplementation(async () => {
|
|
1050
|
+
onTeamEvent?.({
|
|
1051
|
+
type: "task_start",
|
|
1052
|
+
agentId: "providers-investigator",
|
|
1053
|
+
message: "Investigate provider boundaries",
|
|
1054
|
+
});
|
|
1055
|
+
onTeamEvent?.({
|
|
1056
|
+
type: "task_end",
|
|
1057
|
+
agentId: "providers-investigator",
|
|
1058
|
+
error: new Error("401 Unauthorized"),
|
|
1059
|
+
messages: failedMessages,
|
|
1060
|
+
});
|
|
1061
|
+
return createResult({ text: "lead handled failure" });
|
|
1062
|
+
}),
|
|
1063
|
+
continue: vi.fn(),
|
|
1064
|
+
abort: vi.fn(),
|
|
1065
|
+
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
1066
|
+
getMessages: vi.fn().mockReturnValue([]),
|
|
1067
|
+
messages: [],
|
|
1068
|
+
}) as never,
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
await manager.start({
|
|
1072
|
+
config: createConfig({ sessionId }),
|
|
1073
|
+
prompt: "run teammate work",
|
|
1074
|
+
interactive: false,
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
expect(sessionService.onTeamTaskStart).toHaveBeenCalledTimes(1);
|
|
1078
|
+
expect(sessionService.onTeamTaskEnd).toHaveBeenCalledWith(
|
|
1079
|
+
sessionId,
|
|
1080
|
+
"providers-investigator",
|
|
1081
|
+
"failed",
|
|
1082
|
+
"[error] 401 Unauthorized",
|
|
1083
|
+
failedMessages,
|
|
1084
|
+
);
|
|
1085
|
+
});
|
|
816
1086
|
});
|