macro-agent 0.0.14 → 0.0.16

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 (154) hide show
  1. package/.claude/settings.local.json +59 -0
  2. package/dist/acp/index.d.ts +1 -1
  3. package/dist/acp/index.d.ts.map +1 -1
  4. package/dist/acp/index.js.map +1 -1
  5. package/dist/acp/macro-agent.d.ts +21 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +182 -0
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +31 -2
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/agent/agent-manager.js +10 -4
  14. package/dist/agent/agent-manager.js.map +1 -1
  15. package/dist/cli/acp.d.ts +6 -0
  16. package/dist/cli/acp.d.ts.map +1 -1
  17. package/dist/cli/acp.js +16 -2
  18. package/dist/cli/acp.js.map +1 -1
  19. package/dist/map/adapter/acp-over-map.d.ts +5 -0
  20. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  21. package/dist/map/adapter/acp-over-map.js +47 -4
  22. package/dist/map/adapter/acp-over-map.js.map +1 -1
  23. package/dist/map/utils/address-translation.d.ts +99 -0
  24. package/dist/map/utils/address-translation.d.ts.map +1 -0
  25. package/dist/map/utils/address-translation.js +285 -0
  26. package/dist/map/utils/address-translation.js.map +1 -0
  27. package/dist/map/utils/index.d.ts +7 -0
  28. package/dist/map/utils/index.d.ts.map +1 -0
  29. package/dist/map/utils/index.js +7 -0
  30. package/dist/map/utils/index.js.map +1 -0
  31. package/dist/store/event-store.js +9 -2
  32. package/dist/store/event-store.js.map +1 -1
  33. package/dist/store/types/agents.d.ts +2 -0
  34. package/dist/store/types/agents.d.ts.map +1 -1
  35. package/package.json +4 -4
  36. package/references/acp-factory-ref/CHANGELOG.md +33 -0
  37. package/references/acp-factory-ref/LICENSE +21 -0
  38. package/references/acp-factory-ref/README.md +341 -0
  39. package/references/acp-factory-ref/package-lock.json +3102 -0
  40. package/references/acp-factory-ref/package.json +96 -0
  41. package/references/acp-factory-ref/python/CHANGELOG.md +33 -0
  42. package/references/acp-factory-ref/python/LICENSE +21 -0
  43. package/references/acp-factory-ref/python/Makefile +57 -0
  44. package/references/acp-factory-ref/python/README.md +253 -0
  45. package/references/acp-factory-ref/python/pyproject.toml +73 -0
  46. package/references/acp-factory-ref/python/tests/__init__.py +0 -0
  47. package/references/acp-factory-ref/python/tests/e2e/__init__.py +1 -0
  48. package/references/acp-factory-ref/python/tests/e2e/test_codex_e2e.py +349 -0
  49. package/references/acp-factory-ref/python/tests/e2e/test_gemini_e2e.py +165 -0
  50. package/references/acp-factory-ref/python/tests/e2e/test_opencode_e2e.py +296 -0
  51. package/references/acp-factory-ref/python/tests/test_client_handler.py +543 -0
  52. package/references/acp-factory-ref/python/tests/test_pushable.py +199 -0
  53. package/references/claude-code-acp/.github/workflows/ci.yml +45 -0
  54. package/references/claude-code-acp/.github/workflows/publish.yml +34 -0
  55. package/references/claude-code-acp/.prettierrc.json +4 -0
  56. package/references/claude-code-acp/CHANGELOG.md +249 -0
  57. package/references/claude-code-acp/LICENSE +222 -0
  58. package/references/claude-code-acp/README.md +53 -0
  59. package/references/claude-code-acp/docs/RELEASES.md +24 -0
  60. package/references/claude-code-acp/eslint.config.js +48 -0
  61. package/references/claude-code-acp/package-lock.json +4570 -0
  62. package/references/claude-code-acp/package.json +88 -0
  63. package/references/claude-code-acp/scripts/release.sh +119 -0
  64. package/references/claude-code-acp/src/acp-agent.ts +2079 -0
  65. package/references/claude-code-acp/src/index.ts +26 -0
  66. package/references/claude-code-acp/src/lib.ts +38 -0
  67. package/references/claude-code-acp/src/mcp-server.ts +911 -0
  68. package/references/claude-code-acp/src/settings.ts +522 -0
  69. package/references/claude-code-acp/src/tests/.claude/commands/quick-math.md +5 -0
  70. package/references/claude-code-acp/src/tests/.claude/commands/say-hello.md +6 -0
  71. package/references/claude-code-acp/src/tests/acp-agent-fork.test.ts +479 -0
  72. package/references/claude-code-acp/src/tests/acp-agent.test.ts +1502 -0
  73. package/references/claude-code-acp/src/tests/extract-lines.test.ts +103 -0
  74. package/references/claude-code-acp/src/tests/fork-session.test.ts +335 -0
  75. package/references/claude-code-acp/src/tests/replace-and-calculate-location.test.ts +334 -0
  76. package/references/claude-code-acp/src/tests/settings.test.ts +617 -0
  77. package/references/claude-code-acp/src/tests/skills-options.test.ts +187 -0
  78. package/references/claude-code-acp/src/tests/tools.test.ts +318 -0
  79. package/references/claude-code-acp/src/tests/typescript-declarations.test.ts +558 -0
  80. package/references/claude-code-acp/src/tools.ts +819 -0
  81. package/references/claude-code-acp/src/utils.ts +171 -0
  82. package/references/claude-code-acp/tsconfig.json +18 -0
  83. package/references/claude-code-acp/vitest.config.ts +19 -0
  84. package/references/multi-agent-protocol/.sudocode/issues.jsonl +111 -0
  85. package/references/multi-agent-protocol/.sudocode/specs.jsonl +13 -0
  86. package/references/multi-agent-protocol/LICENSE +21 -0
  87. package/references/multi-agent-protocol/README.md +113 -0
  88. package/references/multi-agent-protocol/docs/00-design-specification.md +496 -0
  89. package/references/multi-agent-protocol/docs/01-open-questions.md +1050 -0
  90. package/references/multi-agent-protocol/docs/02-wire-protocol.md +296 -0
  91. package/references/multi-agent-protocol/docs/03-streaming-semantics.md +252 -0
  92. package/references/multi-agent-protocol/docs/04-error-handling.md +231 -0
  93. package/references/multi-agent-protocol/docs/05-connection-model.md +244 -0
  94. package/references/multi-agent-protocol/docs/06-visibility-permissions.md +243 -0
  95. package/references/multi-agent-protocol/docs/07-federation.md +259 -0
  96. package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +253 -0
  97. package/references/multi-agent-protocol/docs/09-authentication.md +680 -0
  98. package/references/multi-agent-protocol/docs/10-mail-protocol.md +553 -0
  99. package/references/multi-agent-protocol/docs/agent-iam-integration.md +877 -0
  100. package/references/multi-agent-protocol/docs/agentic-mesh-integration-draft.md +459 -0
  101. package/references/multi-agent-protocol/docs/git-transport-draft.md +251 -0
  102. package/references/multi-agent-protocol/docs-site/Gemfile +22 -0
  103. package/references/multi-agent-protocol/docs-site/README.md +82 -0
  104. package/references/multi-agent-protocol/docs-site/_config.yml +91 -0
  105. package/references/multi-agent-protocol/docs-site/_includes/head_custom.html +20 -0
  106. package/references/multi-agent-protocol/docs-site/_sass/color_schemes/map.scss +42 -0
  107. package/references/multi-agent-protocol/docs-site/_sass/custom/custom.scss +34 -0
  108. package/references/multi-agent-protocol/docs-site/examples/full-integration.md +510 -0
  109. package/references/multi-agent-protocol/docs-site/examples/index.md +138 -0
  110. package/references/multi-agent-protocol/docs-site/examples/simple-chat.md +282 -0
  111. package/references/multi-agent-protocol/docs-site/examples/task-queue.md +399 -0
  112. package/references/multi-agent-protocol/docs-site/getting-started/index.md +98 -0
  113. package/references/multi-agent-protocol/docs-site/getting-started/installation.md +219 -0
  114. package/references/multi-agent-protocol/docs-site/getting-started/overview.md +172 -0
  115. package/references/multi-agent-protocol/docs-site/getting-started/quickstart.md +237 -0
  116. package/references/multi-agent-protocol/docs-site/index.md +136 -0
  117. package/references/multi-agent-protocol/docs-site/protocol/authentication.md +391 -0
  118. package/references/multi-agent-protocol/docs-site/protocol/connection-model.md +376 -0
  119. package/references/multi-agent-protocol/docs-site/protocol/design.md +284 -0
  120. package/references/multi-agent-protocol/docs-site/protocol/error-handling.md +312 -0
  121. package/references/multi-agent-protocol/docs-site/protocol/federation.md +449 -0
  122. package/references/multi-agent-protocol/docs-site/protocol/index.md +129 -0
  123. package/references/multi-agent-protocol/docs-site/protocol/permissions.md +398 -0
  124. package/references/multi-agent-protocol/docs-site/protocol/streaming.md +353 -0
  125. package/references/multi-agent-protocol/docs-site/protocol/wire-protocol.md +369 -0
  126. package/references/multi-agent-protocol/docs-site/sdk/api/agent.md +357 -0
  127. package/references/multi-agent-protocol/docs-site/sdk/api/client.md +380 -0
  128. package/references/multi-agent-protocol/docs-site/sdk/api/index.md +62 -0
  129. package/references/multi-agent-protocol/docs-site/sdk/api/server.md +453 -0
  130. package/references/multi-agent-protocol/docs-site/sdk/api/types.md +468 -0
  131. package/references/multi-agent-protocol/docs-site/sdk/guides/agent.md +375 -0
  132. package/references/multi-agent-protocol/docs-site/sdk/guides/authentication.md +405 -0
  133. package/references/multi-agent-protocol/docs-site/sdk/guides/client.md +352 -0
  134. package/references/multi-agent-protocol/docs-site/sdk/guides/index.md +89 -0
  135. package/references/multi-agent-protocol/docs-site/sdk/guides/server.md +360 -0
  136. package/references/multi-agent-protocol/docs-site/sdk/guides/testing.md +446 -0
  137. package/references/multi-agent-protocol/docs-site/sdk/guides/transports.md +363 -0
  138. package/references/multi-agent-protocol/docs-site/sdk/index.md +206 -0
  139. package/references/multi-agent-protocol/package-lock.json +3886 -0
  140. package/references/multi-agent-protocol/package.json +56 -0
  141. package/references/multi-agent-protocol/schema/meta.json +467 -0
  142. package/references/multi-agent-protocol/schema/schema.json +2558 -0
  143. package/src/acp/__tests__/history.test.ts +526 -0
  144. package/src/acp/__tests__/integration.test.ts +2 -1
  145. package/src/acp/index.ts +4 -0
  146. package/src/acp/macro-agent.ts +329 -85
  147. package/src/acp/types.ts +39 -2
  148. package/src/agent/__tests__/agent-manager.test.ts +67 -1
  149. package/src/agent/agent-manager.ts +10 -4
  150. package/src/cli/__tests__/stable-instance-id.test.ts +57 -0
  151. package/src/cli/acp.ts +17 -2
  152. package/src/map/adapter/acp-over-map.ts +57 -2
  153. package/src/store/event-store.ts +10 -3
  154. package/src/store/types/agents.ts +2 -0
package/src/acp/types.ts CHANGED
@@ -630,10 +630,44 @@ export interface ResumeAgentResponse {
630
630
  /** The resumed agent's ID */
631
631
  agentId: AgentId;
632
632
 
633
- /** The agent's session ID */
633
+ /** The agent's session ID (from resume) */
634
634
  sessionId: string;
635
635
  }
636
636
 
637
+ // ─────────────────────────────────────────────────────────────────
638
+ // History Extension Types
639
+ // ─────────────────────────────────────────────────────────────────
640
+
641
+ /**
642
+ * A historical turn in a session conversation
643
+ */
644
+ export interface HistoryTurn {
645
+ /** Turn role */
646
+ role: "user" | "assistant";
647
+ /** Timestamp of the turn */
648
+ timestamp: number;
649
+ /** Turn content — plain text for user, structured parts for assistant */
650
+ content: unknown;
651
+ }
652
+
653
+ /**
654
+ * Request for _macro/getHistory extension
655
+ */
656
+ export interface GetHistoryRequest {
657
+ /** ACP session ID to get history for */
658
+ sessionId: string;
659
+ /** Maximum number of turns to return */
660
+ limit?: number;
661
+ }
662
+
663
+ /**
664
+ * Response for _macro/getHistory extension
665
+ */
666
+ export interface GetHistoryResponse {
667
+ /** Conversation turns in chronological order */
668
+ turns: HistoryTurn[];
669
+ }
670
+
637
671
  // ─────────────────────────────────────────────────────────────────
638
672
  // Extension Method Types (Union)
639
673
  // ─────────────────────────────────────────────────────────────────
@@ -657,7 +691,8 @@ export type ACPExtensionMethod =
657
691
  | "_macro/checkCapability"
658
692
  | "_macro/respondToPermission"
659
693
  | "_macro/cancelPermission"
660
- | "_macro/resume";
694
+ | "_macro/resume"
695
+ | "_macro/getHistory";
661
696
 
662
697
  /**
663
698
  * Map of extension methods to their request types
@@ -679,6 +714,7 @@ export interface ACPExtensionRequests {
679
714
  "_macro/respondToPermission": RespondToPermissionRequest;
680
715
  "_macro/cancelPermission": CancelPermissionRequest;
681
716
  "_macro/resume": ResumeAgentRequest;
717
+ "_macro/getHistory": GetHistoryRequest;
682
718
  }
683
719
 
684
720
  /**
@@ -701,6 +737,7 @@ export interface ACPExtensionResponses {
701
737
  "_macro/respondToPermission": RespondToPermissionResponse;
702
738
  "_macro/cancelPermission": CancelPermissionResponse;
703
739
  "_macro/resume": ResumeAgentResponse;
740
+ "_macro/getHistory": GetHistoryResponse;
704
741
  }
705
742
 
706
743
  // ─────────────────────────────────────────────────────────────────
@@ -455,7 +455,7 @@ describe("AgentManager Integration (with mocked acp-factory)", () => {
455
455
  });
456
456
 
457
457
  expect(result.id).toMatch(/^agent_/);
458
- // Session ID is now pre-generated before createSession (for race condition fix)
458
+ // session_id is macro-agent's own ID (pre-generated before createSession)
459
459
  expect(result.session_id).toMatch(/^session_/);
460
460
  expect(result.session).toBeDefined();
461
461
 
@@ -464,6 +464,8 @@ describe("AgentManager Integration (with mocked acp-factory)", () => {
464
464
  expect(agent).toBeDefined();
465
465
  expect(agent?.state).toBe("running");
466
466
  expect(agent?.task).toBe("Test task");
467
+ // provider_session_id should be set from createSession's returned session.id
468
+ expect(agent?.provider_session_id).toBe("mock_session_1");
467
469
  });
468
470
 
469
471
  it("should set up default subscriptions", async () => {
@@ -684,6 +686,70 @@ describe("AgentManager Integration (with mocked acp-factory)", () => {
684
686
  "already has active session",
685
687
  );
686
688
  });
689
+
690
+ it("should use agent's original cwd when loading session", async () => {
691
+ const spawned = await agentManager.spawn({
692
+ task: "Test with custom cwd",
693
+ cwd: "/custom/project/path",
694
+ });
695
+ await agentManager.terminate(spawned.id, "completed");
696
+
697
+ await agentManager.resume(spawned.id);
698
+
699
+ // loadSession should be called with the agent's original cwd, not the default
700
+ expect(mockHandle.loadSession).toHaveBeenCalledWith(
701
+ expect.any(String), // session ID
702
+ "/custom/project/path",
703
+ );
704
+ });
705
+
706
+ it("should use provider_session_id when loading session", async () => {
707
+ const spawned = await agentManager.spawn({ task: "Test" });
708
+
709
+ // Verify the agent has provider_session_id from createSession
710
+ const agent = agentManager.get(spawned.id);
711
+ expect(agent?.provider_session_id).toBeDefined();
712
+
713
+ await agentManager.terminate(spawned.id, "completed");
714
+ await agentManager.resume(spawned.id);
715
+
716
+ // loadSession should be called with the provider_session_id (Claude Code UUID)
717
+ expect(mockHandle.loadSession).toHaveBeenCalledWith(
718
+ agent!.provider_session_id,
719
+ expect.any(String),
720
+ );
721
+ });
722
+
723
+ it("should fall back to session_id if provider_session_id is not set", async () => {
724
+ // Create agent directly without provider_session_id
725
+ const agentId = "agent_no_provider";
726
+ const sessionId = "session_no_provider";
727
+ eventStore.emit({
728
+ type: "spawn",
729
+ source: { agent_id: "system" },
730
+ payload: {
731
+ agent_id: agentId,
732
+ session_id: sessionId,
733
+ task: "Test",
734
+ parent: null,
735
+ cwd: "/tmp",
736
+ },
737
+ });
738
+ // Emit started status WITHOUT provider_session_id
739
+ eventStore.emit({
740
+ type: "status",
741
+ source: { agent_id: agentId },
742
+ payload: { status_type: "started" },
743
+ });
744
+
745
+ await agentManager.resume(agentId);
746
+
747
+ // Should fall back to the macro-agent session_id
748
+ expect(mockHandle.loadSession).toHaveBeenCalledWith(
749
+ sessionId,
750
+ "/tmp",
751
+ );
752
+ });
687
753
  });
688
754
 
689
755
  describe("getOrCreateHeadManager()", () => {
@@ -564,12 +564,15 @@ export function createAgentManager(
564
564
  });
565
565
 
566
566
  // Emit started status (session is ready)
567
+ // Include the provider's session ID (e.g., Claude Code UUID) so
568
+ // it can be used for handle.loadSession() during resume
567
569
  eventStore.emit({
568
570
  type: "status",
569
571
  source: { agent_id: agentId },
570
572
  payload: {
571
573
  status_type: "started",
572
574
  summary: "Agent session started",
575
+ provider_session_id: session.id,
573
576
  },
574
577
  });
575
578
 
@@ -703,7 +706,7 @@ export function createAgentManager(
703
706
 
704
707
  return {
705
708
  id: agentId,
706
- session_id: sessionId, // Use our pre-generated ID (matches what's in EventStore)
709
+ session_id: sessionId, // Macro-agent's own session ID for ACP protocol mapping
707
710
  agent,
708
711
  session,
709
712
  workspace,
@@ -903,8 +906,11 @@ export function createAgentManager(
903
906
  permissionMode: defaultPermissionMode,
904
907
  });
905
908
 
906
- // Load the existing session by ID
907
- const session = await handle.loadSession(agent.session_id, defaultCwd);
909
+ // Load the existing session using the provider's session ID (e.g., Claude Code UUID)
910
+ // Falls back to macro-agent session_id for backwards compatibility
911
+ const loadSessionId = agent.provider_session_id ?? agent.session_id;
912
+ const agentCwd = agent.cwd ?? defaultCwd;
913
+ const session = await handle.loadSession(loadSessionId, agentCwd);
908
914
 
909
915
  // Track active session
910
916
  const activeSession: ActiveSession = {
@@ -928,7 +934,7 @@ export function createAgentManager(
928
934
 
929
935
  return {
930
936
  id: agentId,
931
- session_id: session.id,
937
+ session_id: agent.session_id, // Macro-agent's own session ID
932
938
  agent: eventStore.getAgent(agentId)!,
933
939
  session,
934
940
  };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Tests for getStableInstanceId
3
+ *
4
+ * Verifies that the stable instance ID is deterministic, consistent,
5
+ * and produces valid instance IDs for the EventStore.
6
+ */
7
+
8
+ import { describe, it, expect } from "vitest";
9
+ import { resolve } from "node:path";
10
+ import { getStableInstanceId } from "../acp.js";
11
+
12
+ describe("getStableInstanceId", () => {
13
+ it("should return the same ID for the same path", () => {
14
+ const id1 = getStableInstanceId("/Users/test/project");
15
+ const id2 = getStableInstanceId("/Users/test/project");
16
+ expect(id1).toBe(id2);
17
+ });
18
+
19
+ it("should return different IDs for different paths", () => {
20
+ const id1 = getStableInstanceId("/Users/test/project-a");
21
+ const id2 = getStableInstanceId("/Users/test/project-b");
22
+ expect(id1).not.toBe(id2);
23
+ });
24
+
25
+ it("should start with inst_ prefix", () => {
26
+ const id = getStableInstanceId("/tmp/test");
27
+ expect(id).toMatch(/^inst_/);
28
+ });
29
+
30
+ it("should produce a valid instance ID format (alphanumeric + underscore)", () => {
31
+ const id = getStableInstanceId("/tmp/test");
32
+ expect(id).toMatch(/^[a-zA-Z0-9_]+$/);
33
+ });
34
+
35
+ it("should normalize relative paths to absolute", () => {
36
+ // Both should resolve to the same absolute path
37
+ const absPath = resolve("./src");
38
+ const id1 = getStableInstanceId("./src");
39
+ const id2 = getStableInstanceId(absPath);
40
+ expect(id1).toBe(id2);
41
+ });
42
+
43
+ it("should handle paths with trailing slashes consistently", () => {
44
+ // resolve() strips trailing slashes, so these should be equal
45
+ const id1 = getStableInstanceId("/tmp/project");
46
+ const id2 = getStableInstanceId("/tmp/project/");
47
+ expect(id1).toBe(id2);
48
+ });
49
+
50
+ it("should produce a fixed-length ID", () => {
51
+ const short = getStableInstanceId("/a");
52
+ const long = getStableInstanceId("/very/long/path/to/some/deeply/nested/project/directory");
53
+ // Both should be inst_ + 12 hex chars = 17 chars total
54
+ expect(short.length).toBe(17);
55
+ expect(long.length).toBe(17);
56
+ });
57
+ });
package/src/cli/acp.ts CHANGED
@@ -39,7 +39,8 @@
39
39
  */
40
40
 
41
41
  import { readFileSync } from "node:fs";
42
- import { dirname, join } from "node:path";
42
+ import { createHash } from "node:crypto";
43
+ import { dirname, join, resolve } from "node:path";
43
44
  import { Readable } from "node:stream";
44
45
  import { fileURLToPath } from "node:url";
45
46
  import {
@@ -119,6 +120,17 @@ export function parseArgs(argv?: string[]): ACPServerOptions {
119
120
  return options;
120
121
  }
121
122
 
123
+ /**
124
+ * Generate a stable instance ID from the working directory.
125
+ * This ensures the same project always uses the same EventStore,
126
+ * so agents and sessions persist across server restarts.
127
+ */
128
+ export function getStableInstanceId(cwd: string): string {
129
+ const normalizedPath = resolve(cwd);
130
+ const hash = createHash("sha256").update(normalizedPath).digest("hex").slice(0, 12);
131
+ return `inst_${hash}`;
132
+ }
133
+
122
134
  // ─────────────────────────────────────────────────────────────────
123
135
  // Stream Setup
124
136
  // ─────────────────────────────────────────────────────────────────
@@ -159,9 +171,12 @@ async function main() {
159
171
  const defaultCwd = options.cwd ?? process.cwd();
160
172
 
161
173
  // Initialize services
174
+ // Use explicit --instance-id if provided, otherwise derive a stable ID from the CWD
175
+ // so the same project always reuses the same EventStore across server restarts
176
+ const instanceId = options.instanceId ?? getStableInstanceId(defaultCwd);
162
177
  const eventStore = await createEventStore({
163
178
  inMemory: false,
164
- instanceId: options.instanceId,
179
+ instanceId,
165
180
  });
166
181
 
167
182
  // We need to create agentManager first (with a placeholder router),
@@ -117,11 +117,11 @@ export class ACPOverMAPHandler {
117
117
  break;
118
118
 
119
119
  case "session/new":
120
- result = await this.handleNewSession(streamState, acp.params);
120
+ result = await this.handleNewSession(streamState, acp.params, emitNotification);
121
121
  break;
122
122
 
123
123
  case "session/load":
124
- result = await this.handleLoadSession(streamState, acp.params);
124
+ result = await this.handleLoadSession(streamState, acp.params, emitNotification);
125
125
  break;
126
126
 
127
127
  case "authenticate":
@@ -201,6 +201,7 @@ export class ACPOverMAPHandler {
201
201
  private async handleNewSession(
202
202
  streamState: StreamState,
203
203
  params: unknown,
204
+ emitNotification?: ACPNotificationEmitter,
204
205
  ): Promise<unknown> {
205
206
  if (!streamState.initialized) {
206
207
  throw new Error("Must call initialize before newSession");
@@ -224,12 +225,16 @@ export class ACPOverMAPHandler {
224
225
 
225
226
  console.error(`[ACP-over-MAP] Created session ${sessionId} -> agent ${spawned.id}`);
226
227
 
228
+ // Emit session_info_update so client has title/timestamps
229
+ this.emitSessionInfo(streamState, sessionId, emitNotification);
230
+
227
231
  return { sessionId };
228
232
  }
229
233
 
230
234
  private async handleLoadSession(
231
235
  streamState: StreamState,
232
236
  params: unknown,
237
+ emitNotification?: ACPNotificationEmitter,
233
238
  ): Promise<unknown> {
234
239
  if (!streamState.initialized) {
235
240
  throw new Error("Must call initialize before loadSession");
@@ -271,6 +276,7 @@ export class ACPOverMAPHandler {
271
276
  streamState.sessionId = sessionId;
272
277
  streamState.agentId = existing.id;
273
278
  this.sessionMapper.createMapping(sessionId as ACPSessionId, existing.id);
279
+ this.emitSessionInfo(streamState, sessionId, emitNotification);
274
280
  return {};
275
281
  }
276
282
 
@@ -280,6 +286,7 @@ export class ACPOverMAPHandler {
280
286
  streamState.sessionId = sessionId;
281
287
  streamState.agentId = spawned.id;
282
288
  this.sessionMapper.createMapping(sessionId as ACPSessionId, spawned.id);
289
+ this.emitSessionInfo(streamState, sessionId, emitNotification);
283
290
  return {};
284
291
  }
285
292
 
@@ -293,6 +300,7 @@ export class ACPOverMAPHandler {
293
300
  streamState.sessionId = sessionId;
294
301
  streamState.agentId = spawned.id;
295
302
  this.sessionMapper.createMapping(sessionId as ACPSessionId, spawned.id);
303
+ this.emitSessionInfo(streamState, sessionId, emitNotification);
296
304
 
297
305
  return {};
298
306
  }
@@ -388,6 +396,9 @@ export class ACPOverMAPHandler {
388
396
 
389
397
  console.error(`[ACP-over-MAP] Prompt completed for agent ${agentId}, ${updateCount} updates`);
390
398
 
399
+ // Emit updated session info after prompt completes
400
+ this.emitSessionInfo(streamState, sessionId, emitNotification);
401
+
391
402
  return { stopReason: "end_turn" };
392
403
  } catch (error) {
393
404
  console.error(`[ACP-over-MAP] Prompt error:`, error);
@@ -532,6 +543,50 @@ export class ACPOverMAPHandler {
532
543
  }
533
544
  }
534
545
 
546
+ // ─────────────────────────────────────────────────────────────────
547
+ // Session Info
548
+ // ─────────────────────────────────────────────────────────────────
549
+
550
+ /**
551
+ * Emit a session_info_update notification with title and timestamps.
552
+ * Uses agent task as title and session mapping timestamps.
553
+ */
554
+ private emitSessionInfo(
555
+ streamState: StreamState,
556
+ sessionId: string,
557
+ emitNotification?: ACPNotificationEmitter,
558
+ ): void {
559
+ if (!emitNotification) return;
560
+
561
+ const mapping = this.sessionMapper.getMapping(sessionId as ACPSessionId);
562
+ const agentId = streamState.agentId;
563
+ const agent = agentId ? this.eventStore.getAgent(agentId as AgentId) : null;
564
+
565
+ const title = agent?.task ?? null;
566
+ const updatedAt = new Date(mapping?.updatedAt ?? Date.now()).toISOString();
567
+
568
+ const notification: ACPEnvelope = {
569
+ acp: {
570
+ jsonrpc: "2.0",
571
+ method: "session/update",
572
+ params: {
573
+ sessionId,
574
+ update: {
575
+ sessionUpdate: "session_info_update",
576
+ title,
577
+ updatedAt,
578
+ },
579
+ },
580
+ },
581
+ acpContext: {
582
+ streamId: streamState.streamId,
583
+ sessionId,
584
+ direction: "agent-to-client",
585
+ },
586
+ };
587
+ emitNotification(notification);
588
+ }
589
+
535
590
  // ─────────────────────────────────────────────────────────────────
536
591
  // Cleanup
537
592
  // ─────────────────────────────────────────────────────────────────
@@ -1354,6 +1354,7 @@ function applySpawnEvent(
1354
1354
  store.setRow('agents', agentId, {
1355
1355
  id: agentId,
1356
1356
  session_id: payload.session_id,
1357
+ provider_session_id: '',
1357
1358
  parent: parent ?? '',
1358
1359
  lineage: JSON.stringify(lineage),
1359
1360
  state: 'spawning',
@@ -1408,15 +1409,20 @@ function applyStatusEvent(
1408
1409
  const agentId = event.source.agent_id;
1409
1410
  if (!agentId) return;
1410
1411
 
1411
- const payload = event.payload as { status_type: string };
1412
+ const payload = event.payload as { status_type: string; provider_session_id?: string };
1412
1413
 
1413
1414
  // Handle specific status types
1414
1415
  if (payload.status_type === 'started') {
1415
- store.setPartialRow('agents', agentId, {
1416
+ const updates: Record<string, string | number> = {
1416
1417
  state: 'running',
1417
1418
  started_at: event.timestamp,
1418
1419
  last_activity_at: event.timestamp,
1419
- });
1420
+ };
1421
+ // Store the provider's session ID (e.g., Claude Code UUID for --resume)
1422
+ if (payload.provider_session_id) {
1423
+ updates.provider_session_id = payload.provider_session_id;
1424
+ }
1425
+ store.setPartialRow('agents', agentId, updates);
1420
1426
  } else {
1421
1427
  // Always update last_activity_at on any status event
1422
1428
  store.setPartialRow('agents', agentId, {
@@ -1696,6 +1702,7 @@ function rowToAgent(row: Record<string, unknown>): Agent {
1696
1702
  return {
1697
1703
  id: row.id as AgentId,
1698
1704
  session_id: row.session_id as string,
1705
+ provider_session_id: (row.provider_session_id as string) || undefined,
1699
1706
  parent: (row.parent as string) || null,
1700
1707
  lineage: row.lineage ? JSON.parse(row.lineage as string) : [],
1701
1708
  state: row.state as AgentState,
@@ -29,6 +29,8 @@ export interface AgentConfig {
29
29
  export interface Agent {
30
30
  id: AgentId;
31
31
  session_id: SessionId;
32
+ /** Session ID from the underlying agent provider (e.g., Claude Code UUID for --resume) */
33
+ provider_session_id?: string;
32
34
  parent: AgentId | null;
33
35
  lineage: AgentId[];
34
36
  state: AgentState;