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
@@ -0,0 +1,526 @@
1
+ /**
2
+ * History persistence and retrieval tests
3
+ *
4
+ * Tests the _macro/getHistory extension method and the underlying
5
+ * conversation/turn persistence that records prompt interactions.
6
+ *
7
+ * Uses a real in-memory EventStore to verify the full flow:
8
+ * ensureConversation → recordPromptTurns → handleGetHistory
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
12
+ import { MacroAgent } from "../macro-agent.js";
13
+ import { createEventStore, type EventStore } from "../../store/event-store.js";
14
+ import type { AgentSideConnection } from "@agentclientprotocol/sdk";
15
+ import type { AgentManager } from "../../agent/agent-manager.js";
16
+ import type { TaskManager } from "../../task/task-manager.js";
17
+ import type { Agent, Task } from "../../store/types/index.js";
18
+
19
+ // ─────────────────────────────────────────────────────────────────
20
+ // Helpers
21
+ // ─────────────────────────────────────────────────────────────────
22
+
23
+ function createMockAgent(overrides: Partial<Agent> = {}): Agent {
24
+ return {
25
+ id: "agent-1",
26
+ session_id: "session-1",
27
+ state: "running",
28
+ task: "Test task",
29
+ task_id: "task-1",
30
+ parent: null,
31
+ lineage: [],
32
+ config: {},
33
+ cwd: "/test/cwd",
34
+ created_at: Date.now(),
35
+ started_at: Date.now(),
36
+ ...overrides,
37
+ };
38
+ }
39
+
40
+ function createMockTask(overrides: Partial<Task> = {}): Task {
41
+ return {
42
+ id: "task-1",
43
+ description: "Test task",
44
+ status: "in_progress",
45
+ created_by: "agent-1",
46
+ created_at: Date.now(),
47
+ ...overrides,
48
+ };
49
+ }
50
+
51
+ function createMockConnection(): AgentSideConnection {
52
+ return {
53
+ sessionUpdate: vi.fn().mockResolvedValue(undefined),
54
+ requestPermission: vi.fn().mockResolvedValue({ outcome: "allow_once" }),
55
+ closed: Promise.resolve(),
56
+ } as unknown as AgentSideConnection;
57
+ }
58
+
59
+ /**
60
+ * Create a mock AgentManager that yields the given streaming updates
61
+ * from its `prompt()` method.
62
+ */
63
+ function createMockAgentManager(
64
+ promptUpdates: unknown[] = []
65
+ ): AgentManager {
66
+ const mockAgent = createMockAgent();
67
+
68
+ return {
69
+ spawn: vi.fn().mockResolvedValue({
70
+ id: "agent-new",
71
+ session_id: "session-new",
72
+ agent: createMockAgent({ id: "agent-new", session_id: "session-new" }),
73
+ session: {},
74
+ }),
75
+ get: vi.fn().mockReturnValue(mockAgent),
76
+ list: vi.fn().mockReturnValue([mockAgent]),
77
+ listHeadManagers: vi.fn().mockReturnValue([mockAgent]),
78
+ getChildren: vi.fn().mockReturnValue([]),
79
+ getHierarchy: vi.fn().mockReturnValue({
80
+ root: { agent: mockAgent, children: [] },
81
+ depth: 1,
82
+ totalAgents: 1,
83
+ }),
84
+ getOrCreateHeadManager: vi.fn().mockResolvedValue({
85
+ id: "agent-1",
86
+ session_id: "session-1",
87
+ agent: mockAgent,
88
+ session: {},
89
+ }),
90
+ hasActiveSession: vi.fn().mockReturnValue(true),
91
+ resume: vi.fn().mockResolvedValue({
92
+ id: "agent-1",
93
+ session_id: "session-1",
94
+ agent: mockAgent,
95
+ session: {},
96
+ }),
97
+ terminate: vi.fn().mockResolvedValue(undefined),
98
+ prompt: vi.fn().mockReturnValue({
99
+ [Symbol.asyncIterator]: async function* () {
100
+ for (const update of promptUpdates) {
101
+ yield update;
102
+ }
103
+ },
104
+ }),
105
+ getSession: vi.fn().mockReturnValue(null),
106
+ onLifecycleEvent: vi.fn().mockReturnValue(() => {}),
107
+ close: vi.fn().mockResolvedValue(undefined),
108
+ respondToPermission: vi.fn().mockReturnValue(true),
109
+ cancelPermission: vi.fn().mockReturnValue(true),
110
+ } as unknown as AgentManager;
111
+ }
112
+
113
+ function createMockTaskManager(): TaskManager {
114
+ return {
115
+ get: vi.fn().mockReturnValue(createMockTask()),
116
+ list: vi.fn().mockReturnValue([createMockTask()]),
117
+ create: vi.fn().mockReturnValue(createMockTask()),
118
+ } as unknown as TaskManager;
119
+ }
120
+
121
+ // ─────────────────────────────────────────────────────────────────
122
+ // Tests
123
+ // ─────────────────────────────────────────────────────────────────
124
+
125
+ describe("_macro/getHistory", () => {
126
+ let eventStore: EventStore;
127
+ let macroAgent: MacroAgent;
128
+ let mockConnection: AgentSideConnection;
129
+
130
+ afterEach(async () => {
131
+ await eventStore.close();
132
+ });
133
+
134
+ /**
135
+ * Helper to set up a MacroAgent with given prompt streaming updates.
136
+ */
137
+ async function setup(promptUpdates: unknown[] = []) {
138
+ eventStore = await createEventStore({ inMemory: true });
139
+ mockConnection = createMockConnection();
140
+
141
+ const agentManager = createMockAgentManager(promptUpdates);
142
+ const taskManager = createMockTaskManager();
143
+
144
+ macroAgent = new MacroAgent(mockConnection, {
145
+ agentManager,
146
+ eventStore,
147
+ taskManager,
148
+ defaultCwd: "/test/cwd",
149
+ });
150
+
151
+ await macroAgent.initialize({
152
+ protocolVersion: 1,
153
+ clientCapabilities: {},
154
+ });
155
+
156
+ return { agentManager, taskManager };
157
+ }
158
+
159
+ it("should return empty turns for a session with no history", async () => {
160
+ await setup();
161
+
162
+ // Create a session so it has a conversation
163
+ await macroAgent.newSession({ cwd: "/test" });
164
+ const sessionId =
165
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
166
+
167
+ const response = await macroAgent.extMethod("_macro/getHistory", {
168
+ sessionId,
169
+ });
170
+
171
+ expect(response).toHaveProperty("turns");
172
+ expect((response as { turns: unknown[] }).turns).toEqual([]);
173
+ });
174
+
175
+ it("should record and return user + assistant text turns after prompt", async () => {
176
+ await setup([
177
+ {
178
+ sessionUpdate: "agent_message_chunk",
179
+ content: { type: "text", text: "Hello " },
180
+ },
181
+ {
182
+ sessionUpdate: "agent_message_chunk",
183
+ content: { type: "text", text: "world!" },
184
+ },
185
+ ]);
186
+
187
+ // Create session
188
+ await macroAgent.newSession({ cwd: "/test" });
189
+ const sessionId =
190
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
191
+
192
+ // Send a prompt — this triggers recording
193
+ await macroAgent.prompt({
194
+ sessionId,
195
+ prompt: [{ type: "text", text: "Say hello" }],
196
+ });
197
+
198
+ // Retrieve history
199
+ const response = await macroAgent.extMethod("_macro/getHistory", {
200
+ sessionId,
201
+ });
202
+
203
+ const turns = (response as { turns: { role: string; content: unknown }[] })
204
+ .turns;
205
+
206
+ expect(turns).toHaveLength(2);
207
+
208
+ // User turn
209
+ expect(turns[0].role).toBe("user");
210
+ expect(turns[0].content).toBe("Say hello");
211
+
212
+ // Assistant turn — accumulated text chunks
213
+ expect(turns[1].role).toBe("assistant");
214
+ const content = turns[1].content as { parts: { type: string; text?: string }[] };
215
+ expect(content.parts[0].type).toBe("text");
216
+ expect(content.parts[0].text).toBe("Hello world!");
217
+ });
218
+
219
+ it("should record tool calls in assistant turns", async () => {
220
+ await setup([
221
+ {
222
+ sessionUpdate: "agent_message_chunk",
223
+ content: { type: "text", text: "Let me check that." },
224
+ },
225
+ {
226
+ sessionUpdate: "tool_call",
227
+ toolCallId: "tc-1",
228
+ title: "Read file",
229
+ status: "completed",
230
+ rawInput: { path: "/test.txt" },
231
+ output: "file contents here",
232
+ },
233
+ {
234
+ sessionUpdate: "agent_message_chunk",
235
+ content: { type: "text", text: " Done!" },
236
+ },
237
+ ]);
238
+
239
+ await macroAgent.newSession({ cwd: "/test" });
240
+ const sessionId =
241
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
242
+
243
+ await macroAgent.prompt({
244
+ sessionId,
245
+ prompt: [{ type: "text", text: "Read test.txt" }],
246
+ });
247
+
248
+ const response = await macroAgent.extMethod("_macro/getHistory", {
249
+ sessionId,
250
+ });
251
+
252
+ const turns = (response as { turns: { role: string; content: unknown }[] })
253
+ .turns;
254
+
255
+ expect(turns).toHaveLength(2);
256
+
257
+ // Assistant turn should have text + tool parts
258
+ const assistantContent = turns[1].content as {
259
+ parts: { type: string; text?: string; toolCallId?: string; title?: string; output?: unknown }[];
260
+ };
261
+ expect(assistantContent.parts).toHaveLength(2);
262
+ expect(assistantContent.parts[0]).toEqual({
263
+ type: "text",
264
+ text: "Let me check that. Done!",
265
+ });
266
+ expect(assistantContent.parts[1]).toMatchObject({
267
+ type: "tool",
268
+ toolCallId: "tc-1",
269
+ title: "Read file",
270
+ status: "completed",
271
+ output: "file contents here",
272
+ });
273
+ });
274
+
275
+ it("should accumulate history across multiple prompts", async () => {
276
+ // First prompt returns "Hello"
277
+ const { agentManager } = await setup([
278
+ {
279
+ sessionUpdate: "agent_message_chunk",
280
+ content: { type: "text", text: "Hello" },
281
+ },
282
+ ]);
283
+
284
+ await macroAgent.newSession({ cwd: "/test" });
285
+ const sessionId =
286
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
287
+
288
+ // First prompt
289
+ await macroAgent.prompt({
290
+ sessionId,
291
+ prompt: [{ type: "text", text: "Hi" }],
292
+ });
293
+
294
+ // Second prompt — update mock to return different content
295
+ vi.mocked(agentManager.prompt).mockReturnValue({
296
+ [Symbol.asyncIterator]: async function* () {
297
+ yield {
298
+ sessionUpdate: "agent_message_chunk",
299
+ content: { type: "text", text: "Goodbye" },
300
+ };
301
+ },
302
+ } as any);
303
+
304
+ await macroAgent.prompt({
305
+ sessionId,
306
+ prompt: [{ type: "text", text: "Bye" }],
307
+ });
308
+
309
+ const response = await macroAgent.extMethod("_macro/getHistory", {
310
+ sessionId,
311
+ });
312
+
313
+ const turns = (response as { turns: { role: string; content: unknown }[] })
314
+ .turns;
315
+
316
+ // 2 prompts × 2 turns each = 4 turns total
317
+ expect(turns).toHaveLength(4);
318
+ expect(turns[0].role).toBe("user");
319
+ expect(turns[0].content).toBe("Hi");
320
+ expect(turns[1].role).toBe("assistant");
321
+ expect(turns[2].role).toBe("user");
322
+ expect(turns[2].content).toBe("Bye");
323
+ expect(turns[3].role).toBe("assistant");
324
+ });
325
+
326
+ it("should respect the limit parameter", async () => {
327
+ const { agentManager } = await setup([
328
+ {
329
+ sessionUpdate: "agent_message_chunk",
330
+ content: { type: "text", text: "Response 1" },
331
+ },
332
+ ]);
333
+
334
+ await macroAgent.newSession({ cwd: "/test" });
335
+ const sessionId =
336
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
337
+
338
+ // Send 3 prompts
339
+ for (let i = 0; i < 3; i++) {
340
+ vi.mocked(agentManager.prompt).mockReturnValue({
341
+ [Symbol.asyncIterator]: async function* () {
342
+ yield {
343
+ sessionUpdate: "agent_message_chunk",
344
+ content: { type: "text", text: `Response ${i + 1}` },
345
+ };
346
+ },
347
+ } as any);
348
+
349
+ await macroAgent.prompt({
350
+ sessionId,
351
+ prompt: [{ type: "text", text: `Message ${i + 1}` }],
352
+ });
353
+ }
354
+
355
+ // Request only 2 turns
356
+ const response = await macroAgent.extMethod("_macro/getHistory", {
357
+ sessionId,
358
+ limit: 2,
359
+ });
360
+
361
+ const turns = (response as { turns: unknown[] }).turns;
362
+ expect(turns).toHaveLength(2);
363
+ });
364
+
365
+ it("should not record turns when prompt has no text content", async () => {
366
+ await setup([
367
+ {
368
+ sessionUpdate: "agent_message_chunk",
369
+ content: { type: "text", text: "Response" },
370
+ },
371
+ ]);
372
+
373
+ await macroAgent.newSession({ cwd: "/test" });
374
+ const sessionId =
375
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
376
+
377
+ // Empty prompt — no text blocks
378
+ await macroAgent.prompt({
379
+ sessionId,
380
+ prompt: [],
381
+ });
382
+
383
+ const response = await macroAgent.extMethod("_macro/getHistory", {
384
+ sessionId,
385
+ });
386
+
387
+ const turns = (response as { turns: { role: string }[] }).turns;
388
+
389
+ // Should only have the assistant turn (no user turn since message was empty)
390
+ expect(turns).toHaveLength(1);
391
+ expect(turns[0].role).toBe("assistant");
392
+ });
393
+
394
+ it("should only record completed tool calls, not running ones", async () => {
395
+ await setup([
396
+ {
397
+ sessionUpdate: "tool_call",
398
+ toolCallId: "tc-running",
399
+ title: "Running tool",
400
+ status: "running",
401
+ rawInput: {},
402
+ },
403
+ {
404
+ sessionUpdate: "tool_call",
405
+ toolCallId: "tc-done",
406
+ title: "Done tool",
407
+ status: "completed",
408
+ rawInput: { x: 1 },
409
+ output: "result",
410
+ },
411
+ ]);
412
+
413
+ await macroAgent.newSession({ cwd: "/test" });
414
+ const sessionId =
415
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
416
+
417
+ await macroAgent.prompt({
418
+ sessionId,
419
+ prompt: [{ type: "text", text: "Run tools" }],
420
+ });
421
+
422
+ const response = await macroAgent.extMethod("_macro/getHistory", {
423
+ sessionId,
424
+ });
425
+
426
+ const turns = (response as { turns: { role: string; content: unknown }[] })
427
+ .turns;
428
+
429
+ const assistantContent = turns[1].content as {
430
+ parts: { type: string; toolCallId?: string }[];
431
+ };
432
+
433
+ // Only the completed tool call should be recorded
434
+ const toolParts = assistantContent.parts.filter((p) => p.type === "tool");
435
+ expect(toolParts).toHaveLength(1);
436
+ expect(toolParts[0].toolCallId).toBe("tc-done");
437
+ });
438
+
439
+ it("should return turns ordered by timestamp (ascending)", async () => {
440
+ const { agentManager } = await setup([
441
+ {
442
+ sessionUpdate: "agent_message_chunk",
443
+ content: { type: "text", text: "First" },
444
+ },
445
+ ]);
446
+
447
+ await macroAgent.newSession({ cwd: "/test" });
448
+ const sessionId =
449
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
450
+
451
+ await macroAgent.prompt({
452
+ sessionId,
453
+ prompt: [{ type: "text", text: "Q1" }],
454
+ });
455
+
456
+ // Small delay to ensure distinct timestamps
457
+ await new Promise((r) => setTimeout(r, 5));
458
+
459
+ vi.mocked(agentManager.prompt).mockReturnValue({
460
+ [Symbol.asyncIterator]: async function* () {
461
+ yield {
462
+ sessionUpdate: "agent_message_chunk",
463
+ content: { type: "text", text: "Second" },
464
+ };
465
+ },
466
+ } as any);
467
+
468
+ await macroAgent.prompt({
469
+ sessionId,
470
+ prompt: [{ type: "text", text: "Q2" }],
471
+ });
472
+
473
+ const response = await macroAgent.extMethod("_macro/getHistory", {
474
+ sessionId,
475
+ });
476
+
477
+ const turns = (
478
+ response as { turns: { timestamp: number; content: unknown }[] }
479
+ ).turns;
480
+
481
+ // Verify timestamps are in ascending order
482
+ for (let i = 1; i < turns.length; i++) {
483
+ expect(turns[i].timestamp).toBeGreaterThanOrEqual(turns[i - 1].timestamp);
484
+ }
485
+ });
486
+
487
+ it("should isolate history between different sessions", async () => {
488
+ await setup([
489
+ {
490
+ sessionUpdate: "agent_message_chunk",
491
+ content: { type: "text", text: "Session 1 response" },
492
+ },
493
+ ]);
494
+
495
+ // Create first session and prompt
496
+ await macroAgent.newSession({ cwd: "/test" });
497
+ const session1Id =
498
+ macroAgent.getSessionMapper().getAllMappings()[0]?.acpSessionId;
499
+
500
+ await macroAgent.prompt({
501
+ sessionId: session1Id,
502
+ prompt: [{ type: "text", text: "Session 1 message" }],
503
+ });
504
+
505
+ // Create second session
506
+ await macroAgent.newSession({ cwd: "/test2" });
507
+ const allMappings = macroAgent.getSessionMapper().getAllMappings();
508
+ const session2Id = allMappings.find(
509
+ (m) => m.acpSessionId !== session1Id
510
+ )?.acpSessionId;
511
+
512
+ // Session 2 should have no history
513
+ const response = await macroAgent.extMethod("_macro/getHistory", {
514
+ sessionId: session2Id,
515
+ });
516
+
517
+ const turns = (response as { turns: unknown[] }).turns;
518
+ expect(turns).toHaveLength(0);
519
+
520
+ // Session 1 should still have its history
521
+ const response1 = await macroAgent.extMethod("_macro/getHistory", {
522
+ sessionId: session1Id,
523
+ });
524
+ expect((response1 as { turns: unknown[] }).turns).toHaveLength(2);
525
+ });
526
+ });
@@ -372,7 +372,8 @@ describe("ACP Mode Integration", () => {
372
372
  expect(extensions).toContain("_macro/respondToPermission");
373
373
  expect(extensions).toContain("_macro/cancelPermission");
374
374
  expect(extensions).toContain("_macro/resume");
375
- expect(extensions?.length).toBe(16);
375
+ expect(extensions).toContain("_macro/getHistory");
376
+ expect(extensions?.length).toBe(17);
376
377
 
377
378
  expect(initResponse.agentCapabilities?._meta?.agentType).toBe(
378
379
  "macro-agent"
package/src/acp/index.ts CHANGED
@@ -95,6 +95,10 @@ export type {
95
95
  MountAgentResponse,
96
96
  ForkAgentRequest,
97
97
  ForkAgentResponse,
98
+ // History types
99
+ HistoryTurn,
100
+ GetHistoryRequest,
101
+ GetHistoryResponse,
98
102
  // Union types
99
103
  ACPExtensionMethod,
100
104
  ACPExtensionRequests,