@clinebot/core 0.0.10 → 0.0.12

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/dist/agents/agent-config-loader.d.ts +1 -1
  2. package/dist/agents/agent-config-parser.d.ts +5 -2
  3. package/dist/agents/index.d.ts +1 -1
  4. package/dist/agents/plugin-config-loader.d.ts +4 -0
  5. package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
  6. package/dist/agents/plugin-sandbox.d.ts +4 -0
  7. package/dist/index.node.d.ts +1 -1
  8. package/dist/index.node.js +658 -407
  9. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
  10. package/dist/session/default-session-manager.d.ts +5 -0
  11. package/dist/session/session-config-builder.d.ts +4 -1
  12. package/dist/session/session-manager.d.ts +1 -0
  13. package/dist/session/unified-session-persistence-service.d.ts +6 -0
  14. package/dist/session/utils/helpers.d.ts +1 -1
  15. package/dist/session/utils/types.d.ts +10 -0
  16. package/dist/tools/definitions.d.ts +2 -2
  17. package/dist/tools/presets.d.ts +3 -3
  18. package/dist/tools/schemas.d.ts +14 -14
  19. package/dist/types/config.d.ts +5 -0
  20. package/dist/types/events.d.ts +22 -0
  21. package/package.json +5 -4
  22. package/src/agents/agent-config-loader.test.ts +2 -0
  23. package/src/agents/agent-config-loader.ts +1 -0
  24. package/src/agents/agent-config-parser.ts +12 -5
  25. package/src/agents/index.ts +1 -0
  26. package/src/agents/plugin-config-loader.test.ts +49 -0
  27. package/src/agents/plugin-config-loader.ts +10 -73
  28. package/src/agents/plugin-loader.test.ts +128 -2
  29. package/src/agents/plugin-loader.ts +70 -5
  30. package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
  31. package/src/agents/plugin-sandbox.test.ts +198 -1
  32. package/src/agents/plugin-sandbox.ts +223 -353
  33. package/src/index.node.ts +4 -0
  34. package/src/runtime/hook-file-hooks.test.ts +1 -1
  35. package/src/runtime/hook-file-hooks.ts +16 -6
  36. package/src/runtime/runtime-builder.test.ts +67 -0
  37. package/src/runtime/runtime-builder.ts +70 -16
  38. package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
  39. package/src/session/default-session-manager.e2e.test.ts +20 -1
  40. package/src/session/default-session-manager.test.ts +584 -1
  41. package/src/session/default-session-manager.ts +205 -1
  42. package/src/session/session-config-builder.ts +2 -0
  43. package/src/session/session-manager.ts +1 -0
  44. package/src/session/session-team-coordination.ts +30 -0
  45. package/src/session/unified-session-persistence-service.ts +45 -0
  46. package/src/session/utils/helpers.ts +13 -3
  47. package/src/session/utils/types.ts +11 -0
  48. package/src/storage/sqlite-team-store.ts +16 -5
  49. package/src/tools/definitions.test.ts +87 -8
  50. package/src/tools/definitions.ts +89 -70
  51. package/src/tools/presets.test.ts +2 -3
  52. package/src/tools/presets.ts +3 -3
  53. package/src/tools/schemas.ts +23 -22
  54. package/src/types/config.ts +5 -0
  55. package/src/types/events.ts +23 -0
@@ -2,7 +2,8 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import type { AgentResult } from "@clinebot/agents";
5
- import { describe, expect, it, vi } from "vitest";
5
+ import { setClineDir, setHomeDir } from "@clinebot/shared/storage";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
7
  import { TelemetryService } from "../telemetry/TelemetryService";
7
8
  import { SessionSource } from "../types/common";
8
9
  import type { CoreSessionConfig } from "../types/config";
@@ -55,6 +56,61 @@ function createManifest(sessionId: string): SessionManifest {
55
56
  };
56
57
  }
57
58
 
59
+ type PluginEventTestHarness = {
60
+ handlePluginEvent: (
61
+ rootSessionId: string,
62
+ event: { name: string; payload?: unknown },
63
+ ) => Promise<void>;
64
+ getPendingPrompts: (
65
+ sessionId: string,
66
+ ) => Array<{ prompt: string; delivery: "queue" | "steer" }>;
67
+ };
68
+
69
+ function createPluginEventHarness(
70
+ manager: DefaultSessionManager,
71
+ ): PluginEventTestHarness {
72
+ const target = manager as object;
73
+ return {
74
+ handlePluginEvent: async (rootSessionId, event) => {
75
+ const handler = Reflect.get(target, "handlePluginEvent");
76
+ if (typeof handler !== "function") {
77
+ throw new Error("handlePluginEvent test hook unavailable");
78
+ }
79
+ await Reflect.apply(
80
+ handler as (
81
+ rootSessionId: string,
82
+ event: { name: string; payload?: unknown },
83
+ ) => Promise<void>,
84
+ target,
85
+ [rootSessionId, event],
86
+ );
87
+ },
88
+ getPendingPrompts: (sessionId) => {
89
+ const getter = Reflect.get(target, "getSessionOrThrow");
90
+ if (typeof getter !== "function") {
91
+ throw new Error("getSessionOrThrow test hook unavailable");
92
+ }
93
+ const session = Reflect.apply(
94
+ getter as (sessionId: string) => {
95
+ pendingPrompts: Array<{
96
+ id: string;
97
+ prompt: string;
98
+ delivery: "queue" | "steer";
99
+ userFiles?: unknown;
100
+ userImages?: unknown;
101
+ }>;
102
+ },
103
+ target,
104
+ [sessionId],
105
+ );
106
+ return session.pendingPrompts.map(({ prompt, delivery }) => ({
107
+ prompt,
108
+ delivery,
109
+ }));
110
+ },
111
+ };
112
+ }
113
+
58
114
  function createConfig(
59
115
  overrides: Partial<CoreSessionConfig> = {},
60
116
  ): CoreSessionConfig {
@@ -71,6 +127,28 @@ function createConfig(
71
127
  }
72
128
 
73
129
  describe("DefaultSessionManager", () => {
130
+ const envSnapshot = {
131
+ HOME: process.env.HOME,
132
+ CLINE_DIR: process.env.CLINE_DIR,
133
+ };
134
+ let isolatedHomeDir = "";
135
+
136
+ beforeEach(() => {
137
+ isolatedHomeDir = mkdtempSync(join(tmpdir(), "core-session-home-"));
138
+ process.env.HOME = isolatedHomeDir;
139
+ process.env.CLINE_DIR = join(isolatedHomeDir, ".cline");
140
+ setHomeDir(isolatedHomeDir);
141
+ setClineDir(process.env.CLINE_DIR);
142
+ });
143
+
144
+ afterEach(() => {
145
+ process.env.HOME = envSnapshot.HOME;
146
+ process.env.CLINE_DIR = envSnapshot.CLINE_DIR;
147
+ setHomeDir(envSnapshot.HOME ?? "~");
148
+ setClineDir(envSnapshot.CLINE_DIR ?? join("~", ".cline"));
149
+ rmSync(isolatedHomeDir, { recursive: true, force: true });
150
+ });
151
+
74
152
  it("emits session lifecycle telemetry when configured", async () => {
75
153
  const sessionId = "sess-telemetry";
76
154
  const manifest = createManifest(sessionId);
@@ -309,6 +387,294 @@ describe("DefaultSessionManager", () => {
309
387
  });
310
388
  });
311
389
 
390
+ it("queues sandbox steer messages back into the active session", async () => {
391
+ const sessionId = "sess-steer";
392
+ const manifest = createManifest(sessionId);
393
+ const sessionService = {
394
+ ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
395
+ createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
396
+ manifestPath: "/tmp/manifest.json",
397
+ transcriptPath: "/tmp/transcript.log",
398
+ hookPath: "/tmp/hook.log",
399
+ messagesPath: "/tmp/messages.json",
400
+ manifest,
401
+ }),
402
+ persistSessionMessages: vi.fn(),
403
+ updateSessionStatus: vi.fn().mockResolvedValue({
404
+ updated: true,
405
+ endedAt: "2026-01-01T00:00:05.000Z",
406
+ }),
407
+ writeSessionManifest: vi.fn(),
408
+ listSessions: vi.fn().mockResolvedValue([]),
409
+ deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
410
+ };
411
+ const runtimeBuilder = {
412
+ build: vi.fn().mockReturnValue({
413
+ tools: [],
414
+ shutdown: vi.fn(),
415
+ }),
416
+ };
417
+ const run = vi.fn().mockResolvedValue(
418
+ createResult({
419
+ messages: [
420
+ { role: "user", content: [{ type: "text", text: "hello" }] },
421
+ ],
422
+ }),
423
+ );
424
+ const continueFn = vi.fn().mockResolvedValue(
425
+ createResult({
426
+ text: "steered",
427
+ messages: [
428
+ { role: "user", content: [{ type: "text", text: "hello" }] },
429
+ {
430
+ role: "assistant",
431
+ content: [{ type: "text", text: "steered" }],
432
+ },
433
+ ],
434
+ }),
435
+ );
436
+ const agent = {
437
+ run,
438
+ continue: continueFn,
439
+ abort: vi.fn(),
440
+ shutdown: vi.fn().mockResolvedValue(undefined),
441
+ getMessages: vi
442
+ .fn()
443
+ .mockReturnValue([
444
+ { role: "user", content: [{ type: "text", text: "hello" }] },
445
+ ]),
446
+ canStartRun: vi.fn().mockReturnValue(true),
447
+ };
448
+
449
+ const manager = new DefaultSessionManager({
450
+ distinctId,
451
+ sessionService: sessionService as never,
452
+ runtimeBuilder,
453
+ createAgent: () => agent as never,
454
+ });
455
+
456
+ await manager.start({
457
+ config: createConfig({ sessionId }),
458
+ prompt: "hello",
459
+ interactive: true,
460
+ });
461
+
462
+ const harness = createPluginEventHarness(manager);
463
+ await harness.handlePluginEvent(sessionId, {
464
+ name: "steer_message",
465
+ payload: { prompt: "async result" },
466
+ });
467
+ await vi.waitFor(() => {
468
+ expect(continueFn).toHaveBeenCalledTimes(2);
469
+ });
470
+ expect(continueFn).toHaveBeenLastCalledWith(
471
+ '<user_input mode="act">async result</user_input>',
472
+ undefined,
473
+ undefined,
474
+ );
475
+ });
476
+
477
+ it("promotes queued prompts to the front when they become steer", async () => {
478
+ const sessionId = "sess-steer-priority";
479
+ const manifest = createManifest(sessionId);
480
+ const sessionService = {
481
+ ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
482
+ createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
483
+ manifestPath: "/tmp/manifest.json",
484
+ transcriptPath: "/tmp/transcript.log",
485
+ hookPath: "/tmp/hook.log",
486
+ messagesPath: "/tmp/messages.json",
487
+ manifest,
488
+ }),
489
+ persistSessionMessages: vi.fn(),
490
+ updateSessionStatus: vi.fn().mockResolvedValue({
491
+ updated: true,
492
+ endedAt: "2026-01-01T00:00:05.000Z",
493
+ }),
494
+ writeSessionManifest: vi.fn(),
495
+ listSessions: vi.fn().mockResolvedValue([]),
496
+ deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
497
+ };
498
+ const runtimeBuilder = {
499
+ build: vi.fn().mockReturnValue({
500
+ tools: [],
501
+ shutdown: vi.fn(),
502
+ }),
503
+ };
504
+ const agent = {
505
+ run: vi.fn().mockResolvedValue(createResult()),
506
+ continue: vi.fn().mockResolvedValue(createResult()),
507
+ abort: vi.fn(),
508
+ shutdown: vi.fn().mockResolvedValue(undefined),
509
+ getMessages: vi.fn().mockReturnValue([]),
510
+ canStartRun: vi.fn().mockReturnValue(false),
511
+ };
512
+
513
+ const manager = new DefaultSessionManager({
514
+ distinctId,
515
+ sessionService: sessionService as never,
516
+ runtimeBuilder,
517
+ createAgent: () => agent as never,
518
+ });
519
+
520
+ await manager.start({
521
+ config: createConfig({ sessionId }),
522
+ prompt: "hello",
523
+ interactive: true,
524
+ });
525
+
526
+ const harness = createPluginEventHarness(manager);
527
+
528
+ await harness.handlePluginEvent(sessionId, {
529
+ name: "queue_message",
530
+ payload: { prompt: "queued first" },
531
+ });
532
+ await harness.handlePluginEvent(sessionId, {
533
+ name: "queue_message",
534
+ payload: { prompt: "queued second" },
535
+ });
536
+ await harness.handlePluginEvent(sessionId, {
537
+ name: "steer_message",
538
+ payload: { prompt: "queued first" },
539
+ });
540
+
541
+ expect(harness.getPendingPrompts(sessionId)).toEqual([
542
+ { prompt: "queued first", delivery: "steer" },
543
+ { prompt: "queued second", delivery: "queue" },
544
+ ]);
545
+ });
546
+
547
+ it("preserves per-turn metadata on prior assistant messages across turns", async () => {
548
+ const sessionId = "sess-meta-multi";
549
+ const manifest = createManifest(sessionId);
550
+ const persistSessionMessages = vi.fn();
551
+ const runtimeBuilder = {
552
+ build: vi.fn().mockReturnValue({
553
+ tools: [],
554
+ shutdown: vi.fn(),
555
+ }),
556
+ };
557
+ const firstTurnMessages = [
558
+ {
559
+ role: "user" as const,
560
+ content: [{ type: "text" as const, text: "hello" }],
561
+ },
562
+ {
563
+ role: "assistant" as const,
564
+ content: [{ type: "text" as const, text: "world" }],
565
+ },
566
+ ];
567
+ const secondTurnMessages = [
568
+ ...firstTurnMessages,
569
+ {
570
+ role: "user" as const,
571
+ content: [{ type: "text" as const, text: "again" }],
572
+ },
573
+ {
574
+ role: "assistant" as const,
575
+ content: [{ type: "text" as const, text: "still here" }],
576
+ },
577
+ ];
578
+ const run = vi.fn().mockResolvedValue(
579
+ createResult({
580
+ usage: {
581
+ inputTokens: 33,
582
+ outputTokens: 12,
583
+ cacheReadTokens: 4,
584
+ cacheWriteTokens: 1,
585
+ totalCost: 0.42,
586
+ },
587
+ model: {
588
+ id: "claude-sonnet-4-6",
589
+ provider: "anthropic",
590
+ },
591
+ endedAt: new Date("2026-01-01T00:00:02.000Z"),
592
+ messages: firstTurnMessages,
593
+ }),
594
+ );
595
+ const continueFn = vi.fn().mockResolvedValue(
596
+ createResult({
597
+ usage: {
598
+ inputTokens: 10,
599
+ outputTokens: 5,
600
+ cacheReadTokens: 2,
601
+ cacheWriteTokens: 0,
602
+ totalCost: 0.12,
603
+ },
604
+ model: {
605
+ id: "claude-sonnet-4-6",
606
+ provider: "anthropic",
607
+ },
608
+ endedAt: new Date("2026-01-01T00:00:03.000Z"),
609
+ messages: secondTurnMessages,
610
+ }),
611
+ );
612
+ const agent = {
613
+ run,
614
+ continue: continueFn,
615
+ abort: vi.fn(),
616
+ shutdown: vi.fn().mockResolvedValue(undefined),
617
+ restore: vi.fn(),
618
+ getMessages: vi.fn().mockReturnValue([]),
619
+ messages: [],
620
+ };
621
+ const sessionService = {
622
+ ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
623
+ createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
624
+ manifestPath: "/tmp/manifest-meta-multi.json",
625
+ transcriptPath: "/tmp/transcript-meta-multi.log",
626
+ hookPath: "/tmp/hook-meta-multi.log",
627
+ messagesPath: "/tmp/messages-meta-multi.json",
628
+ manifest,
629
+ }),
630
+ persistSessionMessages,
631
+ updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
632
+ writeSessionManifest: vi.fn(),
633
+ listSessions: vi.fn().mockResolvedValue([]),
634
+ deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
635
+ };
636
+ const manager = new DefaultSessionManager({
637
+ distinctId,
638
+ sessionService: sessionService as never,
639
+ runtimeBuilder,
640
+ createAgent: () => agent as never,
641
+ });
642
+
643
+ await manager.start({
644
+ config: createConfig({
645
+ sessionId,
646
+ providerId: "anthropic",
647
+ modelId: "claude-sonnet-4-6",
648
+ }),
649
+ interactive: true,
650
+ });
651
+
652
+ await manager.send({ sessionId, prompt: "hello" });
653
+ await manager.send({ sessionId, prompt: "again" });
654
+
655
+ const persisted = persistSessionMessages.mock.calls[1]?.[1];
656
+ expect(persisted?.[1]).toMatchObject({
657
+ role: "assistant",
658
+ metrics: {
659
+ inputTokens: 33,
660
+ outputTokens: 12,
661
+ cacheReadTokens: 4,
662
+ cacheWriteTokens: 1,
663
+ cost: 0.42,
664
+ },
665
+ });
666
+ expect(persisted?.[3]).toMatchObject({
667
+ role: "assistant",
668
+ metrics: {
669
+ inputTokens: 10,
670
+ outputTokens: 5,
671
+ cacheReadTokens: 2,
672
+ cacheWriteTokens: 0,
673
+ cost: 0.12,
674
+ },
675
+ });
676
+ });
677
+
312
678
  it("persists rendered messages when a turn fails", async () => {
313
679
  const sessionId = "sess-failed-turn";
314
680
  const manifest = createManifest(sessionId);
@@ -522,6 +888,112 @@ describe("DefaultSessionManager", () => {
522
888
  });
523
889
  });
524
890
 
891
+ it("queues sends with explicit queue or steer delivery and emits snapshots", async () => {
892
+ const sessionId = "sess-delivery-queue";
893
+ const manifest = createManifest(sessionId);
894
+ const sessionService = {
895
+ ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
896
+ createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
897
+ manifestPath: "/tmp/manifest-queue.json",
898
+ transcriptPath: "/tmp/transcript-queue.log",
899
+ hookPath: "/tmp/hook-queue.log",
900
+ messagesPath: "/tmp/messages-queue.json",
901
+ manifest,
902
+ }),
903
+ persistSessionMessages: vi.fn(),
904
+ updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
905
+ writeSessionManifest: vi.fn(),
906
+ listSessions: vi.fn().mockResolvedValue([]),
907
+ deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
908
+ };
909
+ const runtimeBuilder = {
910
+ build: vi.fn().mockReturnValue({
911
+ tools: [],
912
+ shutdown: vi.fn(),
913
+ }),
914
+ };
915
+ let canStartRun = false;
916
+ const run = vi.fn().mockResolvedValue(createResult({ text: "first" }));
917
+ const continueFn = vi
918
+ .fn()
919
+ .mockResolvedValue(createResult({ text: "next" }));
920
+ const manager = new DefaultSessionManager({
921
+ distinctId,
922
+ sessionService: sessionService as never,
923
+ runtimeBuilder,
924
+ createAgent: () =>
925
+ ({
926
+ run,
927
+ continue: continueFn,
928
+ canStartRun: vi.fn(() => canStartRun),
929
+ abort: vi.fn(),
930
+ shutdown: vi.fn().mockResolvedValue(undefined),
931
+ getMessages: vi.fn().mockReturnValue([]),
932
+ messages: [],
933
+ }) as never,
934
+ });
935
+ const events: Array<unknown> = [];
936
+ manager.subscribe((event) => {
937
+ events.push(event);
938
+ });
939
+
940
+ await manager.start({
941
+ config: createConfig({ sessionId }),
942
+ interactive: true,
943
+ });
944
+
945
+ await expect(
946
+ manager.send({ sessionId, prompt: "queued first", delivery: "queue" }),
947
+ ).resolves.toBeUndefined();
948
+ await expect(
949
+ manager.send({ sessionId, prompt: "queued second", delivery: "steer" }),
950
+ ).resolves.toBeUndefined();
951
+
952
+ expect(run).not.toHaveBeenCalled();
953
+ expect(continueFn).not.toHaveBeenCalled();
954
+ const promptSnapshots = events
955
+ .filter((event) => {
956
+ return (
957
+ typeof event === "object" &&
958
+ event !== null &&
959
+ "type" in event &&
960
+ event.type === "pending_prompts"
961
+ );
962
+ })
963
+ .map((event) => (event as { payload: { prompts: unknown[] } }).payload);
964
+ expect(promptSnapshots.at(-1)).toEqual({
965
+ prompts: [
966
+ expect.objectContaining({
967
+ prompt: "queued second",
968
+ delivery: "steer",
969
+ attachmentCount: 0,
970
+ }),
971
+ expect.objectContaining({
972
+ prompt: "queued first",
973
+ delivery: "queue",
974
+ attachmentCount: 0,
975
+ }),
976
+ ],
977
+ sessionId,
978
+ });
979
+
980
+ canStartRun = true;
981
+ await manager.send({ sessionId, prompt: "run now" });
982
+ expect(run).toHaveBeenCalledTimes(1);
983
+ expect(
984
+ events.some((event) => {
985
+ return (
986
+ typeof event === "object" &&
987
+ event !== null &&
988
+ "type" in event &&
989
+ event.type === "pending_prompt_submitted" &&
990
+ "payload" in event &&
991
+ (event.payload as { prompt?: string }).prompt === "queued second"
992
+ );
993
+ }),
994
+ ).toBe(true);
995
+ });
996
+
525
997
  it("returns undefined accumulated usage for unknown sessions", async () => {
526
998
  const manager = new DefaultSessionManager({
527
999
  distinctId,
@@ -1155,4 +1627,115 @@ describe("DefaultSessionManager", () => {
1155
1627
  failedMessages,
1156
1628
  );
1157
1629
  });
1630
+
1631
+ it("persists teammate progress updates for team-task sub-sessions", async () => {
1632
+ const sessionId = "sess-team-task-progress";
1633
+ const manifest = createManifest(sessionId);
1634
+ const sessionService = {
1635
+ ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1636
+ createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1637
+ manifestPath: "/tmp/manifest-team-task-progress.json",
1638
+ transcriptPath: "/tmp/transcript-team-task-progress.log",
1639
+ hookPath: "/tmp/hook-team-task-progress.log",
1640
+ messagesPath: "/tmp/messages-team-task-progress.json",
1641
+ manifest,
1642
+ }),
1643
+ persistSessionMessages: vi.fn(),
1644
+ updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1645
+ writeSessionManifest: vi.fn(),
1646
+ listSessions: vi.fn().mockResolvedValue([]),
1647
+ deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1648
+ onTeamTaskStart: vi.fn().mockResolvedValue(undefined),
1649
+ onTeamTaskEnd: vi.fn().mockResolvedValue(undefined),
1650
+ onTeamTaskProgress: vi.fn().mockResolvedValue(undefined),
1651
+ };
1652
+
1653
+ let onTeamEvent: ((event: unknown) => void) | undefined;
1654
+ const runtimeBuilder = {
1655
+ build: vi
1656
+ .fn()
1657
+ .mockImplementation(
1658
+ (input: { onTeamEvent?: (event: unknown) => void }) => {
1659
+ onTeamEvent = input.onTeamEvent;
1660
+ return {
1661
+ tools: [],
1662
+ shutdown: vi.fn(),
1663
+ };
1664
+ },
1665
+ ),
1666
+ };
1667
+
1668
+ const manager = new DefaultSessionManager({
1669
+ distinctId,
1670
+ sessionService: sessionService as never,
1671
+ runtimeBuilder,
1672
+ createAgent: () =>
1673
+ ({
1674
+ run: vi.fn().mockImplementation(async () => {
1675
+ onTeamEvent?.({
1676
+ type: "task_start",
1677
+ agentId: "providers-investigator",
1678
+ message: "Investigate provider boundaries",
1679
+ });
1680
+ onTeamEvent?.({
1681
+ type: "run_progress",
1682
+ run: {
1683
+ id: "run_00002",
1684
+ agentId: "providers-investigator",
1685
+ status: "running",
1686
+ message: "Investigate provider boundaries",
1687
+ priority: 0,
1688
+ retryCount: 0,
1689
+ maxRetries: 0,
1690
+ continueConversation: false,
1691
+ startedAt: new Date("2026-01-01T00:00:00.000Z"),
1692
+ lastProgressAt: new Date("2026-01-01T00:00:01.000Z"),
1693
+ lastProgressMessage: "heartbeat",
1694
+ currentActivity: "heartbeat",
1695
+ },
1696
+ message: "heartbeat",
1697
+ });
1698
+ onTeamEvent?.({
1699
+ type: "agent_event",
1700
+ agentId: "providers-investigator",
1701
+ event: {
1702
+ type: "content_start",
1703
+ contentType: "text",
1704
+ text: "Drafting the provider boundary analysis now.",
1705
+ },
1706
+ });
1707
+ onTeamEvent?.({
1708
+ type: "task_end",
1709
+ agentId: "providers-investigator",
1710
+ result: createResult(),
1711
+ });
1712
+ return createResult({ text: "lead handled progress" });
1713
+ }),
1714
+ continue: vi.fn(),
1715
+ abort: vi.fn(),
1716
+ shutdown: vi.fn().mockResolvedValue(undefined),
1717
+ getMessages: vi.fn().mockReturnValue([]),
1718
+ messages: [],
1719
+ }) as never,
1720
+ });
1721
+
1722
+ await manager.start({
1723
+ config: createConfig({ sessionId }),
1724
+ prompt: "run teammate work",
1725
+ interactive: false,
1726
+ });
1727
+
1728
+ expect(sessionService.onTeamTaskProgress).toHaveBeenCalledWith(
1729
+ sessionId,
1730
+ "providers-investigator",
1731
+ "heartbeat",
1732
+ { kind: "heartbeat" },
1733
+ );
1734
+ expect(sessionService.onTeamTaskProgress).toHaveBeenCalledWith(
1735
+ sessionId,
1736
+ "providers-investigator",
1737
+ "Drafting the provider boundary analysis now.",
1738
+ { kind: "text" },
1739
+ );
1740
+ });
1158
1741
  });