@ai-devkit/agent-manager 0.33.1 → 0.35.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 (191) 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/ClaudeCodeAdapter.test.js +28 -0
  8. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
  9. package/dist/__tests__/adapters/CodexAdapter.test.js +7 -0
  10. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
  11. package/dist/__tests__/capacity/codex.test.js +2 -1
  12. package/dist/__tests__/capacity/codex.test.js.map +1 -1
  13. package/dist/__tests__/capacity/index.test.js +6 -3
  14. package/dist/__tests__/capacity/index.test.js.map +1 -1
  15. package/dist/__tests__/capacity/zai.test.js +213 -0
  16. package/dist/__tests__/capacity/zai.test.js.map +1 -0
  17. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +18 -0
  18. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
  19. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
  20. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
  21. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
  22. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
  23. package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
  24. package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
  25. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
  26. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
  27. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +213 -0
  28. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -0
  29. package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js +89 -0
  30. package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js.map +1 -0
  31. package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js +94 -0
  32. package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js.map +1 -0
  33. package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js +147 -0
  34. package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js.map +1 -0
  35. package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
  36. package/dist/__tests__/providers/pi/PiAdapter.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 +8 -0
  40. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  41. package/dist/adapters/AgentAdapter.js.map +1 -1
  42. package/dist/adapters/index.d.ts +2 -2
  43. package/dist/adapters/index.d.ts.map +1 -1
  44. package/dist/adapters/index.js +2 -2
  45. package/dist/adapters/index.js.map +1 -1
  46. package/dist/capacity/codex.d.ts +1 -0
  47. package/dist/capacity/codex.d.ts.map +1 -1
  48. package/dist/capacity/codex.js +9 -4
  49. package/dist/capacity/codex.js.map +1 -1
  50. package/dist/capacity/index.d.ts +8 -0
  51. package/dist/capacity/index.d.ts.map +1 -1
  52. package/dist/capacity/index.js +10 -9
  53. package/dist/capacity/index.js.map +1 -1
  54. package/dist/capacity/types.d.ts +8 -0
  55. package/dist/capacity/types.d.ts.map +1 -1
  56. package/dist/capacity/types.js.map +1 -1
  57. package/dist/capacity/zai.d.ts +14 -0
  58. package/dist/capacity/zai.d.ts.map +1 -0
  59. package/dist/capacity/zai.js +203 -0
  60. package/dist/capacity/zai.js.map +1 -0
  61. package/dist/index.d.ts +4 -4
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +3 -3
  64. package/dist/index.js.map +1 -1
  65. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
  66. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
  67. package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
  68. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
  69. package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
  70. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
  71. package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
  72. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
  73. package/dist/providers/codex/CodexAdapter.d.ts +1 -0
  74. package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
  75. package/dist/providers/codex/CodexAdapter.js +8 -0
  76. package/dist/providers/codex/CodexAdapter.js.map +1 -1
  77. package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
  78. package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
  79. package/dist/providers/copilot/CopilotAdapter.js +24 -9
  80. package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
  81. package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
  82. package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
  83. package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
  84. package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
  85. package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
  86. package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
  87. package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
  88. package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
  89. package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
  90. package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
  91. package/dist/providers/grok/GrokAgentMapper.js +36 -0
  92. package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
  93. package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
  94. package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
  95. package/dist/providers/grok/GrokCliAdapter.js +98 -0
  96. package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
  97. package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
  98. package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
  99. package/dist/providers/grok/GrokSessionLocator.js +140 -0
  100. package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
  101. package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
  102. package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
  103. package/dist/providers/grok/GrokSessionParser.js +122 -0
  104. package/dist/providers/grok/GrokSessionParser.js.map +1 -0
  105. package/dist/{adapters → providers/opencode}/OpenCodeAdapter.d.ts +6 -12
  106. package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -0
  107. package/dist/providers/opencode/OpenCodeAdapter.js +89 -0
  108. package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -0
  109. package/dist/providers/opencode/OpenCodeAgentMapper.d.ts +11 -0
  110. package/dist/providers/opencode/OpenCodeAgentMapper.d.ts.map +1 -0
  111. package/dist/providers/opencode/OpenCodeAgentMapper.js +50 -0
  112. package/dist/providers/opencode/OpenCodeAgentMapper.js.map +1 -0
  113. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +23 -0
  114. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -0
  115. package/dist/providers/opencode/OpenCodeSessionLocator.js +118 -0
  116. package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -0
  117. package/dist/providers/opencode/OpenCodeSessionParser.d.ts +18 -0
  118. package/dist/providers/opencode/OpenCodeSessionParser.d.ts.map +1 -0
  119. package/dist/providers/opencode/OpenCodeSessionParser.js +104 -0
  120. package/dist/providers/opencode/OpenCodeSessionParser.js.map +1 -0
  121. package/dist/providers/pi/PiAdapter.d.ts +1 -0
  122. package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
  123. package/dist/providers/pi/PiAdapter.js +10 -0
  124. package/dist/providers/pi/PiAdapter.js.map +1 -1
  125. package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
  126. package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
  127. package/dist/providers/pi/PiSessionLocator.js +8 -1
  128. package/dist/providers/pi/PiSessionLocator.js.map +1 -1
  129. package/dist/utils/session.d.ts +2 -0
  130. package/dist/utils/session.d.ts.map +1 -1
  131. package/dist/utils/session.js +3 -0
  132. package/dist/utils/session.js.map +1 -1
  133. package/package.json +1 -1
  134. package/src/AgentManager.ts +34 -0
  135. package/src/__tests__/AgentManager.test.ts +60 -0
  136. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
  137. package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
  138. package/src/__tests__/capacity/codex.test.ts +94 -26
  139. package/src/__tests__/capacity/fixtures/zai-auth.json +6 -0
  140. package/src/__tests__/capacity/fixtures/zai-quota-invalid.json +7 -0
  141. package/src/__tests__/capacity/fixtures/zai-quota.json +38 -0
  142. package/src/__tests__/capacity/index.test.ts +10 -3
  143. package/src/__tests__/capacity/zai.test.ts +186 -0
  144. package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
  145. package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
  146. package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
  147. package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
  148. package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
  149. package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +215 -0
  150. package/src/__tests__/providers/opencode/OpenCodeAgentMapper.test.ts +83 -0
  151. package/src/__tests__/providers/opencode/OpenCodeSessionLocator.test.ts +84 -0
  152. package/src/__tests__/providers/opencode/OpenCodeSessionParser.test.ts +148 -0
  153. package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
  154. package/src/__tests__/utils/session.test.ts +11 -1
  155. package/src/adapters/AgentAdapter.ts +9 -0
  156. package/src/adapters/index.ts +2 -2
  157. package/src/capacity/codex.ts +96 -28
  158. package/src/capacity/index.ts +29 -11
  159. package/src/capacity/types.ts +8 -0
  160. package/src/capacity/zai.ts +237 -0
  161. package/src/index.ts +9 -4
  162. package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
  163. package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
  164. package/src/providers/codex/CodexAdapter.ts +8 -0
  165. package/src/providers/copilot/CopilotAdapter.ts +26 -10
  166. package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
  167. package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
  168. package/src/providers/grok/GrokAgentMapper.ts +41 -0
  169. package/src/providers/grok/GrokCliAdapter.ts +125 -0
  170. package/src/providers/grok/GrokSessionLocator.ts +179 -0
  171. package/src/providers/grok/GrokSessionParser.ts +166 -0
  172. package/src/providers/opencode/OpenCodeAdapter.ts +116 -0
  173. package/src/providers/opencode/OpenCodeAgentMapper.ts +61 -0
  174. package/src/providers/opencode/OpenCodeSessionLocator.ts +151 -0
  175. package/src/providers/opencode/OpenCodeSessionParser.ts +145 -0
  176. package/src/providers/pi/PiAdapter.ts +12 -0
  177. package/src/providers/pi/PiSessionLocator.ts +16 -1
  178. package/src/utils/session.ts +11 -0
  179. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
  180. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +0 -636
  181. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +0 -1
  182. package/dist/adapters/GrokCliAdapter.d.ts +0 -80
  183. package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
  184. package/dist/adapters/GrokCliAdapter.js +0 -310
  185. package/dist/adapters/GrokCliAdapter.js.map +0 -1
  186. package/dist/adapters/OpenCodeAdapter.d.ts.map +0 -1
  187. package/dist/adapters/OpenCodeAdapter.js +0 -289
  188. package/dist/adapters/OpenCodeAdapter.js.map +0 -1
  189. package/src/__tests__/adapters/OpenCodeAdapter.test.ts +0 -544
  190. package/src/adapters/GrokCliAdapter.ts +0 -408
  191. package/src/adapters/OpenCodeAdapter.ts +0 -373
@@ -0,0 +1,104 @@
1
+ const EMPTY_STATS = {
2
+ lastRole: null,
3
+ lastTimeUpdated: 0,
4
+ lastAssistantCompleted: false,
5
+ lastAssistantErrored: false,
6
+ summary: ""
7
+ };
8
+ export class OpenCodeSessionParser {
9
+ getConversation(db, sessionId, options) {
10
+ const verbose = options?.verbose ?? false;
11
+ try {
12
+ const rows = db.prepare(`
13
+ SELECT json_extract(m.data, '$.role') AS role,
14
+ p.data AS partData,
15
+ p.time_created AS timeCreated
16
+ FROM part p
17
+ JOIN message m ON p.message_id = m.id
18
+ WHERE p.session_id = ?
19
+ ORDER BY p.time_created ASC
20
+ `).all(sessionId);
21
+ const messages = [];
22
+ for (const row of rows){
23
+ const partData = this.parsePartData(row.partData);
24
+ if (!partData) continue;
25
+ const role = row.role === "user" ? "user" : "assistant";
26
+ if (partData.type === "text" && partData.text) {
27
+ messages.push({
28
+ role,
29
+ content: partData.text
30
+ });
31
+ } else if (partData.type === "reasoning" && verbose) {
32
+ const text = partData.reasoning || partData.text || "";
33
+ if (text) messages.push({
34
+ role: "assistant",
35
+ content: `[thinking] ${text}`
36
+ });
37
+ } else if (partData.type === "tool" && verbose) {
38
+ const toolName = partData.tool || "tool";
39
+ messages.push({
40
+ role: "assistant",
41
+ content: `[tool: ${toolName}]`
42
+ });
43
+ }
44
+ }
45
+ return messages;
46
+ } catch {
47
+ return [];
48
+ }
49
+ }
50
+ getSessionStats(db, sessionId) {
51
+ try {
52
+ // Order by time_created — time_updated can lag when OpenCode appends
53
+ // metadata (e.g. summary diffs) to user messages after a turn finishes.
54
+ const last = db.prepare(`
55
+ SELECT json_extract(data, '$.role') AS role,
56
+ time_updated AS timeUpdated
57
+ FROM message
58
+ WHERE session_id = ?
59
+ ORDER BY time_created DESC
60
+ LIMIT 1
61
+ `).get(sessionId);
62
+ const heartbeat = db.prepare(`
63
+ SELECT MAX(time_updated) AS maxUpdated FROM message WHERE session_id = ?
64
+ `).get(sessionId);
65
+ const lastAssistant = db.prepare(`
66
+ SELECT json_extract(data, '$.time.completed') AS completed,
67
+ json_extract(data, '$.time.error') AS errored
68
+ FROM message
69
+ WHERE session_id = ? AND json_extract(data, '$.role') = 'assistant'
70
+ ORDER BY time_created DESC
71
+ LIMIT 1
72
+ `).get(sessionId);
73
+ const first = db.prepare(`
74
+ SELECT json_extract(p.data, '$.text') AS text
75
+ FROM part p
76
+ JOIN message m ON p.message_id = m.id
77
+ WHERE p.session_id = ?
78
+ AND json_extract(m.data, '$.role') = 'user'
79
+ AND json_extract(p.data, '$.type') = 'text'
80
+ AND json_extract(p.data, '$.text') IS NOT NULL
81
+ ORDER BY p.time_created ASC
82
+ LIMIT 1
83
+ `).get(sessionId);
84
+ return {
85
+ lastRole: last?.role ?? null,
86
+ lastTimeUpdated: heartbeat?.maxUpdated ?? last?.timeUpdated ?? 0,
87
+ lastAssistantCompleted: lastAssistant?.completed != null,
88
+ lastAssistantErrored: lastAssistant?.errored != null,
89
+ summary: first?.text?.trim() ?? ""
90
+ };
91
+ } catch {
92
+ return EMPTY_STATS;
93
+ }
94
+ }
95
+ parsePartData(partData) {
96
+ try {
97
+ return JSON.parse(partData);
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ }
103
+
104
+ //# sourceMappingURL=OpenCodeSessionParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/providers/opencode/OpenCodeSessionParser.ts"],"sourcesContent":["import type { ConversationMessage } from \"../../adapters/AgentAdapter.js\";\nimport type Database from \"better-sqlite3\";\n\nexport interface OpenCodeSessionStats {\n lastRole: string | null;\n lastTimeUpdated: number;\n /** OpenCode writes `time.completed` on the assistant message only when the turn finishes. */\n lastAssistantCompleted: boolean;\n lastAssistantErrored: boolean;\n summary: string;\n}\n\ninterface OpenCodePartData {\n type?: string;\n text?: string;\n reasoning?: string;\n tool?: string;\n}\n\nconst EMPTY_STATS: OpenCodeSessionStats = {\n lastRole: null,\n lastTimeUpdated: 0,\n lastAssistantCompleted: false,\n lastAssistantErrored: false,\n summary: \"\",\n};\n\nexport class OpenCodeSessionParser {\n getConversation(\n db: Database.Database,\n sessionId: string,\n options?: { verbose?: boolean },\n ): ConversationMessage[] {\n const verbose = options?.verbose ?? false;\n\n try {\n const rows = db\n .prepare<[string], { role: string; partData: string; timeCreated: number }>(`\n SELECT json_extract(m.data, '$.role') AS role,\n p.data AS partData,\n p.time_created AS timeCreated\n FROM part p\n JOIN message m ON p.message_id = m.id\n WHERE p.session_id = ?\n ORDER BY p.time_created ASC\n `)\n .all(sessionId);\n\n const messages: ConversationMessage[] = [];\n\n for (const row of rows) {\n const partData = this.parsePartData(row.partData);\n if (!partData) continue;\n\n const role = row.role === \"user\" ? \"user\" : \"assistant\";\n\n if (partData.type === \"text\" && partData.text) {\n messages.push({ role, content: partData.text });\n } else if (partData.type === \"reasoning\" && verbose) {\n const text = partData.reasoning || partData.text || \"\";\n if (text) messages.push({ role: \"assistant\", content: `[thinking] ${text}` });\n } else if (partData.type === \"tool\" && verbose) {\n const toolName = partData.tool || \"tool\";\n messages.push({ role: \"assistant\", content: `[tool: ${toolName}]` });\n }\n }\n\n return messages;\n } catch {\n return [];\n }\n }\n\n getSessionStats(db: Database.Database, sessionId: string): OpenCodeSessionStats {\n try {\n // Order by time_created — time_updated can lag when OpenCode appends\n // metadata (e.g. summary diffs) to user messages after a turn finishes.\n const last = db\n .prepare<[string], { role: string; timeUpdated: number }>(`\n SELECT json_extract(data, '$.role') AS role,\n time_updated AS timeUpdated\n FROM message\n WHERE session_id = ?\n ORDER BY time_created DESC\n LIMIT 1\n `)\n .get(sessionId);\n\n const heartbeat = db\n .prepare<[string], { maxUpdated: number }>(`\n SELECT MAX(time_updated) AS maxUpdated FROM message WHERE session_id = ?\n `)\n .get(sessionId);\n\n const lastAssistant = db\n .prepare<\n [string],\n {\n completed: number | null;\n errored: number | null;\n }\n >(`\n SELECT json_extract(data, '$.time.completed') AS completed,\n json_extract(data, '$.time.error') AS errored\n FROM message\n WHERE session_id = ? AND json_extract(data, '$.role') = 'assistant'\n ORDER BY time_created DESC\n LIMIT 1\n `)\n .get(sessionId);\n\n const first = db\n .prepare<[string], { text: string }>(`\n SELECT json_extract(p.data, '$.text') AS text\n FROM part p\n JOIN message m ON p.message_id = m.id\n WHERE p.session_id = ?\n AND json_extract(m.data, '$.role') = 'user'\n AND json_extract(p.data, '$.type') = 'text'\n AND json_extract(p.data, '$.text') IS NOT NULL\n ORDER BY p.time_created ASC\n LIMIT 1\n `)\n .get(sessionId);\n\n return {\n lastRole: last?.role ?? null,\n lastTimeUpdated: heartbeat?.maxUpdated ?? last?.timeUpdated ?? 0,\n lastAssistantCompleted: lastAssistant?.completed != null,\n lastAssistantErrored: lastAssistant?.errored != null,\n summary: first?.text?.trim() ?? \"\",\n };\n } catch {\n return EMPTY_STATS;\n }\n }\n\n private parsePartData(partData: string): OpenCodePartData | null {\n try {\n return JSON.parse(partData) as OpenCodePartData;\n } catch {\n return null;\n }\n }\n}\n"],"names":["EMPTY_STATS","lastRole","lastTimeUpdated","lastAssistantCompleted","lastAssistantErrored","summary","OpenCodeSessionParser","getConversation","db","sessionId","options","verbose","rows","prepare","all","messages","row","partData","parsePartData","role","type","text","push","content","reasoning","toolName","tool","getSessionStats","last","get","heartbeat","lastAssistant","first","maxUpdated","timeUpdated","completed","errored","trim","JSON","parse"],"mappings":"AAmBA,MAAMA,cAAoC;IACxCC,UAAU;IACVC,iBAAiB;IACjBC,wBAAwB;IACxBC,sBAAsB;IACtBC,SAAS;AACX;AAEA,OAAO,MAAMC;IACXC,gBACEC,EAAqB,EACrBC,SAAiB,EACjBC,OAA+B,EACR;QACvB,MAAMC,UAAUD,SAASC,WAAW;QAEpC,IAAI;YACF,MAAMC,OAAOJ,GACVK,OAAO,CAAoE,CAAC;;;;;;;;YAQzE,CAAC,EACJC,GAAG,CAACL;YAEP,MAAMM,WAAkC,EAAE;YAE1C,KAAK,MAAMC,OAAOJ,KAAM;gBACtB,MAAMK,WAAW,IAAI,CAACC,aAAa,CAACF,IAAIC,QAAQ;gBAChD,IAAI,CAACA,UAAU;gBAEf,MAAME,OAAOH,IAAIG,IAAI,KAAK,SAAS,SAAS;gBAE5C,IAAIF,SAASG,IAAI,KAAK,UAAUH,SAASI,IAAI,EAAE;oBAC7CN,SAASO,IAAI,CAAC;wBAAEH;wBAAMI,SAASN,SAASI,IAAI;oBAAC;gBAC/C,OAAO,IAAIJ,SAASG,IAAI,KAAK,eAAeT,SAAS;oBACnD,MAAMU,OAAOJ,SAASO,SAAS,IAAIP,SAASI,IAAI,IAAI;oBACpD,IAAIA,MAAMN,SAASO,IAAI,CAAC;wBAAEH,MAAM;wBAAaI,SAAS,CAAC,WAAW,EAAEF,MAAM;oBAAC;gBAC7E,OAAO,IAAIJ,SAASG,IAAI,KAAK,UAAUT,SAAS;oBAC9C,MAAMc,WAAWR,SAASS,IAAI,IAAI;oBAClCX,SAASO,IAAI,CAAC;wBAAEH,MAAM;wBAAaI,SAAS,CAAC,OAAO,EAAEE,SAAS,CAAC,CAAC;oBAAC;gBACpE;YACF;YAEA,OAAOV;QACT,EAAE,OAAM;YACN,OAAO,EAAE;QACX;IACF;IAEAY,gBAAgBnB,EAAqB,EAAEC,SAAiB,EAAwB;QAC9E,IAAI;YACF,qEAAqE;YACrE,wEAAwE;YACxE,MAAMmB,OAAOpB,GACVK,OAAO,CAAkD,CAAC;;;;;;;YAOvD,CAAC,EACJgB,GAAG,CAACpB;YAEP,MAAMqB,YAAYtB,GACfK,OAAO,CAAmC,CAAC;;YAExC,CAAC,EACJgB,GAAG,CAACpB;YAEP,MAAMsB,gBAAgBvB,GACnBK,OAAO,CAMN,CAAC;;;;;;;YAOC,CAAC,EACJgB,GAAG,CAACpB;YAEP,MAAMuB,QAAQxB,GACXK,OAAO,CAA6B,CAAC;;;;;;;;;;YAUlC,CAAC,EACJgB,GAAG,CAACpB;YAEP,OAAO;gBACLR,UAAU2B,MAAMT,QAAQ;gBACxBjB,iBAAiB4B,WAAWG,cAAcL,MAAMM,eAAe;gBAC/D/B,wBAAwB4B,eAAeI,aAAa;gBACpD/B,sBAAsB2B,eAAeK,WAAW;gBAChD/B,SAAS2B,OAAOX,MAAMgB,UAAU;YAClC;QACF,EAAE,OAAM;YACN,OAAOrC;QACT;IACF;IAEQkB,cAAcD,QAAgB,EAA2B;QAC/D,IAAI;YACF,OAAOqB,KAAKC,KAAK,CAACtB;QACpB,EAAE,OAAM;YACN,OAAO;QACT;IACF;AACF"}
@@ -15,6 +15,7 @@ export declare class PiAdapter implements AgentAdapter {
15
15
  verbose?: boolean;
16
16
  }): ConversationMessage[];
17
17
  listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]>;
18
+ findSessionsById(sessionId: string): Promise<SessionSummary[]>;
18
19
  private getPiProcesses;
19
20
  private createLocator;
20
21
  private createTracker;
@@ -1 +1 @@
1
- {"version":3,"file":"PiAdapter.d.ts","sourceRoot":"","sources":["../../../src/providers/pi/PiAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAgB7D,qBAAa,SAAU,YAAW,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAG,IAAI,CAAU;IAC9B,QAAQ,CAAC,YAAY,0BAA2B;IAEhD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,QAAQ,GAAE,aAAuC;IAU7D,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO;IAItC,YAAY,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAqBzE,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,mBAAmB,EAAE;IAI1F,YAAY,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;YAa3D,cAAc;IAW5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,cAAc;CAOvB"}
1
+ {"version":3,"file":"PiAdapter.d.ts","sourceRoot":"","sources":["../../../src/providers/pi/PiAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAgB7D,qBAAa,SAAU,YAAW,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAG,IAAI,CAAU;IAC9B,QAAQ,CAAC,YAAY,0BAA2B;IAEhD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,QAAQ,GAAE,aAAuC;IAU7D,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO;IAItC,YAAY,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAqBzE,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,mBAAmB,EAAE;IAI1F,YAAY,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAanE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;YAYtD,cAAc;IAW5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,cAAc;CAOvB"}
@@ -57,6 +57,16 @@ export class PiAdapter {
57
57
  }
58
58
  return summaries;
59
59
  }
60
+ async findSessionsById(sessionId) {
61
+ const summaries = [];
62
+ const filePaths = this.createLocator().findHistoricalSessionFilesById(sessionId);
63
+ for (const filePath of filePaths){
64
+ const summary = this.parser.fileToSessionSummary(filePath);
65
+ if (summary?.sessionId === sessionId) summaries.push(summary);
66
+ }
67
+ if (summaries.length > 0) return summaries;
68
+ return (await this.listSessions()).filter((summary)=>summary.sessionId === sessionId);
69
+ }
60
70
  async getPiProcesses(context) {
61
71
  const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
62
72
  const relevant = filterByProcessNames(snapshot, this.processNames);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/providers/pi/PiAdapter.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport type {\n AgentAdapter,\n AgentInfo,\n ProcessInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n AgentDetectionContext,\n} from \"../../adapters/AgentAdapter.js\";\nimport {\n captureProcessSnapshot,\n executableBasename,\n filterByProcessNames,\n} from \"../../utils/process.js\";\nimport { AgentRegistry } from \"../../utils/AgentRegistry.js\";\nimport { PiAgentMapper } from \"./PiAgentMapper.js\";\nimport { PiSessionLocator } from \"./PiSessionLocator.js\";\nimport { PiSessionParser } from \"./PiSessionParser.js\";\nimport { PiSessionTracker } from \"./PiSessionTracker.js\";\n\ninterface MappedAgentResult {\n agents: AgentInfo[];\n fallback: ProcessInfo[];\n}\n\ninterface SessionFileMatch {\n process: ProcessInfo;\n filePath: string;\n}\n\nexport class PiAdapter implements AgentAdapter {\n readonly type = \"pi\" as const;\n readonly processNames = [\"pi\", \"node\"] as const;\n\n private readonly piSessionsDir: string;\n private readonly trackerPath: string;\n private readonly registry: AgentRegistry;\n private readonly parser: PiSessionParser;\n private readonly mapper: PiAgentMapper;\n\n constructor(registry: AgentRegistry = AgentRegistry.default()) {\n const homeDir = process.env.HOME || process.env.USERPROFILE || \"\";\n const piAgentDir = path.join(homeDir, \".pi\", \"agent\");\n this.piSessionsDir = path.join(piAgentDir, \"sessions\");\n this.trackerPath = path.join(piAgentDir, \"sessions.json\");\n this.registry = registry;\n this.parser = new PiSessionParser();\n this.mapper = new PiAgentMapper(this.parser);\n }\n\n canHandle(processInfo: ProcessInfo): boolean {\n return this.isPiExecutable(processInfo.command);\n }\n\n async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {\n const processes = await this.getPiProcesses(context);\n if (processes.length === 0) return [];\n\n const cacheResult = this.mapRegistryCache(processes);\n const trackerResult = this.mapTrackerMatches(cacheResult.fallback);\n const locatorResult = this.createLocator().matchRunningProcesses(trackerResult.fallback);\n const legacyResult = this.mapLegacyMatches(locatorResult.legacyMatches);\n const unmatchedProcesses = this.findUnmatchedProcesses(\n locatorResult.fallback,\n legacyResult.agents,\n );\n\n return [\n ...cacheResult.agents,\n ...trackerResult.agents,\n ...legacyResult.agents,\n ...unmatchedProcesses.map((processInfo) => this.mapper.mapProcessOnlyAgent(processInfo)),\n ];\n }\n\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {\n return this.parser.getConversation(sessionFilePath, options);\n }\n\n async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {\n const summaries: SessionSummary[] = [];\n\n for (const filePath of this.createLocator().discoverHistoricalSessionFiles()) {\n const summary = this.parser.fileToSessionSummary(filePath);\n if (!summary) continue;\n if (opts?.cwd !== undefined && summary.cwd !== opts.cwd) continue;\n summaries.push(summary);\n }\n\n return summaries;\n }\n\n private async getPiProcesses(context?: AgentDetectionContext): Promise<ProcessInfo[]> {\n const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));\n const relevant = filterByProcessNames(snapshot, this.processNames);\n\n const byPid = new Map<number, ProcessInfo>();\n for (const processInfo of relevant) {\n if (this.canHandle(processInfo)) byPid.set(processInfo.pid, processInfo);\n }\n return Array.from(byPid.values());\n }\n\n private createLocator(): PiSessionLocator {\n return new PiSessionLocator({ sessionsDir: this.piSessionsDir }, this.parser);\n }\n\n private createTracker(): PiSessionTracker {\n return new PiSessionTracker({\n sessionsDir: this.piSessionsDir,\n trackerPath: this.trackerPath,\n });\n }\n\n private mapRegistryCache(processes: ProcessInfo[]): MappedAgentResult {\n const matches: SessionFileMatch[] = [];\n const fallback: ProcessInfo[] = [];\n const byPid = new Map(this.registry.list().map((entry) => [entry.pid, entry]));\n\n for (const processInfo of processes) {\n const entry = byPid.get(processInfo.pid);\n if (\n !entry ||\n entry.type !== this.type ||\n !entry.sessionFilePath ||\n !fs.existsSync(entry.sessionFilePath)\n ) {\n fallback.push(processInfo);\n continue;\n }\n\n matches.push({ process: processInfo, filePath: entry.sessionFilePath });\n }\n\n const mapped = this.mapSessionFileMatches(matches);\n return { agents: mapped.agents, fallback: [...fallback, ...mapped.fallback] };\n }\n\n private mapTrackerMatches(processes: ProcessInfo[]): MappedAgentResult {\n const { matches, fallback } = this.createTracker().match(processes);\n const mapped = this.mapSessionFileMatches(matches);\n return { agents: mapped.agents, fallback: [...fallback, ...mapped.fallback] };\n }\n\n private mapLegacyMatches(\n matches: Array<{ process: ProcessInfo; session: { filePath: string } }>,\n ): MappedAgentResult {\n return this.mapSessionFileMatches(\n matches.map((match) => ({ process: match.process, filePath: match.session.filePath })),\n );\n }\n\n private mapSessionFileMatches(matches: SessionFileMatch[]): MappedAgentResult {\n const agents: AgentInfo[] = [];\n const fallback: ProcessInfo[] = [];\n\n for (const match of matches) {\n const session = this.parser.readSession(match.filePath, match.process.cwd);\n if (session) {\n agents.push(this.mapper.mapSessionToAgent(session, match.process, match.filePath));\n } else {\n fallback.push(match.process);\n }\n }\n\n return { agents, fallback };\n }\n\n private findUnmatchedProcesses(processes: ProcessInfo[], agents: AgentInfo[]): ProcessInfo[] {\n const matchedPids = new Set(agents.map((agent) => agent.pid));\n return processes.filter((processInfo) => !matchedPids.has(processInfo.pid));\n }\n\n private isPiExecutable(command: string): boolean {\n for (const token of command.trim().split(/\\s+/)) {\n const base = executableBasename(token);\n if (base === \"pi\" || base === \"pi.exe\" || base === \"pi.js\") return true;\n }\n return false;\n }\n}\n"],"names":["fs","path","captureProcessSnapshot","executableBasename","filterByProcessNames","AgentRegistry","PiAgentMapper","PiSessionLocator","PiSessionParser","PiSessionTracker","PiAdapter","type","processNames","piSessionsDir","trackerPath","registry","parser","mapper","default","homeDir","process","env","HOME","USERPROFILE","piAgentDir","join","canHandle","processInfo","isPiExecutable","command","detectAgents","context","processes","getPiProcesses","length","cacheResult","mapRegistryCache","trackerResult","mapTrackerMatches","fallback","locatorResult","createLocator","matchRunningProcesses","legacyResult","mapLegacyMatches","legacyMatches","unmatchedProcesses","findUnmatchedProcesses","agents","map","mapProcessOnlyAgent","getConversation","sessionFilePath","options","listSessions","opts","summaries","filePath","discoverHistoricalSessionFiles","summary","fileToSessionSummary","cwd","undefined","push","snapshot","relevant","byPid","Map","set","pid","Array","from","values","sessionsDir","createTracker","matches","list","entry","get","existsSync","mapped","mapSessionFileMatches","match","session","readSession","mapSessionToAgent","matchedPids","Set","agent","filter","has","token","trim","split","base"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/providers/pi/PiAdapter.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport type {\n AgentAdapter,\n AgentInfo,\n ProcessInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n AgentDetectionContext,\n} from \"../../adapters/AgentAdapter.js\";\nimport {\n captureProcessSnapshot,\n executableBasename,\n filterByProcessNames,\n} from \"../../utils/process.js\";\nimport { AgentRegistry } from \"../../utils/AgentRegistry.js\";\nimport { PiAgentMapper } from \"./PiAgentMapper.js\";\nimport { PiSessionLocator } from \"./PiSessionLocator.js\";\nimport { PiSessionParser } from \"./PiSessionParser.js\";\nimport { PiSessionTracker } from \"./PiSessionTracker.js\";\n\ninterface MappedAgentResult {\n agents: AgentInfo[];\n fallback: ProcessInfo[];\n}\n\ninterface SessionFileMatch {\n process: ProcessInfo;\n filePath: string;\n}\n\nexport class PiAdapter implements AgentAdapter {\n readonly type = \"pi\" as const;\n readonly processNames = [\"pi\", \"node\"] as const;\n\n private readonly piSessionsDir: string;\n private readonly trackerPath: string;\n private readonly registry: AgentRegistry;\n private readonly parser: PiSessionParser;\n private readonly mapper: PiAgentMapper;\n\n constructor(registry: AgentRegistry = AgentRegistry.default()) {\n const homeDir = process.env.HOME || process.env.USERPROFILE || \"\";\n const piAgentDir = path.join(homeDir, \".pi\", \"agent\");\n this.piSessionsDir = path.join(piAgentDir, \"sessions\");\n this.trackerPath = path.join(piAgentDir, \"sessions.json\");\n this.registry = registry;\n this.parser = new PiSessionParser();\n this.mapper = new PiAgentMapper(this.parser);\n }\n\n canHandle(processInfo: ProcessInfo): boolean {\n return this.isPiExecutable(processInfo.command);\n }\n\n async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {\n const processes = await this.getPiProcesses(context);\n if (processes.length === 0) return [];\n\n const cacheResult = this.mapRegistryCache(processes);\n const trackerResult = this.mapTrackerMatches(cacheResult.fallback);\n const locatorResult = this.createLocator().matchRunningProcesses(trackerResult.fallback);\n const legacyResult = this.mapLegacyMatches(locatorResult.legacyMatches);\n const unmatchedProcesses = this.findUnmatchedProcesses(\n locatorResult.fallback,\n legacyResult.agents,\n );\n\n return [\n ...cacheResult.agents,\n ...trackerResult.agents,\n ...legacyResult.agents,\n ...unmatchedProcesses.map((processInfo) => this.mapper.mapProcessOnlyAgent(processInfo)),\n ];\n }\n\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {\n return this.parser.getConversation(sessionFilePath, options);\n }\n\n async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {\n const summaries: SessionSummary[] = [];\n\n for (const filePath of this.createLocator().discoverHistoricalSessionFiles()) {\n const summary = this.parser.fileToSessionSummary(filePath);\n if (!summary) continue;\n if (opts?.cwd !== undefined && summary.cwd !== opts.cwd) continue;\n summaries.push(summary);\n }\n\n return summaries;\n }\n\n async findSessionsById(sessionId: string): Promise<SessionSummary[]> {\n const summaries: SessionSummary[] = [];\n const filePaths = this.createLocator().findHistoricalSessionFilesById(sessionId);\n for (const filePath of filePaths) {\n const summary = this.parser.fileToSessionSummary(filePath);\n if (summary?.sessionId === sessionId) summaries.push(summary);\n }\n\n if (summaries.length > 0) return summaries;\n return (await this.listSessions()).filter((summary) => summary.sessionId === sessionId);\n }\n\n private async getPiProcesses(context?: AgentDetectionContext): Promise<ProcessInfo[]> {\n const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));\n const relevant = filterByProcessNames(snapshot, this.processNames);\n\n const byPid = new Map<number, ProcessInfo>();\n for (const processInfo of relevant) {\n if (this.canHandle(processInfo)) byPid.set(processInfo.pid, processInfo);\n }\n return Array.from(byPid.values());\n }\n\n private createLocator(): PiSessionLocator {\n return new PiSessionLocator({ sessionsDir: this.piSessionsDir }, this.parser);\n }\n\n private createTracker(): PiSessionTracker {\n return new PiSessionTracker({\n sessionsDir: this.piSessionsDir,\n trackerPath: this.trackerPath,\n });\n }\n\n private mapRegistryCache(processes: ProcessInfo[]): MappedAgentResult {\n const matches: SessionFileMatch[] = [];\n const fallback: ProcessInfo[] = [];\n const byPid = new Map(this.registry.list().map((entry) => [entry.pid, entry]));\n\n for (const processInfo of processes) {\n const entry = byPid.get(processInfo.pid);\n if (\n !entry ||\n entry.type !== this.type ||\n !entry.sessionFilePath ||\n !fs.existsSync(entry.sessionFilePath)\n ) {\n fallback.push(processInfo);\n continue;\n }\n\n matches.push({ process: processInfo, filePath: entry.sessionFilePath });\n }\n\n const mapped = this.mapSessionFileMatches(matches);\n return { agents: mapped.agents, fallback: [...fallback, ...mapped.fallback] };\n }\n\n private mapTrackerMatches(processes: ProcessInfo[]): MappedAgentResult {\n const { matches, fallback } = this.createTracker().match(processes);\n const mapped = this.mapSessionFileMatches(matches);\n return { agents: mapped.agents, fallback: [...fallback, ...mapped.fallback] };\n }\n\n private mapLegacyMatches(\n matches: Array<{ process: ProcessInfo; session: { filePath: string } }>,\n ): MappedAgentResult {\n return this.mapSessionFileMatches(\n matches.map((match) => ({ process: match.process, filePath: match.session.filePath })),\n );\n }\n\n private mapSessionFileMatches(matches: SessionFileMatch[]): MappedAgentResult {\n const agents: AgentInfo[] = [];\n const fallback: ProcessInfo[] = [];\n\n for (const match of matches) {\n const session = this.parser.readSession(match.filePath, match.process.cwd);\n if (session) {\n agents.push(this.mapper.mapSessionToAgent(session, match.process, match.filePath));\n } else {\n fallback.push(match.process);\n }\n }\n\n return { agents, fallback };\n }\n\n private findUnmatchedProcesses(processes: ProcessInfo[], agents: AgentInfo[]): ProcessInfo[] {\n const matchedPids = new Set(agents.map((agent) => agent.pid));\n return processes.filter((processInfo) => !matchedPids.has(processInfo.pid));\n }\n\n private isPiExecutable(command: string): boolean {\n for (const token of command.trim().split(/\\s+/)) {\n const base = executableBasename(token);\n if (base === \"pi\" || base === \"pi.exe\" || base === \"pi.js\") return true;\n }\n return false;\n }\n}\n"],"names":["fs","path","captureProcessSnapshot","executableBasename","filterByProcessNames","AgentRegistry","PiAgentMapper","PiSessionLocator","PiSessionParser","PiSessionTracker","PiAdapter","type","processNames","piSessionsDir","trackerPath","registry","parser","mapper","default","homeDir","process","env","HOME","USERPROFILE","piAgentDir","join","canHandle","processInfo","isPiExecutable","command","detectAgents","context","processes","getPiProcesses","length","cacheResult","mapRegistryCache","trackerResult","mapTrackerMatches","fallback","locatorResult","createLocator","matchRunningProcesses","legacyResult","mapLegacyMatches","legacyMatches","unmatchedProcesses","findUnmatchedProcesses","agents","map","mapProcessOnlyAgent","getConversation","sessionFilePath","options","listSessions","opts","summaries","filePath","discoverHistoricalSessionFiles","summary","fileToSessionSummary","cwd","undefined","push","findSessionsById","sessionId","filePaths","findHistoricalSessionFilesById","filter","snapshot","relevant","byPid","Map","set","pid","Array","from","values","sessionsDir","createTracker","matches","list","entry","get","existsSync","mapped","mapSessionFileMatches","match","session","readSession","mapSessionToAgent","matchedPids","Set","agent","has","token","trim","split","base"],"mappings":"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"}
@@ -16,6 +16,7 @@ export declare class PiSessionLocator {
16
16
  matchRunningProcesses(processes: ProcessInfo[]): PiProcessSessionMatches;
17
17
  discoverLiveSessions(processes?: ProcessInfo[]): SessionFile[];
18
18
  discoverHistoricalSessionFiles(): string[];
19
+ findHistoricalSessionFilesById(sessionId: string): string[];
19
20
  private buildProjectDirCwdMap;
20
21
  private collectJsonlFiles;
21
22
  private encodeProjectDir;
@@ -1 +1 @@
1
- {"version":3,"file":"PiSessionLocator.d.ts","sourceRoot":"","sources":["../../../src/providers/pi/PiSessionLocator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAA4B,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,WAAW,EAAE,CAAC;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAKzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAGnC,OAAO,GAAE,uBAA4B,EACpB,MAAM,GAAE,eAAuC;IAMlE,qBAAqB,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,uBAAuB;IAYxE,oBAAoB,CAAC,SAAS,GAAE,WAAW,EAAO,GAAG,WAAW,EAAE;IAwBlE,8BAA8B,IAAI,MAAM,EAAE;IAI1C,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;CAIzB"}
1
+ {"version":3,"file":"PiSessionLocator.d.ts","sourceRoot":"","sources":["../../../src/providers/pi/PiSessionLocator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAA4B,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,WAAW,EAAE,CAAC;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAKzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAGnC,OAAO,GAAE,uBAA4B,EACpB,MAAM,GAAE,eAAuC;IAMlE,qBAAqB,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,uBAAuB;IAYxE,oBAAoB,CAAC,SAAS,GAAE,WAAW,EAAO,GAAG,WAAW,EAAE;IAwBlE,8BAA8B,IAAI,MAAM,EAAE;IAI1C,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;IAS3D,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;CAIzB"}
@@ -1,6 +1,6 @@
1
1
  import * as path from "path";
2
2
  import { matchProcessesToSessions } from "../../utils/matching.js";
3
- import { isDirectory, safeReaddir, safeStat } from "../../utils/session.js";
3
+ import { isDirectory, isSafePathSegment, safeReaddir, safeStat } from "../../utils/session.js";
4
4
  import { PiSessionParser } from "./PiSessionParser.js";
5
5
  export class PiSessionLocator {
6
6
  parser;
@@ -48,6 +48,13 @@ export class PiSessionLocator {
48
48
  discoverHistoricalSessionFiles() {
49
49
  return this.collectJsonlFiles(this.sessionsDir);
50
50
  }
51
+ findHistoricalSessionFilesById(sessionId) {
52
+ if (!isSafePathSegment(sessionId)) return [];
53
+ return this.collectJsonlFiles(this.sessionsDir).filter((filePath)=>{
54
+ const basename = path.basename(filePath, ".jsonl");
55
+ return basename === sessionId || basename.endsWith(`_${sessionId}`);
56
+ });
57
+ }
51
58
  buildProjectDirCwdMap(processes) {
52
59
  const map = new Map();
53
60
  for (const proc of processes){
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/providers/pi/PiSessionLocator.ts"],"sourcesContent":["import * as path from \"path\";\nimport type { ProcessInfo } from \"../../adapters/AgentAdapter.js\";\nimport { matchProcessesToSessions, type MatchResult } from \"../../utils/matching.js\";\nimport { isDirectory, safeReaddir, safeStat, type SessionFile } from \"../../utils/session.js\";\nimport { PiSessionParser } from \"./PiSessionParser.js\";\n\nexport interface PiSessionLocatorOptions {\n sessionsDir?: string;\n}\n\nexport interface PiProcessSessionMatches {\n legacyMatches: MatchResult[];\n fallback: ProcessInfo[];\n}\n\nexport class PiSessionLocator {\n private readonly sessionsDir: string;\n\n constructor(\n options: PiSessionLocatorOptions = {},\n private readonly parser: PiSessionParser = new PiSessionParser(),\n ) {\n const homeDir = process.env.HOME || process.env.USERPROFILE || \"\";\n this.sessionsDir = options.sessionsDir ?? path.join(homeDir, \".pi\", \"agent\", \"sessions\");\n }\n\n matchRunningProcesses(processes: ProcessInfo[]): PiProcessSessionMatches {\n if (processes.length === 0) return { legacyMatches: [], fallback: [] };\n\n const sessions = this.discoverLiveSessions(processes);\n if (sessions.length === 0) return { legacyMatches: [], fallback: processes };\n\n return {\n legacyMatches: matchProcessesToSessions(processes, sessions),\n fallback: processes,\n };\n }\n\n discoverLiveSessions(processes: ProcessInfo[] = []): SessionFile[] {\n if (!isDirectory(this.sessionsDir)) return [];\n\n const cwdByProjectDir = this.buildProjectDirCwdMap(processes);\n const sessions: SessionFile[] = [];\n for (const filePath of this.discoverHistoricalSessionFiles()) {\n const stat = safeStat(filePath);\n if (!stat) continue;\n\n const session = this.parser.readSession(filePath);\n const sessionId = session?.sessionId || this.parser.sessionIdFromFile(filePath);\n const projectDir = path.dirname(filePath);\n sessions.push({\n sessionId,\n filePath,\n projectDir,\n birthtimeMs: stat.birthtimeMs || stat.mtimeMs,\n resolvedCwd: session?.projectPath || cwdByProjectDir.get(path.basename(projectDir)) || \"\",\n });\n }\n\n return sessions;\n }\n\n discoverHistoricalSessionFiles(): string[] {\n return this.collectJsonlFiles(this.sessionsDir);\n }\n\n private buildProjectDirCwdMap(processes: ProcessInfo[]): Map<string, string> {\n const map = new Map<string, string>();\n for (const proc of processes) {\n if (!proc.cwd) continue;\n map.set(this.encodeProjectDir(proc.cwd), proc.cwd);\n }\n return map;\n }\n\n private collectJsonlFiles(dir: string): string[] {\n const files: string[] = [];\n for (const entry of safeReaddir(dir)) {\n const fullPath = path.join(dir, entry);\n const stat = safeStat(fullPath);\n if (!stat) continue;\n if (stat.isDirectory()) {\n files.push(...this.collectJsonlFiles(fullPath));\n } else if (stat.isFile() && entry.endsWith(\".jsonl\")) {\n files.push(fullPath);\n }\n }\n return files;\n }\n\n private encodeProjectDir(cwd: string): string {\n const normalized = path.resolve(cwd);\n return `--${normalized.replace(/^\\//, \"\").replace(/\\//g, \"-\")}--`;\n }\n}\n"],"names":["path","matchProcessesToSessions","isDirectory","safeReaddir","safeStat","PiSessionParser","PiSessionLocator","sessionsDir","options","parser","homeDir","process","env","HOME","USERPROFILE","join","matchRunningProcesses","processes","length","legacyMatches","fallback","sessions","discoverLiveSessions","cwdByProjectDir","buildProjectDirCwdMap","filePath","discoverHistoricalSessionFiles","stat","session","readSession","sessionId","sessionIdFromFile","projectDir","dirname","push","birthtimeMs","mtimeMs","resolvedCwd","projectPath","get","basename","collectJsonlFiles","map","Map","proc","cwd","set","encodeProjectDir","dir","files","entry","fullPath","isFile","endsWith","normalized","resolve","replace"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/providers/pi/PiSessionLocator.ts"],"sourcesContent":["import * as path from \"path\";\nimport type { ProcessInfo } from \"../../adapters/AgentAdapter.js\";\nimport { matchProcessesToSessions, type MatchResult } from \"../../utils/matching.js\";\nimport {\n isDirectory,\n isSafePathSegment,\n safeReaddir,\n safeStat,\n type SessionFile,\n} from \"../../utils/session.js\";\nimport { PiSessionParser } from \"./PiSessionParser.js\";\n\nexport interface PiSessionLocatorOptions {\n sessionsDir?: string;\n}\n\nexport interface PiProcessSessionMatches {\n legacyMatches: MatchResult[];\n fallback: ProcessInfo[];\n}\n\nexport class PiSessionLocator {\n private readonly sessionsDir: string;\n\n constructor(\n options: PiSessionLocatorOptions = {},\n private readonly parser: PiSessionParser = new PiSessionParser(),\n ) {\n const homeDir = process.env.HOME || process.env.USERPROFILE || \"\";\n this.sessionsDir = options.sessionsDir ?? path.join(homeDir, \".pi\", \"agent\", \"sessions\");\n }\n\n matchRunningProcesses(processes: ProcessInfo[]): PiProcessSessionMatches {\n if (processes.length === 0) return { legacyMatches: [], fallback: [] };\n\n const sessions = this.discoverLiveSessions(processes);\n if (sessions.length === 0) return { legacyMatches: [], fallback: processes };\n\n return {\n legacyMatches: matchProcessesToSessions(processes, sessions),\n fallback: processes,\n };\n }\n\n discoverLiveSessions(processes: ProcessInfo[] = []): SessionFile[] {\n if (!isDirectory(this.sessionsDir)) return [];\n\n const cwdByProjectDir = this.buildProjectDirCwdMap(processes);\n const sessions: SessionFile[] = [];\n for (const filePath of this.discoverHistoricalSessionFiles()) {\n const stat = safeStat(filePath);\n if (!stat) continue;\n\n const session = this.parser.readSession(filePath);\n const sessionId = session?.sessionId || this.parser.sessionIdFromFile(filePath);\n const projectDir = path.dirname(filePath);\n sessions.push({\n sessionId,\n filePath,\n projectDir,\n birthtimeMs: stat.birthtimeMs || stat.mtimeMs,\n resolvedCwd: session?.projectPath || cwdByProjectDir.get(path.basename(projectDir)) || \"\",\n });\n }\n\n return sessions;\n }\n\n discoverHistoricalSessionFiles(): string[] {\n return this.collectJsonlFiles(this.sessionsDir);\n }\n\n findHistoricalSessionFilesById(sessionId: string): string[] {\n if (!isSafePathSegment(sessionId)) return [];\n\n return this.collectJsonlFiles(this.sessionsDir).filter((filePath) => {\n const basename = path.basename(filePath, \".jsonl\");\n return basename === sessionId || basename.endsWith(`_${sessionId}`);\n });\n }\n\n private buildProjectDirCwdMap(processes: ProcessInfo[]): Map<string, string> {\n const map = new Map<string, string>();\n for (const proc of processes) {\n if (!proc.cwd) continue;\n map.set(this.encodeProjectDir(proc.cwd), proc.cwd);\n }\n return map;\n }\n\n private collectJsonlFiles(dir: string): string[] {\n const files: string[] = [];\n for (const entry of safeReaddir(dir)) {\n const fullPath = path.join(dir, entry);\n const stat = safeStat(fullPath);\n if (!stat) continue;\n if (stat.isDirectory()) {\n files.push(...this.collectJsonlFiles(fullPath));\n } else if (stat.isFile() && entry.endsWith(\".jsonl\")) {\n files.push(fullPath);\n }\n }\n return files;\n }\n\n private encodeProjectDir(cwd: string): string {\n const normalized = path.resolve(cwd);\n return `--${normalized.replace(/^\\//, \"\").replace(/\\//g, \"-\")}--`;\n }\n}\n"],"names":["path","matchProcessesToSessions","isDirectory","isSafePathSegment","safeReaddir","safeStat","PiSessionParser","PiSessionLocator","sessionsDir","options","parser","homeDir","process","env","HOME","USERPROFILE","join","matchRunningProcesses","processes","length","legacyMatches","fallback","sessions","discoverLiveSessions","cwdByProjectDir","buildProjectDirCwdMap","filePath","discoverHistoricalSessionFiles","stat","session","readSession","sessionId","sessionIdFromFile","projectDir","dirname","push","birthtimeMs","mtimeMs","resolvedCwd","projectPath","get","basename","collectJsonlFiles","findHistoricalSessionFilesById","filter","endsWith","map","Map","proc","cwd","set","encodeProjectDir","dir","files","entry","fullPath","isFile","normalized","resolve","replace"],"mappings":"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"}
@@ -25,6 +25,8 @@ export interface SessionFile {
25
25
  * Returns false on any error (missing path, permission denied, broken symlink, etc.).
26
26
  */
27
27
  export declare function isDirectory(p: string): boolean;
28
+ /** Return whether a value can be safely appended as one filesystem path segment. */
29
+ export declare function isSafePathSegment(value: string): boolean;
28
30
  /**
29
31
  * `fs.statSync` that swallows errors and returns `undefined` on failure.
30
32
  * Callers can pull whichever fields they need (mtime, birthtime, ...).
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/utils/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAGzB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAElB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IAEjB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IAEnB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IAEpB,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,SAAS,CAM/D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAMjD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAwC3E"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/utils/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAGzB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAElB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IAEjB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IAEnB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IAEpB,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQxD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,SAAS,CAM/D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAMjD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAwC3E"}
@@ -11,6 +11,9 @@ import * as path from "path";
11
11
  */ export function isDirectory(p) {
12
12
  return safeStat(p)?.isDirectory() ?? false;
13
13
  }
14
+ /** Return whether a value can be safely appended as one filesystem path segment. */ export function isSafePathSegment(value) {
15
+ return value.length > 0 && value !== "." && value !== ".." && !value.includes("\0") && path.basename(value) === value;
16
+ }
14
17
  /**
15
18
  * `fs.statSync` that swallows errors and returns `undefined` on failure.
16
19
  * Callers can pull whichever fields they need (mtime, birthtime, ...).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/session.ts"],"sourcesContent":["/**\n * Session File Utilities\n *\n * Utilities for discovering session files and their birth times.\n * Uses Node.js fs APIs to get birth timestamps without reading file contents.\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/**\n * Represents a session file with its birth time metadata.\n */\nexport interface SessionFile {\n /** Session identifier (filename without .jsonl extension) */\n sessionId: string;\n\n /** Full path to the session file */\n filePath: string;\n\n /** Parent directory of the session file */\n projectDir: string;\n\n /** File creation time in milliseconds since epoch */\n birthtimeMs: number;\n\n /** CWD this session maps to — set by the adapter after calling batchGetSessionFileBirthtimes() */\n resolvedCwd: string;\n}\n\n/**\n * Check whether a path exists and is a directory.\n * Returns false on any error (missing path, permission denied, broken symlink, etc.).\n */\nexport function isDirectory(p: string): boolean {\n return safeStat(p)?.isDirectory() ?? false;\n}\n\n/**\n * `fs.statSync` that swallows errors and returns `undefined` on failure.\n * Callers can pull whichever fields they need (mtime, birthtime, ...).\n */\nexport function safeStat(filePath: string): fs.Stats | undefined {\n try {\n return fs.statSync(filePath);\n } catch {\n return undefined;\n }\n}\n\n/**\n * `fs.readFileSync` (utf-8) that swallows errors and returns `undefined`\n * on failure. Use when an unreadable file should be skipped rather than\n * raised.\n */\nexport function safeReadFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * `fs.readdirSync` that swallows errors and returns `[]` on failure.\n * Useful when walking optional/transient directories where missing or\n * unreadable entries should be skipped silently.\n */\nexport function safeReaddir(dir: string): string[] {\n try {\n return fs.readdirSync(dir);\n } catch {\n return [];\n }\n}\n\n/**\n * List entries in a directory that end with `.jsonl`. Returns `[]` on\n * read errors. The result preserves directory order (no sorting).\n */\nexport function listJsonl(dir: string): string[] {\n return safeReaddir(dir).filter((name) => name.endsWith(\".jsonl\"));\n}\n\n/**\n * Get birth times for .jsonl session files across multiple directories.\n *\n * Enumerates each directory with readdirSync and stats each .jsonl file\n * to get its birth time. No shell commands are used.\n * Returns empty array if no directories have .jsonl files or reads fail.\n * resolvedCwd is left empty — the adapter must set it.\n */\nexport function batchGetSessionFileBirthtimes(dirs: string[]): SessionFile[] {\n if (dirs.length === 0) return [];\n\n const results: SessionFile[] = [];\n\n for (const dir of dirs) {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(\".jsonl\")) continue;\n\n const filePath = path.join(dir, entry);\n\n let birthtimeMs: number;\n try {\n birthtimeMs = fs.statSync(filePath).birthtimeMs;\n } catch {\n continue;\n }\n\n if (!Number.isFinite(birthtimeMs) || birthtimeMs <= 0) continue;\n\n const sessionId = entry.replace(/\\.jsonl$/, \"\");\n\n results.push({\n sessionId,\n filePath,\n projectDir: dir,\n birthtimeMs,\n resolvedCwd: \"\",\n });\n }\n }\n\n return results;\n}\n"],"names":["fs","path","isDirectory","p","safeStat","filePath","statSync","undefined","safeReadFile","readFileSync","safeReaddir","dir","readdirSync","listJsonl","filter","name","endsWith","batchGetSessionFileBirthtimes","dirs","length","results","entries","entry","join","birthtimeMs","Number","isFinite","sessionId","replace","push","projectDir","resolvedCwd"],"mappings":"AAAA;;;;;CAKC,GAED,YAAYA,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAsB7B;;;CAGC,GACD,OAAO,SAASC,YAAYC,CAAS;IACnC,OAAOC,SAASD,IAAID,iBAAiB;AACvC;AAEA;;;CAGC,GACD,OAAO,SAASE,SAASC,QAAgB;IACvC,IAAI;QACF,OAAOL,GAAGM,QAAQ,CAACD;IACrB,EAAE,OAAM;QACN,OAAOE;IACT;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASC,aAAaH,QAAgB;IAC3C,IAAI;QACF,OAAOL,GAAGS,YAAY,CAACJ,UAAU;IACnC,EAAE,OAAM;QACN,OAAOE;IACT;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASG,YAAYC,GAAW;IACrC,IAAI;QACF,OAAOX,GAAGY,WAAW,CAACD;IACxB,EAAE,OAAM;QACN,OAAO,EAAE;IACX;AACF;AAEA;;;CAGC,GACD,OAAO,SAASE,UAAUF,GAAW;IACnC,OAAOD,YAAYC,KAAKG,MAAM,CAAC,CAACC,OAASA,KAAKC,QAAQ,CAAC;AACzD;AAEA;;;;;;;CAOC,GACD,OAAO,SAASC,8BAA8BC,IAAc;IAC1D,IAAIA,KAAKC,MAAM,KAAK,GAAG,OAAO,EAAE;IAEhC,MAAMC,UAAyB,EAAE;IAEjC,KAAK,MAAMT,OAAOO,KAAM;QACtB,IAAIG;QACJ,IAAI;YACFA,UAAUrB,GAAGY,WAAW,CAACD;QAC3B,EAAE,OAAM;YACN;QACF;QAEA,KAAK,MAAMW,SAASD,QAAS;YAC3B,IAAI,CAACC,MAAMN,QAAQ,CAAC,WAAW;YAE/B,MAAMX,WAAWJ,KAAKsB,IAAI,CAACZ,KAAKW;YAEhC,IAAIE;YACJ,IAAI;gBACFA,cAAcxB,GAAGM,QAAQ,CAACD,UAAUmB,WAAW;YACjD,EAAE,OAAM;gBACN;YACF;YAEA,IAAI,CAACC,OAAOC,QAAQ,CAACF,gBAAgBA,eAAe,GAAG;YAEvD,MAAMG,YAAYL,MAAMM,OAAO,CAAC,YAAY;YAE5CR,QAAQS,IAAI,CAAC;gBACXF;gBACAtB;gBACAyB,YAAYnB;gBACZa;gBACAO,aAAa;YACf;QACF;IACF;IAEA,OAAOX;AACT"}
1
+ {"version":3,"sources":["../../src/utils/session.ts"],"sourcesContent":["/**\n * Session File Utilities\n *\n * Utilities for discovering session files and their birth times.\n * Uses Node.js fs APIs to get birth timestamps without reading file contents.\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/**\n * Represents a session file with its birth time metadata.\n */\nexport interface SessionFile {\n /** Session identifier (filename without .jsonl extension) */\n sessionId: string;\n\n /** Full path to the session file */\n filePath: string;\n\n /** Parent directory of the session file */\n projectDir: string;\n\n /** File creation time in milliseconds since epoch */\n birthtimeMs: number;\n\n /** CWD this session maps to — set by the adapter after calling batchGetSessionFileBirthtimes() */\n resolvedCwd: string;\n}\n\n/**\n * Check whether a path exists and is a directory.\n * Returns false on any error (missing path, permission denied, broken symlink, etc.).\n */\nexport function isDirectory(p: string): boolean {\n return safeStat(p)?.isDirectory() ?? false;\n}\n\n/** Return whether a value can be safely appended as one filesystem path segment. */\nexport function isSafePathSegment(value: string): boolean {\n return (\n value.length > 0 &&\n value !== \".\" &&\n value !== \"..\" &&\n !value.includes(\"\\0\") &&\n path.basename(value) === value\n );\n}\n\n/**\n * `fs.statSync` that swallows errors and returns `undefined` on failure.\n * Callers can pull whichever fields they need (mtime, birthtime, ...).\n */\nexport function safeStat(filePath: string): fs.Stats | undefined {\n try {\n return fs.statSync(filePath);\n } catch {\n return undefined;\n }\n}\n\n/**\n * `fs.readFileSync` (utf-8) that swallows errors and returns `undefined`\n * on failure. Use when an unreadable file should be skipped rather than\n * raised.\n */\nexport function safeReadFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * `fs.readdirSync` that swallows errors and returns `[]` on failure.\n * Useful when walking optional/transient directories where missing or\n * unreadable entries should be skipped silently.\n */\nexport function safeReaddir(dir: string): string[] {\n try {\n return fs.readdirSync(dir);\n } catch {\n return [];\n }\n}\n\n/**\n * List entries in a directory that end with `.jsonl`. Returns `[]` on\n * read errors. The result preserves directory order (no sorting).\n */\nexport function listJsonl(dir: string): string[] {\n return safeReaddir(dir).filter((name) => name.endsWith(\".jsonl\"));\n}\n\n/**\n * Get birth times for .jsonl session files across multiple directories.\n *\n * Enumerates each directory with readdirSync and stats each .jsonl file\n * to get its birth time. No shell commands are used.\n * Returns empty array if no directories have .jsonl files or reads fail.\n * resolvedCwd is left empty — the adapter must set it.\n */\nexport function batchGetSessionFileBirthtimes(dirs: string[]): SessionFile[] {\n if (dirs.length === 0) return [];\n\n const results: SessionFile[] = [];\n\n for (const dir of dirs) {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(\".jsonl\")) continue;\n\n const filePath = path.join(dir, entry);\n\n let birthtimeMs: number;\n try {\n birthtimeMs = fs.statSync(filePath).birthtimeMs;\n } catch {\n continue;\n }\n\n if (!Number.isFinite(birthtimeMs) || birthtimeMs <= 0) continue;\n\n const sessionId = entry.replace(/\\.jsonl$/, \"\");\n\n results.push({\n sessionId,\n filePath,\n projectDir: dir,\n birthtimeMs,\n resolvedCwd: \"\",\n });\n }\n }\n\n return results;\n}\n"],"names":["fs","path","isDirectory","p","safeStat","isSafePathSegment","value","length","includes","basename","filePath","statSync","undefined","safeReadFile","readFileSync","safeReaddir","dir","readdirSync","listJsonl","filter","name","endsWith","batchGetSessionFileBirthtimes","dirs","results","entries","entry","join","birthtimeMs","Number","isFinite","sessionId","replace","push","projectDir","resolvedCwd"],"mappings":"AAAA;;;;;CAKC,GAED,YAAYA,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAsB7B;;;CAGC,GACD,OAAO,SAASC,YAAYC,CAAS;IACnC,OAAOC,SAASD,IAAID,iBAAiB;AACvC;AAEA,kFAAkF,GAClF,OAAO,SAASG,kBAAkBC,KAAa;IAC7C,OACEA,MAAMC,MAAM,GAAG,KACfD,UAAU,OACVA,UAAU,QACV,CAACA,MAAME,QAAQ,CAAC,SAChBP,KAAKQ,QAAQ,CAACH,WAAWA;AAE7B;AAEA;;;CAGC,GACD,OAAO,SAASF,SAASM,QAAgB;IACvC,IAAI;QACF,OAAOV,GAAGW,QAAQ,CAACD;IACrB,EAAE,OAAM;QACN,OAAOE;IACT;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASC,aAAaH,QAAgB;IAC3C,IAAI;QACF,OAAOV,GAAGc,YAAY,CAACJ,UAAU;IACnC,EAAE,OAAM;QACN,OAAOE;IACT;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASG,YAAYC,GAAW;IACrC,IAAI;QACF,OAAOhB,GAAGiB,WAAW,CAACD;IACxB,EAAE,OAAM;QACN,OAAO,EAAE;IACX;AACF;AAEA;;;CAGC,GACD,OAAO,SAASE,UAAUF,GAAW;IACnC,OAAOD,YAAYC,KAAKG,MAAM,CAAC,CAACC,OAASA,KAAKC,QAAQ,CAAC;AACzD;AAEA;;;;;;;CAOC,GACD,OAAO,SAASC,8BAA8BC,IAAc;IAC1D,IAAIA,KAAKhB,MAAM,KAAK,GAAG,OAAO,EAAE;IAEhC,MAAMiB,UAAyB,EAAE;IAEjC,KAAK,MAAMR,OAAOO,KAAM;QACtB,IAAIE;QACJ,IAAI;YACFA,UAAUzB,GAAGiB,WAAW,CAACD;QAC3B,EAAE,OAAM;YACN;QACF;QAEA,KAAK,MAAMU,SAASD,QAAS;YAC3B,IAAI,CAACC,MAAML,QAAQ,CAAC,WAAW;YAE/B,MAAMX,WAAWT,KAAK0B,IAAI,CAACX,KAAKU;YAEhC,IAAIE;YACJ,IAAI;gBACFA,cAAc5B,GAAGW,QAAQ,CAACD,UAAUkB,WAAW;YACjD,EAAE,OAAM;gBACN;YACF;YAEA,IAAI,CAACC,OAAOC,QAAQ,CAACF,gBAAgBA,eAAe,GAAG;YAEvD,MAAMG,YAAYL,MAAMM,OAAO,CAAC,YAAY;YAE5CR,QAAQS,IAAI,CAAC;gBACXF;gBACArB;gBACAwB,YAAYlB;gBACZY;gBACAO,aAAa;YACf;QACF;IACF;IAEA,OAAOX;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-devkit/agent-manager",
3
- "version": "0.33.1",
3
+ "version": "0.35.0",
4
4
  "description": "Standalone agent detection and management utilities for AI DevKit",
5
5
  "keywords": [
6
6
  "agent",
@@ -347,6 +347,40 @@ export class AgentManager {
347
347
  return merged;
348
348
  }
349
349
 
350
+ /** Resolve an exact historical session ID across the selected providers. */
351
+ async findSessionsById(
352
+ sessionId: string,
353
+ opts?: Pick<ListSessionsOptions, "type">,
354
+ ): Promise<SessionSummary[]> {
355
+ const targetAdapters = Array.from(this.adapters.values()).filter(
356
+ (adapter) => opts?.type === undefined || adapter.type === opts.type,
357
+ );
358
+ const errors: Array<{ type: string; error: Error }> = [];
359
+
360
+ const results = await Promise.all(
361
+ targetAdapters.map(async (adapter) => {
362
+ try {
363
+ if (adapter.findSessionsById) return await adapter.findSessionsById(sessionId);
364
+ const sessions = await adapter.listSessions(opts);
365
+ return sessions.filter((session) => session.sessionId === sessionId);
366
+ } catch (error) {
367
+ const err = error instanceof Error ? error : new Error(String(error));
368
+ errors.push({ type: adapter.type, error: err });
369
+ return [];
370
+ }
371
+ }),
372
+ );
373
+
374
+ if (errors.length > 0) {
375
+ console.error(`Warning: ${errors.length} adapter(s) failed to find session by ID:`);
376
+ for (const { type, error } of errors) {
377
+ console.error(` - ${type}: ${error.message}`);
378
+ }
379
+ }
380
+
381
+ return results.flat();
382
+ }
383
+
350
384
  /**
351
385
  * Get count of registered adapters
352
386
  *
@@ -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" });
@@ -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(