@clinebot/core 0.0.0 → 0.0.2

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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/default-tools/index.d.ts +1 -0
  3. package/dist/default-tools/model-tool-routing.d.ts +33 -0
  4. package/dist/default-tools/schemas.d.ts +13 -7
  5. package/dist/index.browser.d.ts +1 -0
  6. package/dist/index.browser.js +220 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.node.d.ts +1 -0
  9. package/dist/index.node.js +220 -0
  10. package/dist/server/index.d.ts +5 -3
  11. package/dist/server/index.js +244 -192
  12. package/dist/session/default-session-manager.d.ts +3 -1
  13. package/dist/session/session-host.d.ts +2 -2
  14. package/dist/session/session-manager.d.ts +8 -0
  15. package/dist/session/unified-session-persistence-service.d.ts +1 -1
  16. package/dist/session/utils/helpers.d.ts +11 -0
  17. package/dist/session/utils/types.d.ts +42 -0
  18. package/dist/session/utils/usage.d.ts +9 -0
  19. package/dist/storage/provider-settings-manager.d.ts +2 -0
  20. package/dist/types/config.d.ts +8 -1
  21. package/dist/types.d.ts +1 -1
  22. package/package.json +19 -20
  23. package/src/default-tools/definitions.test.ts +130 -1
  24. package/src/default-tools/definitions.ts +6 -2
  25. package/src/default-tools/executors/editor.ts +10 -9
  26. package/src/default-tools/executors/file-read.test.ts +1 -1
  27. package/src/default-tools/executors/file-read.ts +11 -6
  28. package/src/default-tools/index.ts +5 -0
  29. package/src/default-tools/model-tool-routing.test.ts +86 -0
  30. package/src/default-tools/model-tool-routing.ts +132 -0
  31. package/src/default-tools/schemas.ts +49 -52
  32. package/src/index.browser.ts +1 -0
  33. package/src/index.node.ts +1 -0
  34. package/src/index.ts +41 -2
  35. package/src/input/file-indexer.ts +28 -2
  36. package/src/runtime/runtime-builder.test.ts +69 -0
  37. package/src/runtime/runtime-builder.ts +20 -0
  38. package/src/runtime/runtime-parity.test.ts +20 -9
  39. package/src/server/index.ts +40 -1
  40. package/src/session/default-session-manager.e2e.test.ts +11 -1
  41. package/src/session/default-session-manager.test.ts +270 -0
  42. package/src/session/default-session-manager.ts +109 -191
  43. package/src/session/index.ts +7 -2
  44. package/src/session/session-host.ts +30 -18
  45. package/src/session/session-manager.ts +11 -0
  46. package/src/session/unified-session-persistence-service.ts +11 -5
  47. package/src/session/utils/helpers.ts +148 -0
  48. package/src/session/utils/types.ts +46 -0
  49. package/src/session/utils/usage.ts +32 -0
  50. package/src/storage/provider-settings-legacy-migration.test.ts +3 -3
  51. package/src/storage/provider-settings-manager.test.ts +34 -0
  52. package/src/storage/provider-settings-manager.ts +22 -1
  53. package/src/types/config.ts +13 -0
  54. package/src/types.ts +1 -0
  55. package/dist/index.js +0 -220
@@ -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({ version: 1, updated_at: nowIso(), messages }, null, 2)}\n`,
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
  });