@ai-devkit/agent-manager 0.34.0 → 0.36.0

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 (211) hide show
  1. package/dist/AgentManager.d.ts +2 -0
  2. package/dist/AgentManager.d.ts.map +1 -1
  3. package/dist/AgentManager.js +25 -0
  4. package/dist/AgentManager.js.map +1 -1
  5. package/dist/__tests__/AgentManager.test.js +74 -0
  6. package/dist/__tests__/AgentManager.test.js.map +1 -1
  7. package/dist/__tests__/adapters/AntigravityCliAdapter.test.js +381 -0
  8. package/dist/__tests__/adapters/AntigravityCliAdapter.test.js.map +1 -0
  9. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +28 -0
  10. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
  11. package/dist/__tests__/adapters/CodexAdapter.test.js +7 -0
  12. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
  13. package/dist/__tests__/adapters/KiroAdapter.test.js +607 -0
  14. package/dist/__tests__/adapters/KiroAdapter.test.js.map +1 -0
  15. package/dist/__tests__/capacity/claude.test.js +485 -0
  16. package/dist/__tests__/capacity/claude.test.js.map +1 -0
  17. package/dist/__tests__/capacity/openai.test.js +672 -0
  18. package/dist/__tests__/capacity/openai.test.js.map +1 -0
  19. package/dist/__tests__/capacity/wham.test.js +144 -0
  20. package/dist/__tests__/capacity/wham.test.js.map +1 -0
  21. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +18 -0
  22. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
  23. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
  24. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
  25. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
  26. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
  27. package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
  28. package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
  29. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
  30. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
  31. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +24 -0
  32. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -1
  33. package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
  34. package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -1
  35. package/dist/__tests__/utils/agents.test.js +6 -0
  36. package/dist/__tests__/utils/agents.test.js.map +1 -1
  37. package/dist/__tests__/utils/session.test.js +18 -1
  38. package/dist/__tests__/utils/session.test.js.map +1 -1
  39. package/dist/adapters/AgentAdapter.d.ts +9 -1
  40. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  41. package/dist/adapters/AgentAdapter.js.map +1 -1
  42. package/dist/adapters/AntigravityCliAdapter.d.ts +57 -0
  43. package/dist/adapters/AntigravityCliAdapter.d.ts.map +1 -0
  44. package/dist/adapters/AntigravityCliAdapter.js +273 -0
  45. package/dist/adapters/AntigravityCliAdapter.js.map +1 -0
  46. package/dist/adapters/KiroAdapter.d.ts +46 -0
  47. package/dist/adapters/KiroAdapter.d.ts.map +1 -0
  48. package/dist/adapters/KiroAdapter.js +344 -0
  49. package/dist/adapters/KiroAdapter.js.map +1 -0
  50. package/dist/adapters/index.d.ts +3 -1
  51. package/dist/adapters/index.d.ts.map +1 -1
  52. package/dist/adapters/index.js +3 -1
  53. package/dist/adapters/index.js.map +1 -1
  54. package/dist/capacity/claude.d.ts +17 -0
  55. package/dist/capacity/claude.d.ts.map +1 -0
  56. package/dist/capacity/claude.js +271 -0
  57. package/dist/capacity/claude.js.map +1 -0
  58. package/dist/capacity/codex.d.ts +1 -2
  59. package/dist/capacity/codex.d.ts.map +1 -1
  60. package/dist/capacity/codex.js +5 -47
  61. package/dist/capacity/codex.js.map +1 -1
  62. package/dist/capacity/index.d.ts +11 -0
  63. package/dist/capacity/index.d.ts.map +1 -1
  64. package/dist/capacity/index.js +17 -0
  65. package/dist/capacity/index.js.map +1 -1
  66. package/dist/capacity/openai.d.ts +48 -0
  67. package/dist/capacity/openai.d.ts.map +1 -0
  68. package/dist/capacity/openai.js +312 -0
  69. package/dist/capacity/openai.js.map +1 -0
  70. package/dist/capacity/wham.d.ts +16 -0
  71. package/dist/capacity/wham.d.ts.map +1 -0
  72. package/dist/capacity/wham.js +66 -0
  73. package/dist/capacity/wham.js.map +1 -0
  74. package/dist/index.d.ts +5 -3
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +4 -2
  77. package/dist/index.js.map +1 -1
  78. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
  79. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
  80. package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
  81. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
  82. package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
  83. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
  84. package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
  85. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
  86. package/dist/providers/codex/CodexAdapter.d.ts +1 -0
  87. package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
  88. package/dist/providers/codex/CodexAdapter.js +8 -0
  89. package/dist/providers/codex/CodexAdapter.js.map +1 -1
  90. package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
  91. package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
  92. package/dist/providers/copilot/CopilotAdapter.js +24 -9
  93. package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
  94. package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
  95. package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
  96. package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
  97. package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
  98. package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
  99. package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
  100. package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
  101. package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
  102. package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
  103. package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
  104. package/dist/providers/grok/GrokAgentMapper.js +36 -0
  105. package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
  106. package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
  107. package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
  108. package/dist/providers/grok/GrokCliAdapter.js +98 -0
  109. package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
  110. package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
  111. package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
  112. package/dist/providers/grok/GrokSessionLocator.js +140 -0
  113. package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
  114. package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
  115. package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
  116. package/dist/providers/grok/GrokSessionParser.js +122 -0
  117. package/dist/providers/grok/GrokSessionParser.js.map +1 -0
  118. package/dist/providers/opencode/OpenCodeAdapter.d.ts +1 -0
  119. package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -1
  120. package/dist/providers/opencode/OpenCodeAdapter.js +3 -0
  121. package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -1
  122. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +2 -0
  123. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -1
  124. package/dist/providers/opencode/OpenCodeSessionLocator.js +31 -12
  125. package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -1
  126. package/dist/providers/pi/PiAdapter.d.ts +1 -0
  127. package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
  128. package/dist/providers/pi/PiAdapter.js +10 -0
  129. package/dist/providers/pi/PiAdapter.js.map +1 -1
  130. package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
  131. package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
  132. package/dist/providers/pi/PiSessionLocator.js +8 -1
  133. package/dist/providers/pi/PiSessionLocator.js.map +1 -1
  134. package/dist/readiness/AgentReadiness.d.ts +1 -1
  135. package/dist/readiness/AgentReadiness.d.ts.map +1 -1
  136. package/dist/readiness/AgentReadiness.js.map +1 -1
  137. package/dist/runtime/ManagedAgentRuntime.js +3 -1
  138. package/dist/runtime/ManagedAgentRuntime.js.map +1 -1
  139. package/dist/runtime/herdr/HerdrAgentDiscovery.js +10 -0
  140. package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -1
  141. package/dist/utils/agents.d.ts +1 -1
  142. package/dist/utils/agents.d.ts.map +1 -1
  143. package/dist/utils/agents.js +11 -0
  144. package/dist/utils/agents.js.map +1 -1
  145. package/dist/utils/session.d.ts +2 -0
  146. package/dist/utils/session.d.ts.map +1 -1
  147. package/dist/utils/session.js +3 -0
  148. package/dist/utils/session.js.map +1 -1
  149. package/package.json +1 -1
  150. package/src/AgentManager.ts +34 -0
  151. package/src/__tests__/AgentManager.test.ts +60 -0
  152. package/src/__tests__/adapters/AntigravityCliAdapter.test.ts +324 -0
  153. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
  154. package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
  155. package/src/__tests__/adapters/KiroAdapter.test.ts +534 -0
  156. package/src/__tests__/capacity/claude.test.ts +467 -0
  157. package/src/__tests__/capacity/fixtures/claude-credentials.json +7 -0
  158. package/src/__tests__/capacity/fixtures/claude-usage-malformed.json +1 -0
  159. package/src/__tests__/capacity/fixtures/claude-usage-partial.json +15 -0
  160. package/src/__tests__/capacity/fixtures/claude-usage.json +63 -0
  161. package/src/__tests__/capacity/fixtures/openai-auth.json +6 -0
  162. package/src/__tests__/capacity/fixtures/openai-codex-auth.json +9 -0
  163. package/src/__tests__/capacity/fixtures/openai-usage-completions.json +67 -0
  164. package/src/__tests__/capacity/fixtures/openai-usage-responses.json +20 -0
  165. package/src/__tests__/capacity/fixtures/wham-usage.json +32 -0
  166. package/src/__tests__/capacity/openai.test.ts +570 -0
  167. package/src/__tests__/capacity/wham.test.ts +104 -0
  168. package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
  169. package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
  170. package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
  171. package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
  172. package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
  173. package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +18 -0
  174. package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
  175. package/src/__tests__/utils/agents.test.ts +7 -0
  176. package/src/__tests__/utils/session.test.ts +11 -1
  177. package/src/adapters/AgentAdapter.ts +11 -0
  178. package/src/adapters/AntigravityCliAdapter.ts +356 -0
  179. package/src/adapters/KiroAdapter.ts +456 -0
  180. package/src/adapters/index.ts +3 -1
  181. package/src/capacity/claude.ts +313 -0
  182. package/src/capacity/codex.ts +25 -134
  183. package/src/capacity/index.ts +33 -4
  184. package/src/capacity/openai.ts +361 -0
  185. package/src/capacity/wham.ts +84 -0
  186. package/src/index.ts +11 -2
  187. package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
  188. package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
  189. package/src/providers/codex/CodexAdapter.ts +8 -0
  190. package/src/providers/copilot/CopilotAdapter.ts +26 -10
  191. package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
  192. package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
  193. package/src/providers/grok/GrokAgentMapper.ts +41 -0
  194. package/src/providers/grok/GrokCliAdapter.ts +125 -0
  195. package/src/providers/grok/GrokSessionLocator.ts +179 -0
  196. package/src/providers/grok/GrokSessionParser.ts +166 -0
  197. package/src/providers/opencode/OpenCodeAdapter.ts +4 -0
  198. package/src/providers/opencode/OpenCodeSessionLocator.ts +43 -15
  199. package/src/providers/pi/PiAdapter.ts +12 -0
  200. package/src/providers/pi/PiSessionLocator.ts +16 -1
  201. package/src/readiness/AgentReadiness.ts +4 -1
  202. package/src/runtime/ManagedAgentRuntime.ts +2 -0
  203. package/src/runtime/herdr/HerdrAgentDiscovery.ts +5 -1
  204. package/src/utils/agents.ts +11 -1
  205. package/src/utils/session.ts +11 -0
  206. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
  207. package/dist/adapters/GrokCliAdapter.d.ts +0 -80
  208. package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
  209. package/dist/adapters/GrokCliAdapter.js +0 -310
  210. package/dist/adapters/GrokCliAdapter.js.map +0 -1
  211. package/src/adapters/GrokCliAdapter.ts +0 -408
@@ -21,6 +21,7 @@ import type { HerdrAgentPane } from "../runtime/herdr/HerdrAgentDiscovery.js";
21
21
  // Mock adapter for testing
22
22
  class MockAdapter implements AgentAdapter {
23
23
  public lastListSessionsOpts: unknown = undefined;
24
+ public findSessionsByIdCalls: string[] = [];
24
25
 
25
26
  constructor(
26
27
  public readonly type: AgentType,
@@ -53,6 +54,11 @@ class MockAdapter implements AgentAdapter {
53
54
  return this.mockSessions;
54
55
  }
55
56
 
57
+ async findSessionsById(sessionId: string): Promise<SessionSummary[]> {
58
+ this.findSessionsByIdCalls.push(sessionId);
59
+ return this.mockSessions.filter((session) => session.sessionId === sessionId);
60
+ }
61
+
56
62
  setAgents(agents: AgentInfo[]): void {
57
63
  this.mockAgents = agents;
58
64
  }
@@ -1119,6 +1125,60 @@ describe("AgentManager", () => {
1119
1125
  });
1120
1126
  });
1121
1127
 
1128
+ describe("findSessionsById", () => {
1129
+ const session = (type: AgentType, sessionId: string): SessionSummary => ({
1130
+ type,
1131
+ sessionId,
1132
+ cwd: "/repo",
1133
+ firstUserMessage: "hello",
1134
+ lastActive: new Date("2025-01-01T00:00:00Z"),
1135
+ startedAt: new Date("2025-01-01T00:00:00Z"),
1136
+ sessionFilePath: `/tmp/${sessionId}`,
1137
+ });
1138
+
1139
+ it("uses each built-in adapter direct lookup and preserves cross-provider ambiguity", async () => {
1140
+ const claude = new MockAdapter("claude", [], false, [session("claude", "shared")]);
1141
+ const codex = new MockAdapter("codex", [], false, [session("codex", "shared")]);
1142
+ manager.registerAdapter(claude);
1143
+ manager.registerAdapter(codex);
1144
+
1145
+ const result = await manager.findSessionsById("shared");
1146
+
1147
+ expect(result).toHaveLength(2);
1148
+ expect(claude.findSessionsByIdCalls).toEqual(["shared"]);
1149
+ expect(codex.findSessionsByIdCalls).toEqual(["shared"]);
1150
+ expect(claude.lastListSessionsOpts).toBeUndefined();
1151
+ expect(codex.lastListSessionsOpts).toBeUndefined();
1152
+ });
1153
+
1154
+ it("skips non-matching providers when type is supplied", async () => {
1155
+ const claude = new MockAdapter("claude", [], false, [session("claude", "target")]);
1156
+ const codex = new MockAdapter("codex", [], false, [session("codex", "target")]);
1157
+ manager.registerAdapter(claude);
1158
+ manager.registerAdapter(codex);
1159
+
1160
+ const result = await manager.findSessionsById("target", { type: "codex" });
1161
+
1162
+ expect(result).toEqual([expect.objectContaining({ type: "codex", sessionId: "target" })]);
1163
+ expect(claude.findSessionsByIdCalls).toEqual([]);
1164
+ expect(codex.findSessionsByIdCalls).toEqual(["target"]);
1165
+ });
1166
+
1167
+ it("falls back to filtered listing for external adapters without direct lookup", async () => {
1168
+ const external = new MockAdapter("other", [], false, [
1169
+ session("other", "target"),
1170
+ session("other", "different"),
1171
+ ]);
1172
+ (external as Partial<AgentAdapter>).findSessionsById = undefined;
1173
+ manager.registerAdapter(external);
1174
+
1175
+ const result = await manager.findSessionsById("target");
1176
+
1177
+ expect(result).toEqual([expect.objectContaining({ sessionId: "target" })]);
1178
+ expect(external.lastListSessionsOpts).toEqual(undefined);
1179
+ });
1180
+ });
1181
+
1122
1182
  describe("resolveAgent", () => {
1123
1183
  it("should return null for empty input or empty agents list", () => {
1124
1184
  const agent = createMockAgent({ name: "test-agent" });
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Tests for AntigravityCliAdapter
3
+ *
4
+ * The adapter resolves a live `agy` process to its conversation via
5
+ * ~/.gemini/antigravity-cli/cache/last_conversations.json (cwd -> conversationId)
6
+ * and reads session details from brain/<id>/.system_generated/logs/transcript.jsonl.
7
+ */
8
+
9
+ import type { MockedFunction } from "vitest";
10
+ import * as fs from "fs";
11
+ import * as os from "os";
12
+ import * as path from "path";
13
+
14
+ import { AntigravityCliAdapter } from "../../adapters/AntigravityCliAdapter.js";
15
+ import type { ProcessInfo } from "../../adapters/AgentAdapter.js";
16
+ import { AgentStatus } from "../../adapters/AgentAdapter.js";
17
+ import { listAgentProcesses, enrichProcesses } from "../../utils/process.js";
18
+ import { generateAgentName } from "../../utils/matching.js";
19
+
20
+ vi.mock("../../utils/process.js", async (importOriginal) => {
21
+ const actual = (await importOriginal()) as typeof import("../../utils/process.js");
22
+ return {
23
+ ...actual,
24
+ listAgentProcesses: vi.fn(),
25
+ enrichProcesses: vi.fn(),
26
+ };
27
+ });
28
+
29
+ vi.mock("../../utils/matching.js", async (importOriginal) => {
30
+ const actual = (await importOriginal()) as typeof import("../../utils/matching.js");
31
+ return {
32
+ ...actual,
33
+ generateAgentName: vi.fn(),
34
+ };
35
+ });
36
+
37
+ const mockedListAgentProcesses = listAgentProcesses as MockedFunction<typeof listAgentProcesses>;
38
+ const mockedEnrichProcesses = enrichProcesses as MockedFunction<typeof enrichProcesses>;
39
+ const mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;
40
+
41
+ const CONVERSATION_ID = "10485e13-2742-4e9e-b286-ac0606f0cb1e";
42
+
43
+ const iso = (d: Date) => d.toISOString().replace(/\.\d+Z$/, "Z");
44
+ /** A user transcript record: real prompt wrapped in <USER_REQUEST> like agy writes it. */
45
+ const userRecord = (text: string, at?: Date) => ({
46
+ source: "USER_EXPLICIT",
47
+ type: "USER_INPUT",
48
+ created_at: iso(at ?? new Date()),
49
+ content: `<USER_REQUEST>\n${text}\n</USER_REQUEST>\n<ADDITIONAL_CONTEXT>ignored</ADDITIONAL_CONTEXT>`,
50
+ });
51
+ const modelRecord = (text: string, at?: Date) => ({
52
+ source: "MODEL",
53
+ type: "PLANNER_RESPONSE",
54
+ created_at: iso(at ?? new Date()),
55
+ content: text,
56
+ });
57
+ /** A MODEL tool-call record (e.g. RUN_COMMAND) — execution detail, not a reply. */
58
+ const toolRecord = (text: string, at?: Date) => ({
59
+ source: "MODEL",
60
+ type: "RUN_COMMAND",
61
+ created_at: iso(at ?? new Date()),
62
+ content: text,
63
+ });
64
+ const systemRecord = (text: string, at?: Date) => ({
65
+ source: "SYSTEM",
66
+ type: "CHECKPOINT",
67
+ created_at: iso(at ?? new Date()),
68
+ content: text,
69
+ });
70
+
71
+ describe("AntigravityCliAdapter", () => {
72
+ let adapter: AntigravityCliAdapter;
73
+ let base: string;
74
+ let cwd: string;
75
+
76
+ beforeEach(() => {
77
+ base = fs.mkdtempSync(path.join(os.tmpdir(), "agy-adapter-test-"));
78
+ process.env.ANTIGRAVITY_CLI_HOME = base;
79
+ cwd = "/Users/dev/my-project";
80
+
81
+ adapter = new AntigravityCliAdapter();
82
+
83
+ mockedListAgentProcesses.mockReset();
84
+ mockedEnrichProcesses.mockReset();
85
+ mockedGenerateAgentName.mockReset();
86
+
87
+ mockedEnrichProcesses.mockImplementation((procs) => procs);
88
+ mockedGenerateAgentName.mockImplementation(
89
+ (c: string, pid: number) => `${path.basename(c) || "unknown"}-${pid}`,
90
+ );
91
+ });
92
+
93
+ afterEach(() => {
94
+ delete process.env.ANTIGRAVITY_CLI_HOME;
95
+ fs.rmSync(base, { recursive: true, force: true });
96
+ });
97
+
98
+ /** Write brain/<id>/.system_generated/logs/transcript.jsonl. */
99
+ function writeTranscript(opts: {
100
+ id?: string;
101
+ records?: object[];
102
+ transcript?: boolean;
103
+ }): string {
104
+ const id = opts.id ?? CONVERSATION_ID;
105
+ const dir = path.join(base, "brain", id, ".system_generated", "logs");
106
+ fs.mkdirSync(dir, { recursive: true });
107
+ if (opts.transcript !== false) {
108
+ const records = opts.records ?? [userRecord("fix the bug")];
109
+ fs.writeFileSync(
110
+ path.join(dir, "transcript.jsonl"),
111
+ records.map((r) => JSON.stringify(r)).join("\n"),
112
+ );
113
+ }
114
+ return path.join(dir, "transcript.jsonl");
115
+ }
116
+
117
+ /** Write cache/last_conversations.json (cwd -> conversationId). */
118
+ function writeRegistry(map: Record<string, string>): void {
119
+ const dir = path.join(base, "cache");
120
+ fs.mkdirSync(dir, { recursive: true });
121
+ fs.writeFileSync(path.join(dir, "last_conversations.json"), JSON.stringify(map));
122
+ }
123
+
124
+ function proc(overrides: Partial<ProcessInfo> = {}): ProcessInfo {
125
+ return {
126
+ pid: 4242,
127
+ ppid: 1,
128
+ command: "agy",
129
+ cwd,
130
+ tty: "ttys010",
131
+ startTime: new Date(),
132
+ ...overrides,
133
+ };
134
+ }
135
+
136
+ describe("initialization", () => {
137
+ it("exposes the antigravity_cli type", () => {
138
+ expect(adapter.type).toBe("antigravity_cli");
139
+ });
140
+ });
141
+
142
+ describe("canHandle", () => {
143
+ it("returns true for a plain agy command", () => {
144
+ expect(adapter.canHandle(proc({ command: "agy" }))).toBe(true);
145
+ });
146
+
147
+ it("returns true for agy with a full path and args", () => {
148
+ expect(
149
+ adapter.canHandle(
150
+ proc({ command: "/Users/dev/.local/bin/agy --dangerously-skip-permissions" }),
151
+ ),
152
+ ).toBe(true);
153
+ });
154
+
155
+ it("returns false for non-agy processes", () => {
156
+ expect(adapter.canHandle(proc({ command: "node app.js" }))).toBe(false);
157
+ });
158
+
159
+ it('returns false when "agy" appears only in an argument path', () => {
160
+ expect(adapter.canHandle(proc({ command: "node /path/to/agy-thing.js" }))).toBe(false);
161
+ });
162
+ });
163
+
164
+ describe("detectAgents", () => {
165
+ it("returns [] when there are no agy processes", async () => {
166
+ mockedListAgentProcesses.mockReturnValue([]);
167
+ expect(await adapter.detectAgents()).toEqual([]);
168
+ });
169
+
170
+ it("resolves the conversation via last_conversations.json (cwd -> id)", async () => {
171
+ writeTranscript({});
172
+ writeRegistry({ [cwd]: CONVERSATION_ID });
173
+ mockedListAgentProcesses.mockReturnValue([proc()]);
174
+
175
+ const agents = await adapter.detectAgents();
176
+
177
+ expect(agents).toHaveLength(1);
178
+ expect(agents[0]).toMatchObject({
179
+ type: "antigravity_cli",
180
+ pid: 4242,
181
+ projectPath: cwd,
182
+ sessionId: CONVERSATION_ID,
183
+ summary: "fix the bug",
184
+ });
185
+ expect(agents[0].sessionFilePath).toBe(
186
+ path.join(base, "brain", CONVERSATION_ID, ".system_generated", "logs", "transcript.jsonl"),
187
+ );
188
+ });
189
+
190
+ it("falls back to a process-only RUNNING agent when the cwd is not in the registry", async () => {
191
+ writeTranscript({});
192
+ writeRegistry({ "/some/other/cwd": CONVERSATION_ID });
193
+ mockedListAgentProcesses.mockReturnValue([proc()]);
194
+
195
+ const agents = await adapter.detectAgents();
196
+
197
+ expect(agents).toHaveLength(1);
198
+ expect(agents[0].status).toBe(AgentStatus.RUNNING);
199
+ expect(agents[0].sessionId).toBe("pid-4242");
200
+ expect(agents[0].sessionFilePath).toBeUndefined();
201
+ });
202
+
203
+ it("is process-only when the mapped conversation has no transcript", async () => {
204
+ writeTranscript({ transcript: false });
205
+ writeRegistry({ [cwd]: CONVERSATION_ID });
206
+ mockedListAgentProcesses.mockReturnValue([proc()]);
207
+
208
+ const agents = await adapter.detectAgents();
209
+
210
+ expect(agents[0].sessionId).toBe("pid-4242");
211
+ });
212
+ });
213
+
214
+ describe("getConversation", () => {
215
+ it("maps USER_INPUT (<USER_REQUEST>) and MODEL records to roles", () => {
216
+ const file = writeTranscript({ records: [userRecord("hi"), modelRecord("hello")] });
217
+ expect(adapter.getConversation(file)).toEqual([
218
+ { role: "user", content: "hi" },
219
+ { role: "assistant", content: "hello" },
220
+ ]);
221
+ });
222
+
223
+ it("accepts a bare conversation id and skips malformed lines", () => {
224
+ const file = writeTranscript({ records: [userRecord("hi")] });
225
+ fs.appendFileSync(file, "\n{bad json");
226
+ expect(adapter.getConversation(CONVERSATION_ID)).toEqual([{ role: "user", content: "hi" }]);
227
+ });
228
+
229
+ it("excludes SYSTEM records unless verbose", () => {
230
+ const file = writeTranscript({ records: [systemRecord("checkpoint"), userRecord("go")] });
231
+ expect(adapter.getConversation(file)).toEqual([{ role: "user", content: "go" }]);
232
+ expect(adapter.getConversation(file, { verbose: true }).map((m) => m.role)).toEqual([
233
+ "system",
234
+ "user",
235
+ ]);
236
+ });
237
+
238
+ it("excludes MODEL tool-call (RUN_COMMAND) records except in verbose", () => {
239
+ const file = writeTranscript({
240
+ records: [userRecord("go"), toolRecord("ran: echo hi"), modelRecord("done")],
241
+ });
242
+ // A tool call is not an assistant reply; only USER_INPUT + PLANNER_RESPONSE show.
243
+ expect(adapter.getConversation(file)).toEqual([
244
+ { role: "user", content: "go" },
245
+ { role: "assistant", content: "done" },
246
+ ]);
247
+ expect(adapter.getConversation(file, { verbose: true }).map((m) => m.role)).toEqual([
248
+ "user",
249
+ "system",
250
+ "assistant",
251
+ ]);
252
+ });
253
+ });
254
+
255
+ describe("detectAgents status + summary mapping", () => {
256
+ const detectFirst = async () => (await adapter.detectAgents())[0];
257
+
258
+ it("marks WAITING when the last reply is a PLANNER_RESPONSE (after a tool call)", async () => {
259
+ writeTranscript({
260
+ records: [userRecord("go"), toolRecord("ran: echo hi"), modelRecord("done")],
261
+ });
262
+ writeRegistry({ [cwd]: CONVERSATION_ID });
263
+ mockedListAgentProcesses.mockReturnValue([proc()]);
264
+ expect((await detectFirst()).status).toBe(AgentStatus.WAITING);
265
+ });
266
+
267
+ it("marks RUNNING when the last transcript turn is a USER_INPUT message", async () => {
268
+ writeTranscript({ records: [userRecord("still there?")] });
269
+ writeRegistry({ [cwd]: CONVERSATION_ID });
270
+ mockedListAgentProcesses.mockReturnValue([proc()]);
271
+ expect((await detectFirst()).status).toBe(AgentStatus.RUNNING);
272
+ });
273
+
274
+ it("marks IDLE when the last created_at is older than the threshold", async () => {
275
+ const old = new Date(Date.now() - 10 * 60 * 1000);
276
+ writeTranscript({ records: [userRecord("go", old)] });
277
+ writeRegistry({ [cwd]: CONVERSATION_ID });
278
+ mockedListAgentProcesses.mockReturnValue([proc()]);
279
+ expect((await detectFirst()).status).toBe(AgentStatus.IDLE);
280
+ });
281
+
282
+ it("uses the last user request as the agent summary", async () => {
283
+ writeTranscript({ records: [userRecord("refactor the parser"), modelRecord("on it")] });
284
+ writeRegistry({ [cwd]: CONVERSATION_ID });
285
+ mockedListAgentProcesses.mockReturnValue([proc()]);
286
+ expect((await detectFirst()).summary).toBe("refactor the parser");
287
+ });
288
+ });
289
+
290
+ describe("listSessions", () => {
291
+ it("returns [] when there is no registry", async () => {
292
+ expect(await adapter.listSessions()).toEqual([]);
293
+ });
294
+
295
+ it("lists sessions from the registry with cwd + first user message", async () => {
296
+ writeTranscript({});
297
+ writeRegistry({ [cwd]: CONVERSATION_ID });
298
+ const summaries = await adapter.listSessions();
299
+ expect(summaries).toHaveLength(1);
300
+ expect(summaries[0]).toMatchObject({
301
+ type: "antigravity_cli",
302
+ sessionId: CONVERSATION_ID,
303
+ cwd,
304
+ firstUserMessage: "fix the bug",
305
+ });
306
+ });
307
+
308
+ it("applies the cwd filter", async () => {
309
+ writeTranscript({ id: "aaa0000a-0000-4000-8000-00000000000a", records: [userRecord("a")] });
310
+ writeTranscript({ id: "bbb0000b-0000-4000-8000-00000000000b", records: [userRecord("b")] });
311
+ writeRegistry({
312
+ "/Users/dev/project-a": "aaa0000a-0000-4000-8000-00000000000a",
313
+ "/Users/dev/project-b": "bbb0000b-0000-4000-8000-00000000000b",
314
+ });
315
+
316
+ const all = await adapter.listSessions();
317
+ expect(all).toHaveLength(2);
318
+
319
+ const filtered = await adapter.listSessions({ cwd: "/Users/dev/project-a" });
320
+ expect(filtered).toHaveLength(1);
321
+ expect(filtered[0].cwd).toBe("/Users/dev/project-a");
322
+ });
323
+ });
324
+ });
@@ -1624,6 +1624,29 @@ describe("ClaudeCodeAdapter", () => {
1624
1624
  expect(cwds).toEqual([cwdA, cwdB]);
1625
1625
  });
1626
1626
 
1627
+ it("finds an exact session ID without returning other project sessions", async () => {
1628
+ const wantedPath = writeSession(path.join(projectsDir, "-repo-a"), "wanted", [
1629
+ {
1630
+ type: "user",
1631
+ timestamp: "2025-01-01T00:00:00Z",
1632
+ cwd: "/repo-a",
1633
+ message: { content: "wanted prompt" },
1634
+ },
1635
+ ]);
1636
+ writeSession(path.join(projectsDir, "-repo-b"), "other", [
1637
+ {
1638
+ type: "user",
1639
+ timestamp: "2025-01-01T00:00:00Z",
1640
+ cwd: "/repo-b",
1641
+ message: { content: "other prompt" },
1642
+ },
1643
+ ]);
1644
+
1645
+ await expect(adapter.findSessionsById("wanted")).resolves.toEqual([
1646
+ expect.objectContaining({ sessionId: "wanted", sessionFilePath: wantedPath }),
1647
+ ]);
1648
+ });
1649
+
1627
1650
  it("drops sessions whose recorded cwd does not match opts.cwd (strict equality)", async () => {
1628
1651
  const cwdReal = "/Users/test/foo";
1629
1652
  const cwdRequested = "/Users/test/foo/sub";
@@ -339,6 +339,13 @@ describe("CodexAdapter", () => {
339
339
  sessionFilePath: sessionFile,
340
340
  },
341
341
  ]);
342
+ await expect(adapter.findSessionsById("listed")).resolves.toMatchObject([
343
+ {
344
+ type: "codex",
345
+ sessionId: "listed",
346
+ sessionFilePath: sessionFile,
347
+ },
348
+ ]);
342
349
  });
343
350
 
344
351
  function writeSession(