@ai-devkit/agent-manager 0.34.0 → 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 (134) 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__/providers/copilot/CopilotAdapter.test.js +18 -0
  12. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
  13. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
  14. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
  15. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
  16. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
  17. package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
  18. package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
  19. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
  20. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
  21. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +24 -0
  22. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -1
  23. package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
  24. package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -1
  25. package/dist/__tests__/utils/session.test.js +18 -1
  26. package/dist/__tests__/utils/session.test.js.map +1 -1
  27. package/dist/adapters/AgentAdapter.d.ts +8 -0
  28. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  29. package/dist/adapters/AgentAdapter.js.map +1 -1
  30. package/dist/adapters/index.d.ts +1 -1
  31. package/dist/adapters/index.d.ts.map +1 -1
  32. package/dist/adapters/index.js +1 -1
  33. package/dist/adapters/index.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
  39. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
  40. package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
  41. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
  42. package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
  43. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
  44. package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
  45. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
  46. package/dist/providers/codex/CodexAdapter.d.ts +1 -0
  47. package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
  48. package/dist/providers/codex/CodexAdapter.js +8 -0
  49. package/dist/providers/codex/CodexAdapter.js.map +1 -1
  50. package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
  51. package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
  52. package/dist/providers/copilot/CopilotAdapter.js +24 -9
  53. package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
  54. package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
  55. package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
  56. package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
  57. package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
  58. package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
  59. package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
  60. package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
  61. package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
  62. package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
  63. package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
  64. package/dist/providers/grok/GrokAgentMapper.js +36 -0
  65. package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
  66. package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
  67. package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
  68. package/dist/providers/grok/GrokCliAdapter.js +98 -0
  69. package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
  70. package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
  71. package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
  72. package/dist/providers/grok/GrokSessionLocator.js +140 -0
  73. package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
  74. package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
  75. package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
  76. package/dist/providers/grok/GrokSessionParser.js +122 -0
  77. package/dist/providers/grok/GrokSessionParser.js.map +1 -0
  78. package/dist/providers/opencode/OpenCodeAdapter.d.ts +1 -0
  79. package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -1
  80. package/dist/providers/opencode/OpenCodeAdapter.js +3 -0
  81. package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -1
  82. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +2 -0
  83. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -1
  84. package/dist/providers/opencode/OpenCodeSessionLocator.js +31 -12
  85. package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -1
  86. package/dist/providers/pi/PiAdapter.d.ts +1 -0
  87. package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
  88. package/dist/providers/pi/PiAdapter.js +10 -0
  89. package/dist/providers/pi/PiAdapter.js.map +1 -1
  90. package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
  91. package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
  92. package/dist/providers/pi/PiSessionLocator.js +8 -1
  93. package/dist/providers/pi/PiSessionLocator.js.map +1 -1
  94. package/dist/utils/session.d.ts +2 -0
  95. package/dist/utils/session.d.ts.map +1 -1
  96. package/dist/utils/session.js +3 -0
  97. package/dist/utils/session.js.map +1 -1
  98. package/package.json +1 -1
  99. package/src/AgentManager.ts +34 -0
  100. package/src/__tests__/AgentManager.test.ts +60 -0
  101. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
  102. package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
  103. package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
  104. package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
  105. package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
  106. package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
  107. package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
  108. package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +18 -0
  109. package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
  110. package/src/__tests__/utils/session.test.ts +11 -1
  111. package/src/adapters/AgentAdapter.ts +9 -0
  112. package/src/adapters/index.ts +1 -1
  113. package/src/index.ts +1 -1
  114. package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
  115. package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
  116. package/src/providers/codex/CodexAdapter.ts +8 -0
  117. package/src/providers/copilot/CopilotAdapter.ts +26 -10
  118. package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
  119. package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
  120. package/src/providers/grok/GrokAgentMapper.ts +41 -0
  121. package/src/providers/grok/GrokCliAdapter.ts +125 -0
  122. package/src/providers/grok/GrokSessionLocator.ts +179 -0
  123. package/src/providers/grok/GrokSessionParser.ts +166 -0
  124. package/src/providers/opencode/OpenCodeAdapter.ts +4 -0
  125. package/src/providers/opencode/OpenCodeSessionLocator.ts +43 -15
  126. package/src/providers/pi/PiAdapter.ts +12 -0
  127. package/src/providers/pi/PiSessionLocator.ts +16 -1
  128. package/src/utils/session.ts +11 -0
  129. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
  130. package/dist/adapters/GrokCliAdapter.d.ts +0 -80
  131. package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
  132. package/dist/adapters/GrokCliAdapter.js +0 -310
  133. package/dist/adapters/GrokCliAdapter.js.map +0 -1
  134. package/src/adapters/GrokCliAdapter.ts +0 -408
@@ -0,0 +1,54 @@
1
+ import type { ConversationMessage } from "../../adapters/AgentAdapter.js";
2
+ import { AgentStatus } from "../../adapters/AgentAdapter.js";
3
+ export declare const CHAT_HISTORY_FILE = "chat_history.jsonl";
4
+ /** Parsed state for a single ~/.grok/sessions/<cwd>/<id>/ directory. */
5
+ export interface GrokSession {
6
+ sessionId: string;
7
+ projectPath: string;
8
+ sessionFilePath: string;
9
+ sessionStart: Date;
10
+ lastActive: Date;
11
+ firstUserMessage?: string;
12
+ lastUserMessage?: string;
13
+ lastRole?: ConversationMessage["role"];
14
+ }
15
+ export declare class GrokSessionParser {
16
+ /**
17
+ * Parse a session directory into a {@link GrokSession} from its
18
+ * chat_history.jsonl transcript. Returns null when the transcript is
19
+ * missing — i.e. there is no real session to surface.
20
+ */
21
+ readSession(sessionDir: string, defaultCwd: string): GrokSession | null;
22
+ /** Accepts a session dir or an explicit chat_history.jsonl path. */
23
+ getConversation(sessionPath: string, options?: {
24
+ verbose?: boolean;
25
+ }): ConversationMessage[];
26
+ /**
27
+ * Determine agent status from parsed session state.
28
+ *
29
+ * - past the idle threshold → IDLE
30
+ * - last transcript turn is an assistant message → WAITING (awaiting user)
31
+ * - otherwise (last turn was a user message, or unknown) → RUNNING
32
+ */
33
+ determineStatus(session: GrokSession): AgentStatus;
34
+ /**
35
+ * Single pass over chat_history.jsonl. Each line is a
36
+ * { type: 'system' | 'user' | 'assistant', content } record where content is
37
+ * either a string or an array of { type: 'text', text } blocks.
38
+ *
39
+ * Grok wraps the real user prompt in <user_query>...</user_query>; the other
40
+ * user records are context injections (<user_info>, <system-reminder>, ...)
41
+ * and are skipped so the summary is the actual prompt, not boilerplate.
42
+ */
43
+ private parseChatHistory;
44
+ /** Flatten a chat record's content (string or text-block array) to text. */
45
+ private extractText;
46
+ /**
47
+ * Extract the prompt inside <user_query>...</user_query>. Returns null when
48
+ * the record has no such tag (a context injection rather than a prompt).
49
+ */
50
+ private extractUserQuery;
51
+ /** Resolve a session dir or an explicit chat_history.jsonl path to the file. */
52
+ private resolveChatPath;
53
+ }
54
+ //# sourceMappingURL=GrokSessionParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrokSessionParser.d.ts","sourceRoot":"","sources":["../../../src/providers/grok/GrokSessionParser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAG7D,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAgBtD,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,IAAI,CAAC;IACnB,UAAU,EAAE,IAAI,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;CACxC;AAED,qBAAa,iBAAiB;IAC5B;;;;OAIG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAqBvE,oEAAoE;IACpE,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,mBAAmB,EAAE;IAK5F;;;;;;OAMG;IACH,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW;IAWlD;;;;;;;;OAQG;IACH,OAAO,CAAC,gBAAgB;IA0CxB,4EAA4E;IAC5E,OAAO,CAAC,WAAW;IAgBnB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAKxB,gFAAgF;IAChF,OAAO,CAAC,eAAe;CAGxB"}
@@ -0,0 +1,122 @@
1
+ import * as path from "path";
2
+ import { AgentStatus } from "../../adapters/AgentAdapter.js";
3
+ import { safeReadFile, safeStat } from "../../utils/session.js";
4
+ export const CHAT_HISTORY_FILE = "chat_history.jsonl";
5
+ const IDLE_THRESHOLD_MINUTES = 5;
6
+ export class GrokSessionParser {
7
+ /**
8
+ * Parse a session directory into a {@link GrokSession} from its
9
+ * chat_history.jsonl transcript. Returns null when the transcript is
10
+ * missing — i.e. there is no real session to surface.
11
+ */ readSession(sessionDir, defaultCwd) {
12
+ const chatPath = path.join(sessionDir, CHAT_HISTORY_FILE);
13
+ const chatStat = safeStat(chatPath);
14
+ if (!chatStat) return null;
15
+ const scan = this.parseChatHistory(chatPath, false);
16
+ const dirStat = safeStat(sessionDir);
17
+ const lastActive = chatStat.mtime;
18
+ return {
19
+ sessionId: path.basename(sessionDir),
20
+ projectPath: defaultCwd,
21
+ sessionFilePath: chatPath,
22
+ sessionStart: dirStat?.birthtime || lastActive,
23
+ lastActive,
24
+ firstUserMessage: scan.firstUserMessage,
25
+ lastUserMessage: scan.lastUserMessage,
26
+ lastRole: scan.lastRole
27
+ };
28
+ }
29
+ /** Accepts a session dir or an explicit chat_history.jsonl path. */ getConversation(sessionPath, options) {
30
+ return this.parseChatHistory(this.resolveChatPath(sessionPath), options?.verbose ?? false).messages;
31
+ }
32
+ /**
33
+ * Determine agent status from parsed session state.
34
+ *
35
+ * - past the idle threshold → IDLE
36
+ * - last transcript turn is an assistant message → WAITING (awaiting user)
37
+ * - otherwise (last turn was a user message, or unknown) → RUNNING
38
+ */ determineStatus(session) {
39
+ const diffMinutes = (Date.now() - session.lastActive.getTime()) / 60000;
40
+ if (diffMinutes > IDLE_THRESHOLD_MINUTES) {
41
+ return AgentStatus.IDLE;
42
+ }
43
+ if (session.lastRole === "assistant") {
44
+ return AgentStatus.WAITING;
45
+ }
46
+ return AgentStatus.RUNNING;
47
+ }
48
+ /**
49
+ * Single pass over chat_history.jsonl. Each line is a
50
+ * { type: 'system' | 'user' | 'assistant', content } record where content is
51
+ * either a string or an array of { type: 'text', text } blocks.
52
+ *
53
+ * Grok wraps the real user prompt in <user_query>...</user_query>; the other
54
+ * user records are context injections (<user_info>, <system-reminder>, ...)
55
+ * and are skipped so the summary is the actual prompt, not boilerplate.
56
+ */ parseChatHistory(chatPath, verbose) {
57
+ const content = safeReadFile(chatPath);
58
+ if (content === undefined) return {
59
+ messages: []
60
+ };
61
+ const messages = [];
62
+ let lastRole;
63
+ for (const line of content.trim().split("\n")){
64
+ if (!line.trim()) continue;
65
+ let record;
66
+ try {
67
+ record = JSON.parse(line);
68
+ } catch {
69
+ continue;
70
+ }
71
+ const text = this.extractText(record.content);
72
+ if (record.type === "user") {
73
+ const query = this.extractUserQuery(text);
74
+ if (query === null) continue; // context injection, not a real prompt
75
+ messages.push({
76
+ role: "user",
77
+ content: query
78
+ });
79
+ lastRole = "user";
80
+ } else if (record.type === "assistant") {
81
+ if (!text) continue;
82
+ messages.push({
83
+ role: "assistant",
84
+ content: text
85
+ });
86
+ lastRole = "assistant";
87
+ } else if (verbose && record.type === "system") {
88
+ if (!text) continue;
89
+ messages.push({
90
+ role: "system",
91
+ content: text
92
+ });
93
+ }
94
+ }
95
+ const userTurns = messages.filter((m)=>m.role === "user");
96
+ return {
97
+ messages,
98
+ firstUserMessage: userTurns[0]?.content,
99
+ lastUserMessage: userTurns[userTurns.length - 1]?.content,
100
+ lastRole
101
+ };
102
+ }
103
+ /** Flatten a chat record's content (string or text-block array) to text. */ extractText(content) {
104
+ if (typeof content === "string") return content;
105
+ if (Array.isArray(content)) {
106
+ return content.map((block)=>block && typeof block === "object" && typeof block.text === "string" ? block.text : "").join("");
107
+ }
108
+ return "";
109
+ }
110
+ /**
111
+ * Extract the prompt inside <user_query>...</user_query>. Returns null when
112
+ * the record has no such tag (a context injection rather than a prompt).
113
+ */ extractUserQuery(text) {
114
+ const match = text.match(/<user_query>\s*([\s\S]*?)\s*<\/user_query>/);
115
+ return match ? match[1].trim() : null;
116
+ }
117
+ /** Resolve a session dir or an explicit chat_history.jsonl path to the file. */ resolveChatPath(sessionPath) {
118
+ return sessionPath.endsWith(".jsonl") ? sessionPath : path.join(sessionPath, CHAT_HISTORY_FILE);
119
+ }
120
+ }
121
+
122
+ //# sourceMappingURL=GrokSessionParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/providers/grok/GrokSessionParser.ts"],"sourcesContent":["import * as path from \"path\";\nimport type { ConversationMessage } from \"../../adapters/AgentAdapter.js\";\nimport { AgentStatus } from \"../../adapters/AgentAdapter.js\";\nimport { safeReadFile, safeStat } from \"../../utils/session.js\";\n\nexport const CHAT_HISTORY_FILE = \"chat_history.jsonl\";\nconst IDLE_THRESHOLD_MINUTES = 5;\n\n/** One line of chat_history.jsonl. */\ninterface ChatRecord {\n type?: string;\n content?: unknown;\n}\n\ninterface ChatScan {\n messages: ConversationMessage[];\n firstUserMessage?: string;\n lastUserMessage?: string;\n lastRole?: ConversationMessage[\"role\"];\n}\n\n/** Parsed state for a single ~/.grok/sessions/<cwd>/<id>/ directory. */\nexport interface GrokSession {\n sessionId: string;\n projectPath: string;\n sessionFilePath: string;\n sessionStart: Date;\n lastActive: Date;\n firstUserMessage?: string;\n lastUserMessage?: string;\n lastRole?: ConversationMessage[\"role\"];\n}\n\nexport class GrokSessionParser {\n /**\n * Parse a session directory into a {@link GrokSession} from its\n * chat_history.jsonl transcript. Returns null when the transcript is\n * missing — i.e. there is no real session to surface.\n */\n readSession(sessionDir: string, defaultCwd: string): GrokSession | null {\n const chatPath = path.join(sessionDir, CHAT_HISTORY_FILE);\n const chatStat = safeStat(chatPath);\n if (!chatStat) return null;\n\n const scan = this.parseChatHistory(chatPath, false);\n const dirStat = safeStat(sessionDir);\n const lastActive = chatStat.mtime;\n\n return {\n sessionId: path.basename(sessionDir),\n projectPath: defaultCwd,\n sessionFilePath: chatPath,\n sessionStart: dirStat?.birthtime || lastActive,\n lastActive,\n firstUserMessage: scan.firstUserMessage,\n lastUserMessage: scan.lastUserMessage,\n lastRole: scan.lastRole,\n };\n }\n\n /** Accepts a session dir or an explicit chat_history.jsonl path. */\n getConversation(sessionPath: string, options?: { verbose?: boolean }): ConversationMessage[] {\n return this.parseChatHistory(this.resolveChatPath(sessionPath), options?.verbose ?? false)\n .messages;\n }\n\n /**\n * Determine agent status from parsed session state.\n *\n * - past the idle threshold → IDLE\n * - last transcript turn is an assistant message → WAITING (awaiting user)\n * - otherwise (last turn was a user message, or unknown) → RUNNING\n */\n determineStatus(session: GrokSession): AgentStatus {\n const diffMinutes = (Date.now() - session.lastActive.getTime()) / 60000;\n if (diffMinutes > IDLE_THRESHOLD_MINUTES) {\n return AgentStatus.IDLE;\n }\n if (session.lastRole === \"assistant\") {\n return AgentStatus.WAITING;\n }\n return AgentStatus.RUNNING;\n }\n\n /**\n * Single pass over chat_history.jsonl. Each line is a\n * { type: 'system' | 'user' | 'assistant', content } record where content is\n * either a string or an array of { type: 'text', text } blocks.\n *\n * Grok wraps the real user prompt in <user_query>...</user_query>; the other\n * user records are context injections (<user_info>, <system-reminder>, ...)\n * and are skipped so the summary is the actual prompt, not boilerplate.\n */\n private parseChatHistory(chatPath: string, verbose: boolean): ChatScan {\n const content = safeReadFile(chatPath);\n if (content === undefined) return { messages: [] };\n\n const messages: ConversationMessage[] = [];\n let lastRole: ConversationMessage[\"role\"] | undefined;\n\n for (const line of content.trim().split(\"\\n\")) {\n if (!line.trim()) continue;\n\n let record: ChatRecord;\n try {\n record = JSON.parse(line);\n } catch {\n continue;\n }\n\n const text = this.extractText(record.content);\n if (record.type === \"user\") {\n const query = this.extractUserQuery(text);\n if (query === null) continue; // context injection, not a real prompt\n messages.push({ role: \"user\", content: query });\n lastRole = \"user\";\n } else if (record.type === \"assistant\") {\n if (!text) continue;\n messages.push({ role: \"assistant\", content: text });\n lastRole = \"assistant\";\n } else if (verbose && record.type === \"system\") {\n if (!text) continue;\n messages.push({ role: \"system\", content: text });\n }\n }\n\n const userTurns = messages.filter((m) => m.role === \"user\");\n return {\n messages,\n firstUserMessage: userTurns[0]?.content,\n lastUserMessage: userTurns[userTurns.length - 1]?.content,\n lastRole,\n };\n }\n\n /** Flatten a chat record's content (string or text-block array) to text. */\n private extractText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n return content\n .map((block) =>\n block &&\n typeof block === \"object\" &&\n typeof (block as { text?: unknown }).text === \"string\"\n ? (block as { text: string }).text\n : \"\",\n )\n .join(\"\");\n }\n return \"\";\n }\n\n /**\n * Extract the prompt inside <user_query>...</user_query>. Returns null when\n * the record has no such tag (a context injection rather than a prompt).\n */\n private extractUserQuery(text: string): string | null {\n const match = text.match(/<user_query>\\s*([\\s\\S]*?)\\s*<\\/user_query>/);\n return match ? match[1].trim() : null;\n }\n\n /** Resolve a session dir or an explicit chat_history.jsonl path to the file. */\n private resolveChatPath(sessionPath: string): string {\n return sessionPath.endsWith(\".jsonl\") ? sessionPath : path.join(sessionPath, CHAT_HISTORY_FILE);\n }\n}\n"],"names":["path","AgentStatus","safeReadFile","safeStat","CHAT_HISTORY_FILE","IDLE_THRESHOLD_MINUTES","GrokSessionParser","readSession","sessionDir","defaultCwd","chatPath","join","chatStat","scan","parseChatHistory","dirStat","lastActive","mtime","sessionId","basename","projectPath","sessionFilePath","sessionStart","birthtime","firstUserMessage","lastUserMessage","lastRole","getConversation","sessionPath","options","resolveChatPath","verbose","messages","determineStatus","session","diffMinutes","Date","now","getTime","IDLE","WAITING","RUNNING","content","undefined","line","trim","split","record","JSON","parse","text","extractText","type","query","extractUserQuery","push","role","userTurns","filter","m","length","Array","isArray","map","block","match","endsWith"],"mappings":"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"}
@@ -26,5 +26,6 @@ export declare class OpenCodeAdapter implements AgentAdapter {
26
26
  verbose?: boolean;
27
27
  }): ConversationMessage[];
28
28
  listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]>;
29
+ findSessionsById(sessionId: string): Promise<SessionSummary[]>;
29
30
  }
30
31
  //# sourceMappingURL=OpenCodeAdapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OpenCodeAdapter.d.ts","sourceRoot":"","sources":["../../../src/providers/opencode/OpenCodeAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,gCAAgC,CAAC;AAkBxC,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IACpC,QAAQ,CAAC,YAAY,wBAAyB;IAE9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;gBAExC,MAAM,CAAC,EAAE,MAAM;IAS3B,KAAK,IAAI,IAAI;IAOb,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO;IAKtC,YAAY,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA6BzE,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,mBAAmB,EAAE;IAU1F,YAAY,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAG1E"}
1
+ {"version":3,"file":"OpenCodeAdapter.d.ts","sourceRoot":"","sources":["../../../src/providers/opencode/OpenCodeAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,gCAAgC,CAAC;AAkBxC,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IACpC,QAAQ,CAAC,YAAY,wBAAyB;IAE9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;gBAExC,MAAM,CAAC,EAAE,MAAM;IAS3B,KAAK,IAAI,IAAI;IAOb,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO;IAKtC,YAAY,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA6BzE,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,mBAAmB,EAAE;IAU1F,YAAY,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAInE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAGrE"}
@@ -81,6 +81,9 @@ export class OpenCodeAdapter {
81
81
  async listSessions(opts) {
82
82
  return this.locator.listSessions(opts);
83
83
  }
84
+ async findSessionsById(sessionId) {
85
+ return this.locator.findSessionsById(sessionId);
86
+ }
84
87
  }
85
88
 
86
89
  //# sourceMappingURL=OpenCodeAdapter.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/providers/opencode/OpenCodeAdapter.ts"],"sourcesContent":["/**\n * OpenCode Adapter\n *\n * Detects running OpenCode agents by:\n * 1. Filtering OpenCode processes from a shared asynchronous process snapshot\n * 2. Using snapshot CWD and start-time enrichment\n * 3. Querying OpenCode's SQLite DB (~/.local/share/opencode/opencode.db) to\n * find the session matching each process's CWD and read status from message.time.completed\n *\n * sessionFilePath encodes \"<dbPath>::<sessionId>\" so getConversation() can open the right\n * DB row without extending the AgentAdapter interface.\n */\n\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 { OpenCodeAgentMapper } from \"./OpenCodeAgentMapper.js\";\nimport { OpenCodeSessionLocator } from \"./OpenCodeSessionLocator.js\";\nimport { OpenCodeSessionParser } from \"./OpenCodeSessionParser.js\";\n\nconst SESSION_REF_SEP = \"::\";\n\nfunction decodeSessionRef(ref: string): { sessionId: string } | null {\n const idx = ref.lastIndexOf(SESSION_REF_SEP);\n if (idx === -1) return null;\n return { sessionId: ref.slice(idx + SESSION_REF_SEP.length) };\n}\n\nexport class OpenCodeAdapter implements AgentAdapter {\n readonly type = \"opencode\" as const;\n readonly processNames = [\"opencode\"] as const;\n\n private readonly parser: OpenCodeSessionParser;\n private readonly locator: OpenCodeSessionLocator;\n private readonly mapper: OpenCodeAgentMapper;\n private readonly cleanup = (): void => this.close();\n\n constructor(dbPath?: string) {\n this.parser = new OpenCodeSessionParser();\n this.locator = new OpenCodeSessionLocator(dbPath, this.parser);\n this.mapper = new OpenCodeAgentMapper(this.locator.dbFilePath);\n process.once(\"exit\", this.cleanup);\n process.once(\"SIGINT\", this.cleanup);\n process.once(\"SIGTERM\", this.cleanup);\n }\n\n close(): void {\n process.off(\"exit\", this.cleanup);\n process.off(\"SIGINT\", this.cleanup);\n process.off(\"SIGTERM\", this.cleanup);\n this.locator.close();\n }\n\n canHandle(processInfo: ProcessInfo): boolean {\n const base = executableBasename(processInfo.command);\n return base === \"opencode\" || base === \"opencode.exe\";\n }\n\n async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {\n const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));\n const relevant = filterByProcessNames(snapshot, this.processNames);\n const processes = relevant.filter((process) => this.canHandle(process));\n if (processes.length === 0) return [];\n\n const db = this.locator.openDb();\n if (!db) return processes.map((process) => this.mapper.mapProcessOnlyAgent(process));\n\n const agents: AgentInfo[] = [];\n for (const proc of processes) {\n if (!proc.cwd) {\n agents.push(this.mapper.mapProcessOnlyAgent(proc));\n continue;\n }\n\n const session = this.locator.findSessionForDirectory(db, proc.cwd);\n if (!session) {\n agents.push(this.mapper.mapProcessOnlyAgent(proc));\n continue;\n }\n\n const stats = this.parser.getSessionStats(db, session.sessionId);\n agents.push(this.mapper.mapSessionToAgent(session, stats, proc));\n }\n\n return agents;\n }\n\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {\n const ref = decodeSessionRef(sessionFilePath);\n if (!ref) return [];\n\n const db = this.locator.openDb();\n if (!db) return [];\n\n return this.parser.getConversation(db, ref.sessionId, options);\n }\n\n async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {\n return this.locator.listSessions(opts);\n }\n}\n"],"names":["captureProcessSnapshot","executableBasename","filterByProcessNames","OpenCodeAgentMapper","OpenCodeSessionLocator","OpenCodeSessionParser","SESSION_REF_SEP","decodeSessionRef","ref","idx","lastIndexOf","sessionId","slice","length","OpenCodeAdapter","type","processNames","parser","locator","mapper","cleanup","close","dbPath","dbFilePath","process","once","off","canHandle","processInfo","base","command","detectAgents","context","snapshot","processes","relevant","filter","db","openDb","map","mapProcessOnlyAgent","agents","proc","cwd","push","session","findSessionForDirectory","stats","getSessionStats","mapSessionToAgent","getConversation","sessionFilePath","options","listSessions","opts"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/providers/opencode/OpenCodeAdapter.ts"],"sourcesContent":["/**\n * OpenCode Adapter\n *\n * Detects running OpenCode agents by:\n * 1. Filtering OpenCode processes from a shared asynchronous process snapshot\n * 2. Using snapshot CWD and start-time enrichment\n * 3. Querying OpenCode's SQLite DB (~/.local/share/opencode/opencode.db) to\n * find the session matching each process's CWD and read status from message.time.completed\n *\n * sessionFilePath encodes \"<dbPath>::<sessionId>\" so getConversation() can open the right\n * DB row without extending the AgentAdapter interface.\n */\n\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 { OpenCodeAgentMapper } from \"./OpenCodeAgentMapper.js\";\nimport { OpenCodeSessionLocator } from \"./OpenCodeSessionLocator.js\";\nimport { OpenCodeSessionParser } from \"./OpenCodeSessionParser.js\";\n\nconst SESSION_REF_SEP = \"::\";\n\nfunction decodeSessionRef(ref: string): { sessionId: string } | null {\n const idx = ref.lastIndexOf(SESSION_REF_SEP);\n if (idx === -1) return null;\n return { sessionId: ref.slice(idx + SESSION_REF_SEP.length) };\n}\n\nexport class OpenCodeAdapter implements AgentAdapter {\n readonly type = \"opencode\" as const;\n readonly processNames = [\"opencode\"] as const;\n\n private readonly parser: OpenCodeSessionParser;\n private readonly locator: OpenCodeSessionLocator;\n private readonly mapper: OpenCodeAgentMapper;\n private readonly cleanup = (): void => this.close();\n\n constructor(dbPath?: string) {\n this.parser = new OpenCodeSessionParser();\n this.locator = new OpenCodeSessionLocator(dbPath, this.parser);\n this.mapper = new OpenCodeAgentMapper(this.locator.dbFilePath);\n process.once(\"exit\", this.cleanup);\n process.once(\"SIGINT\", this.cleanup);\n process.once(\"SIGTERM\", this.cleanup);\n }\n\n close(): void {\n process.off(\"exit\", this.cleanup);\n process.off(\"SIGINT\", this.cleanup);\n process.off(\"SIGTERM\", this.cleanup);\n this.locator.close();\n }\n\n canHandle(processInfo: ProcessInfo): boolean {\n const base = executableBasename(processInfo.command);\n return base === \"opencode\" || base === \"opencode.exe\";\n }\n\n async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {\n const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));\n const relevant = filterByProcessNames(snapshot, this.processNames);\n const processes = relevant.filter((process) => this.canHandle(process));\n if (processes.length === 0) return [];\n\n const db = this.locator.openDb();\n if (!db) return processes.map((process) => this.mapper.mapProcessOnlyAgent(process));\n\n const agents: AgentInfo[] = [];\n for (const proc of processes) {\n if (!proc.cwd) {\n agents.push(this.mapper.mapProcessOnlyAgent(proc));\n continue;\n }\n\n const session = this.locator.findSessionForDirectory(db, proc.cwd);\n if (!session) {\n agents.push(this.mapper.mapProcessOnlyAgent(proc));\n continue;\n }\n\n const stats = this.parser.getSessionStats(db, session.sessionId);\n agents.push(this.mapper.mapSessionToAgent(session, stats, proc));\n }\n\n return agents;\n }\n\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {\n const ref = decodeSessionRef(sessionFilePath);\n if (!ref) return [];\n\n const db = this.locator.openDb();\n if (!db) return [];\n\n return this.parser.getConversation(db, ref.sessionId, options);\n }\n\n async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {\n return this.locator.listSessions(opts);\n }\n\n async findSessionsById(sessionId: string): Promise<SessionSummary[]> {\n return this.locator.findSessionsById(sessionId);\n }\n}\n"],"names":["captureProcessSnapshot","executableBasename","filterByProcessNames","OpenCodeAgentMapper","OpenCodeSessionLocator","OpenCodeSessionParser","SESSION_REF_SEP","decodeSessionRef","ref","idx","lastIndexOf","sessionId","slice","length","OpenCodeAdapter","type","processNames","parser","locator","mapper","cleanup","close","dbPath","dbFilePath","process","once","off","canHandle","processInfo","base","command","detectAgents","context","snapshot","processes","relevant","filter","db","openDb","map","mapProcessOnlyAgent","agents","proc","cwd","push","session","findSessionForDirectory","stats","getSessionStats","mapSessionToAgent","getConversation","sessionFilePath","options","listSessions","opts","findSessionsById"],"mappings":"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"}
@@ -16,6 +16,8 @@ export declare class OpenCodeSessionLocator {
16
16
  close(): void;
17
17
  findSessionForDirectory(db: Database.Database, directory: string): OpenCodeSession | null;
18
18
  listSessions(opts?: ListSessionsOptions): SessionSummary[];
19
+ findSessionsById(sessionId: string): SessionSummary[];
20
+ private toSessionSummary;
19
21
  get dbFilePath(): string;
20
22
  }
21
23
  //# sourceMappingURL=OpenCodeSessionLocator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OpenCodeSessionLocator.d.ts","sourceRoot":"","sources":["../../../src/providers/opencode/OpenCodeSessionLocator.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAQnE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,sBAAsB;IAM/B,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,EAAE,CAAkC;gBAG1C,MAAM,SAAyC,EAC9B,MAAM,GAAE,qBAAmD;IAK9E,MAAM,CAAC,aAAa,IAAI,MAAM;IAO9B,MAAM,IAAI,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAYlC,KAAK,IAAI,IAAI;IAWb,uBAAuB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAmBzF,YAAY,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,cAAc,EAAE;IAyC1D,IAAI,UAAU,IAAI,MAAM,CAEvB;CACF"}
1
+ {"version":3,"file":"OpenCodeSessionLocator.d.ts","sourceRoot":"","sources":["../../../src/providers/opencode/OpenCodeSessionLocator.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAQnE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAQD,qBAAa,sBAAsB;IAM/B,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,EAAE,CAAkC;gBAG1C,MAAM,SAAyC,EAC9B,MAAM,GAAE,qBAAmD;IAK9E,MAAM,CAAC,aAAa,IAAI,MAAM;IAO9B,MAAM,IAAI,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAYlC,KAAK,IAAI,IAAI;IAWb,uBAAuB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAmBzF,YAAY,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,cAAc,EAAE;IA4B1D,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE;IAmBrD,OAAO,CAAC,gBAAgB;IAgBxB,IAAI,UAAU,IAAI,MAAM,CAEvB;CACF"}
@@ -72,18 +72,7 @@ export class OpenCodeSessionLocator {
72
72
  const summaries = [];
73
73
  for (const row of rows){
74
74
  if (opts?.cwd !== undefined && row.directory !== opts.cwd) continue;
75
- const stats = this.parser.getSessionStats(db, row.id);
76
- const lastActive = stats.lastTimeUpdated > 0 ? new Date(stats.lastTimeUpdated) : new Date(row.timeCreated);
77
- const startedAt = new Date(row.timeCreated);
78
- summaries.push({
79
- type: "opencode",
80
- sessionId: row.id,
81
- cwd: row.directory,
82
- firstUserMessage: stats.summary,
83
- lastActive,
84
- startedAt,
85
- sessionFilePath: encodeOpenCodeSessionRef(this.dbPath, row.id)
86
- });
75
+ summaries.push(this.toSessionSummary(db, row));
87
76
  }
88
77
  return summaries;
89
78
  } catch {
@@ -91,6 +80,36 @@ export class OpenCodeSessionLocator {
91
80
  return [];
92
81
  }
93
82
  }
83
+ findSessionsById(sessionId) {
84
+ const db = this.openDb();
85
+ if (!db) return [];
86
+ try {
87
+ const row = db.prepare(`
88
+ SELECT id, directory, time_created AS timeCreated
89
+ FROM session
90
+ WHERE id = ?
91
+ `).get(sessionId);
92
+ return row ? [
93
+ this.toSessionSummary(db, row)
94
+ ] : [];
95
+ } catch {
96
+ this.close();
97
+ return [];
98
+ }
99
+ }
100
+ toSessionSummary(db, row) {
101
+ const stats = this.parser.getSessionStats(db, row.id);
102
+ const lastActive = stats.lastTimeUpdated > 0 ? new Date(stats.lastTimeUpdated) : new Date(row.timeCreated);
103
+ return {
104
+ type: "opencode",
105
+ sessionId: row.id,
106
+ cwd: row.directory,
107
+ firstUserMessage: stats.summary,
108
+ lastActive,
109
+ startedAt: new Date(row.timeCreated),
110
+ sessionFilePath: encodeOpenCodeSessionRef(this.dbPath, row.id)
111
+ };
112
+ }
94
113
  get dbFilePath() {
95
114
  return this.dbPath;
96
115
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/providers/opencode/OpenCodeSessionLocator.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport Database from \"better-sqlite3\";\nimport type { ListSessionsOptions, SessionSummary } from \"../../adapters/AgentAdapter.js\";\nimport { OpenCodeSessionParser } from \"./OpenCodeSessionParser.js\";\n\nconst SESSION_REF_SEP = \"::\";\n\nfunction encodeOpenCodeSessionRef(dbPath: string, sessionId: string): string {\n return `${dbPath}${SESSION_REF_SEP}${sessionId}`;\n}\n\nexport interface OpenCodeSession {\n sessionId: string;\n directory: string;\n timeCreated: number;\n}\n\nexport class OpenCodeSessionLocator {\n private readonly dbPath: string;\n private db: Database.Database | null = null;\n\n constructor(\n dbPath = OpenCodeSessionLocator.resolveDbPath(),\n private readonly parser: OpenCodeSessionParser = new OpenCodeSessionParser(),\n ) {\n this.dbPath = dbPath;\n }\n\n static resolveDbPath(): string {\n const xdg = process.env.XDG_DATA_HOME;\n const home = process.env.HOME || process.env.USERPROFILE || \"\";\n const base = xdg || path.join(home, \".local\", \"share\");\n return path.join(base, \"opencode\", \"opencode.db\");\n }\n\n openDb(): Database.Database | null {\n if (this.db) return this.db;\n if (!fs.existsSync(this.dbPath)) return null;\n\n try {\n this.db = new Database(this.dbPath, { readonly: true });\n return this.db;\n } catch {\n return null;\n }\n }\n\n close(): void {\n if (this.db) {\n try {\n this.db.close();\n } catch {\n /* ignore */\n }\n this.db = null;\n }\n }\n\n findSessionForDirectory(db: Database.Database, directory: string): OpenCodeSession | null {\n try {\n const row = db\n .prepare<[string], { id: string; directory: string; time_created: number }>(`\n SELECT id, directory, time_created\n FROM session\n WHERE directory = ?\n ORDER BY time_created DESC\n LIMIT 1\n `)\n .get(directory);\n\n if (!row) return null;\n return { sessionId: row.id, directory: row.directory, timeCreated: row.time_created };\n } catch {\n return null;\n }\n }\n\n listSessions(opts?: ListSessionsOptions): SessionSummary[] {\n const db = this.openDb();\n if (!db) return [];\n\n try {\n const rows = db\n .prepare<[], { id: string; directory: string; timeCreated: number }>(`\n SELECT id, directory, time_created AS timeCreated\n FROM session\n ORDER BY time_created DESC\n `)\n .all();\n\n const summaries: SessionSummary[] = [];\n\n for (const row of rows) {\n if (opts?.cwd !== undefined && row.directory !== opts.cwd) continue;\n\n const stats = this.parser.getSessionStats(db, row.id);\n const lastActive =\n stats.lastTimeUpdated > 0 ? new Date(stats.lastTimeUpdated) : new Date(row.timeCreated);\n const startedAt = new Date(row.timeCreated);\n\n summaries.push({\n type: \"opencode\",\n sessionId: row.id,\n cwd: row.directory,\n firstUserMessage: stats.summary,\n lastActive,\n startedAt,\n sessionFilePath: encodeOpenCodeSessionRef(this.dbPath, row.id),\n });\n }\n\n return summaries;\n } catch {\n this.close();\n return [];\n }\n }\n\n get dbFilePath(): string {\n return this.dbPath;\n }\n}\n"],"names":["fs","path","Database","OpenCodeSessionParser","SESSION_REF_SEP","encodeOpenCodeSessionRef","dbPath","sessionId","OpenCodeSessionLocator","db","resolveDbPath","parser","xdg","process","env","XDG_DATA_HOME","home","HOME","USERPROFILE","base","join","openDb","existsSync","readonly","close","findSessionForDirectory","directory","row","prepare","get","id","timeCreated","time_created","listSessions","opts","rows","all","summaries","cwd","undefined","stats","getSessionStats","lastActive","lastTimeUpdated","Date","startedAt","push","type","firstUserMessage","summary","sessionFilePath","dbFilePath"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/providers/opencode/OpenCodeSessionLocator.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport Database from \"better-sqlite3\";\nimport type { ListSessionsOptions, SessionSummary } from \"../../adapters/AgentAdapter.js\";\nimport { OpenCodeSessionParser } from \"./OpenCodeSessionParser.js\";\n\nconst SESSION_REF_SEP = \"::\";\n\nfunction encodeOpenCodeSessionRef(dbPath: string, sessionId: string): string {\n return `${dbPath}${SESSION_REF_SEP}${sessionId}`;\n}\n\nexport interface OpenCodeSession {\n sessionId: string;\n directory: string;\n timeCreated: number;\n}\n\ninterface OpenCodeSessionRow {\n id: string;\n directory: string;\n timeCreated: number;\n}\n\nexport class OpenCodeSessionLocator {\n private readonly dbPath: string;\n private db: Database.Database | null = null;\n\n constructor(\n dbPath = OpenCodeSessionLocator.resolveDbPath(),\n private readonly parser: OpenCodeSessionParser = new OpenCodeSessionParser(),\n ) {\n this.dbPath = dbPath;\n }\n\n static resolveDbPath(): string {\n const xdg = process.env.XDG_DATA_HOME;\n const home = process.env.HOME || process.env.USERPROFILE || \"\";\n const base = xdg || path.join(home, \".local\", \"share\");\n return path.join(base, \"opencode\", \"opencode.db\");\n }\n\n openDb(): Database.Database | null {\n if (this.db) return this.db;\n if (!fs.existsSync(this.dbPath)) return null;\n\n try {\n this.db = new Database(this.dbPath, { readonly: true });\n return this.db;\n } catch {\n return null;\n }\n }\n\n close(): void {\n if (this.db) {\n try {\n this.db.close();\n } catch {\n /* ignore */\n }\n this.db = null;\n }\n }\n\n findSessionForDirectory(db: Database.Database, directory: string): OpenCodeSession | null {\n try {\n const row = db\n .prepare<[string], { id: string; directory: string; time_created: number }>(`\n SELECT id, directory, time_created\n FROM session\n WHERE directory = ?\n ORDER BY time_created DESC\n LIMIT 1\n `)\n .get(directory);\n\n if (!row) return null;\n return { sessionId: row.id, directory: row.directory, timeCreated: row.time_created };\n } catch {\n return null;\n }\n }\n\n listSessions(opts?: ListSessionsOptions): SessionSummary[] {\n const db = this.openDb();\n if (!db) return [];\n\n try {\n const rows = db\n .prepare<[], OpenCodeSessionRow>(`\n SELECT id, directory, time_created AS timeCreated\n FROM session\n ORDER BY time_created DESC\n `)\n .all();\n\n const summaries: SessionSummary[] = [];\n\n for (const row of rows) {\n if (opts?.cwd !== undefined && row.directory !== opts.cwd) continue;\n\n summaries.push(this.toSessionSummary(db, row));\n }\n\n return summaries;\n } catch {\n this.close();\n return [];\n }\n }\n\n findSessionsById(sessionId: string): SessionSummary[] {\n const db = this.openDb();\n if (!db) return [];\n\n try {\n const row = db\n .prepare<[string], OpenCodeSessionRow>(`\n SELECT id, directory, time_created AS timeCreated\n FROM session\n WHERE id = ?\n `)\n .get(sessionId);\n return row ? [this.toSessionSummary(db, row)] : [];\n } catch {\n this.close();\n return [];\n }\n }\n\n private toSessionSummary(db: Database.Database, row: OpenCodeSessionRow): SessionSummary {\n const stats = this.parser.getSessionStats(db, row.id);\n const lastActive =\n stats.lastTimeUpdated > 0 ? new Date(stats.lastTimeUpdated) : new Date(row.timeCreated);\n\n return {\n type: \"opencode\",\n sessionId: row.id,\n cwd: row.directory,\n firstUserMessage: stats.summary,\n lastActive,\n startedAt: new Date(row.timeCreated),\n sessionFilePath: encodeOpenCodeSessionRef(this.dbPath, row.id),\n };\n }\n\n get dbFilePath(): string {\n return this.dbPath;\n }\n}\n"],"names":["fs","path","Database","OpenCodeSessionParser","SESSION_REF_SEP","encodeOpenCodeSessionRef","dbPath","sessionId","OpenCodeSessionLocator","db","resolveDbPath","parser","xdg","process","env","XDG_DATA_HOME","home","HOME","USERPROFILE","base","join","openDb","existsSync","readonly","close","findSessionForDirectory","directory","row","prepare","get","id","timeCreated","time_created","listSessions","opts","rows","all","summaries","cwd","undefined","push","toSessionSummary","findSessionsById","stats","getSessionStats","lastActive","lastTimeUpdated","Date","type","firstUserMessage","summary","startedAt","sessionFilePath","dbFilePath"],"mappings":"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"}
@@ -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":"AAAA,YAAYA,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAU7B,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,oBAAoB,QACf,yBAAyB;AAChC,SAASC,aAAa,QAAQ,+BAA+B;AAC7D,SAASC,aAAa,QAAQ,qBAAqB;AACnD,SAASC,gBAAgB,QAAQ,wBAAwB;AACzD,SAASC,eAAe,QAAQ,uBAAuB;AACvD,SAASC,gBAAgB,QAAQ,wBAAwB;AAYzD,OAAO,MAAMC;IACFC,OAAO,KAAc;IACrBC,eAAe;QAAC;QAAM;KAAO,CAAU;IAE/BC,cAAsB;IACtBC,YAAoB;IACpBC,SAAwB;IACxBC,OAAwB;IACxBC,OAAsB;IAEvC,YAAYF,WAA0BV,cAAca,OAAO,EAAE,CAAE;QAC7D,MAAMC,UAAUC,QAAQC,GAAG,CAACC,IAAI,IAAIF,QAAQC,GAAG,CAACE,WAAW,IAAI;QAC/D,MAAMC,aAAavB,KAAKwB,IAAI,CAACN,SAAS,OAAO;QAC7C,IAAI,CAACN,aAAa,GAAGZ,KAAKwB,IAAI,CAACD,YAAY;QAC3C,IAAI,CAACV,WAAW,GAAGb,KAAKwB,IAAI,CAACD,YAAY;QACzC,IAAI,CAACT,QAAQ,GAAGA;QAChB,IAAI,CAACC,MAAM,GAAG,IAAIR;QAClB,IAAI,CAACS,MAAM,GAAG,IAAIX,cAAc,IAAI,CAACU,MAAM;IAC7C;IAEAU,UAAUC,WAAwB,EAAW;QAC3C,OAAO,IAAI,CAACC,cAAc,CAACD,YAAYE,OAAO;IAChD;IAEA,MAAMC,aAAaC,OAA+B,EAAwB;QACxE,MAAMC,YAAY,MAAM,IAAI,CAACC,cAAc,CAACF;QAC5C,IAAIC,UAAUE,MAAM,KAAK,GAAG,OAAO,EAAE;QAErC,MAAMC,cAAc,IAAI,CAACC,gBAAgB,CAACJ;QAC1C,MAAMK,gBAAgB,IAAI,CAACC,iBAAiB,CAACH,YAAYI,QAAQ;QACjE,MAAMC,gBAAgB,IAAI,CAACC,aAAa,GAAGC,qBAAqB,CAACL,cAAcE,QAAQ;QACvF,MAAMI,eAAe,IAAI,CAACC,gBAAgB,CAACJ,cAAcK,aAAa;QACtE,MAAMC,qBAAqB,IAAI,CAACC,sBAAsB,CACpDP,cAAcD,QAAQ,EACtBI,aAAaK,MAAM;QAGrB,OAAO;eACFb,YAAYa,MAAM;eAClBX,cAAcW,MAAM;eACpBL,aAAaK,MAAM;eACnBF,mBAAmBG,GAAG,CAAC,CAACtB,cAAgB,IAAI,CAACV,MAAM,CAACiC,mBAAmB,CAACvB;SAC5E;IACH;IAEAwB,gBAAgBC,eAAuB,EAAEC,OAA+B,EAAyB;QAC/F,OAAO,IAAI,CAACrC,MAAM,CAACmC,eAAe,CAACC,iBAAiBC;IACtD;IAEA,MAAMC,aAAaC,IAA0B,EAA6B;QACxE,MAAMC,YAA8B,EAAE;QAEtC,KAAK,MAAMC,YAAY,IAAI,CAAChB,aAAa,GAAGiB,8BAA8B,GAAI;YAC5E,MAAMC,UAAU,IAAI,CAAC3C,MAAM,CAAC4C,oBAAoB,CAACH;YACjD,IAAI,CAACE,SAAS;YACd,IAAIJ,MAAMM,QAAQC,aAAaH,QAAQE,GAAG,KAAKN,KAAKM,GAAG,EAAE;YACzDL,UAAUO,IAAI,CAACJ;QACjB;QAEA,OAAOH;IACT;IAEA,MAAcvB,eAAeF,OAA+B,EAA0B;QACpF,MAAMiC,WAAWjC,SAASC,aAAc,MAAM9B,uBAAuB,IAAI,CAACU,YAAY;QACtF,MAAMqD,WAAW7D,qBAAqB4D,UAAU,IAAI,CAACpD,YAAY;QAEjE,MAAMsD,QAAQ,IAAIC;QAClB,KAAK,MAAMxC,eAAesC,SAAU;YAClC,IAAI,IAAI,CAACvC,SAAS,CAACC,cAAcuC,MAAME,GAAG,CAACzC,YAAY0C,GAAG,EAAE1C;QAC9D;QACA,OAAO2C,MAAMC,IAAI,CAACL,MAAMM,MAAM;IAChC;IAEQ/B,gBAAkC;QACxC,OAAO,IAAIlC,iBAAiB;YAAEkE,aAAa,IAAI,CAAC5D,aAAa;QAAC,GAAG,IAAI,CAACG,MAAM;IAC9E;IAEQ0D,gBAAkC;QACxC,OAAO,IAAIjE,iBAAiB;YAC1BgE,aAAa,IAAI,CAAC5D,aAAa;YAC/BC,aAAa,IAAI,CAACA,WAAW;QAC/B;IACF;IAEQsB,iBAAiBJ,SAAwB,EAAqB;QACpE,MAAM2C,UAA8B,EAAE;QACtC,MAAMpC,WAA0B,EAAE;QAClC,MAAM2B,QAAQ,IAAIC,IAAI,IAAI,CAACpD,QAAQ,CAAC6D,IAAI,GAAG3B,GAAG,CAAC,CAAC4B,QAAU;gBAACA,MAAMR,GAAG;gBAAEQ;aAAM;QAE5E,KAAK,MAAMlD,eAAeK,UAAW;YACnC,MAAM6C,QAAQX,MAAMY,GAAG,CAACnD,YAAY0C,GAAG;YACvC,IACE,CAACQ,SACDA,MAAMlE,IAAI,KAAK,IAAI,CAACA,IAAI,IACxB,CAACkE,MAAMzB,eAAe,IACtB,CAACpD,GAAG+E,UAAU,CAACF,MAAMzB,eAAe,GACpC;gBACAb,SAASwB,IAAI,CAACpC;gBACd;YACF;YAEAgD,QAAQZ,IAAI,CAAC;gBAAE3C,SAASO;gBAAa8B,UAAUoB,MAAMzB,eAAe;YAAC;QACvE;QAEA,MAAM4B,SAAS,IAAI,CAACC,qBAAqB,CAACN;QAC1C,OAAO;YAAE3B,QAAQgC,OAAOhC,MAAM;YAAET,UAAU;mBAAIA;mBAAayC,OAAOzC,QAAQ;aAAC;QAAC;IAC9E;IAEQD,kBAAkBN,SAAwB,EAAqB;QACrE,MAAM,EAAE2C,OAAO,EAAEpC,QAAQ,EAAE,GAAG,IAAI,CAACmC,aAAa,GAAGQ,KAAK,CAAClD;QACzD,MAAMgD,SAAS,IAAI,CAACC,qBAAqB,CAACN;QAC1C,OAAO;YAAE3B,QAAQgC,OAAOhC,MAAM;YAAET,UAAU;mBAAIA;mBAAayC,OAAOzC,QAAQ;aAAC;QAAC;IAC9E;IAEQK,iBACN+B,OAAuE,EACpD;QACnB,OAAO,IAAI,CAACM,qBAAqB,CAC/BN,QAAQ1B,GAAG,CAAC,CAACiC,QAAW,CAAA;gBAAE9D,SAAS8D,MAAM9D,OAAO;gBAAEqC,UAAUyB,MAAMC,OAAO,CAAC1B,QAAQ;YAAC,CAAA;IAEvF;IAEQwB,sBAAsBN,OAA2B,EAAqB;QAC5E,MAAM3B,SAAsB,EAAE;QAC9B,MAAMT,WAA0B,EAAE;QAElC,KAAK,MAAM2C,SAASP,QAAS;YAC3B,MAAMQ,UAAU,IAAI,CAACnE,MAAM,CAACoE,WAAW,CAACF,MAAMzB,QAAQ,EAAEyB,MAAM9D,OAAO,CAACyC,GAAG;YACzE,IAAIsB,SAAS;gBACXnC,OAAOe,IAAI,CAAC,IAAI,CAAC9C,MAAM,CAACoE,iBAAiB,CAACF,SAASD,MAAM9D,OAAO,EAAE8D,MAAMzB,QAAQ;YAClF,OAAO;gBACLlB,SAASwB,IAAI,CAACmB,MAAM9D,OAAO;YAC7B;QACF;QAEA,OAAO;YAAE4B;YAAQT;QAAS;IAC5B;IAEQQ,uBAAuBf,SAAwB,EAAEgB,MAAmB,EAAiB;QAC3F,MAAMsC,cAAc,IAAIC,IAAIvC,OAAOC,GAAG,CAAC,CAACuC,QAAUA,MAAMnB,GAAG;QAC3D,OAAOrC,UAAUyD,MAAM,CAAC,CAAC9D,cAAgB,CAAC2D,YAAYI,GAAG,CAAC/D,YAAY0C,GAAG;IAC3E;IAEQzC,eAAeC,OAAe,EAAW;QAC/C,KAAK,MAAM8D,SAAS9D,QAAQ+D,IAAI,GAAGC,KAAK,CAAC,OAAQ;YAC/C,MAAMC,OAAO3F,mBAAmBwF;YAChC,IAAIG,SAAS,QAAQA,SAAS,YAAYA,SAAS,SAAS,OAAO;QACrE;QACA,OAAO;IACT;AACF"}
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":"AAAA,YAAYA,UAAU,OAAO;AAE7B,SAASC,wBAAwB,QAA0B,0BAA0B;AACrF,SAASC,WAAW,EAAEC,WAAW,EAAEC,QAAQ,QAA0B,yBAAyB;AAC9F,SAASC,eAAe,QAAQ,uBAAuB;AAWvD,OAAO,MAAMC;;IACMC,YAAoB;IAErC,YACEC,UAAmC,CAAC,CAAC,EACrC,AAAiBC,SAA0B,IAAIJ,iBAAiB,CAChE;aADiBI,SAAAA;QAEjB,MAAMC,UAAUC,QAAQC,GAAG,CAACC,IAAI,IAAIF,QAAQC,GAAG,CAACE,WAAW,IAAI;QAC/D,IAAI,CAACP,WAAW,GAAGC,QAAQD,WAAW,IAAIP,KAAKe,IAAI,CAACL,SAAS,OAAO,SAAS;IAC/E;IAEAM,sBAAsBC,SAAwB,EAA2B;QACvE,IAAIA,UAAUC,MAAM,KAAK,GAAG,OAAO;YAAEC,eAAe,EAAE;YAAEC,UAAU,EAAE;QAAC;QAErE,MAAMC,WAAW,IAAI,CAACC,oBAAoB,CAACL;QAC3C,IAAII,SAASH,MAAM,KAAK,GAAG,OAAO;YAAEC,eAAe,EAAE;YAAEC,UAAUH;QAAU;QAE3E,OAAO;YACLE,eAAelB,yBAAyBgB,WAAWI;YACnDD,UAAUH;QACZ;IACF;IAEAK,qBAAqBL,YAA2B,EAAE,EAAiB;QACjE,IAAI,CAACf,YAAY,IAAI,CAACK,WAAW,GAAG,OAAO,EAAE;QAE7C,MAAMgB,kBAAkB,IAAI,CAACC,qBAAqB,CAACP;QACnD,MAAMI,WAA0B,EAAE;QAClC,KAAK,MAAMI,YAAY,IAAI,CAACC,8BAA8B,GAAI;YAC5D,MAAMC,OAAOvB,SAASqB;YACtB,IAAI,CAACE,MAAM;YAEX,MAAMC,UAAU,IAAI,CAACnB,MAAM,CAACoB,WAAW,CAACJ;YACxC,MAAMK,YAAYF,SAASE,aAAa,IAAI,CAACrB,MAAM,CAACsB,iBAAiB,CAACN;YACtE,MAAMO,aAAahC,KAAKiC,OAAO,CAACR;YAChCJ,SAASa,IAAI,CAAC;gBACZJ;gBACAL;gBACAO;gBACAG,aAAaR,KAAKQ,WAAW,IAAIR,KAAKS,OAAO;gBAC7CC,aAAaT,SAASU,eAAef,gBAAgBgB,GAAG,CAACvC,KAAKwC,QAAQ,CAACR,gBAAgB;YACzF;QACF;QAEA,OAAOX;IACT;IAEAK,iCAA2C;QACzC,OAAO,IAAI,CAACe,iBAAiB,CAAC,IAAI,CAAClC,WAAW;IAChD;IAEQiB,sBAAsBP,SAAwB,EAAuB;QAC3E,MAAMyB,MAAM,IAAIC;QAChB,KAAK,MAAMC,QAAQ3B,UAAW;YAC5B,IAAI,CAAC2B,KAAKC,GAAG,EAAE;YACfH,IAAII,GAAG,CAAC,IAAI,CAACC,gBAAgB,CAACH,KAAKC,GAAG,GAAGD,KAAKC,GAAG;QACnD;QACA,OAAOH;IACT;IAEQD,kBAAkBO,GAAW,EAAY;QAC/C,MAAMC,QAAkB,EAAE;QAC1B,KAAK,MAAMC,SAAS/C,YAAY6C,KAAM;YACpC,MAAMG,WAAWnD,KAAKe,IAAI,CAACiC,KAAKE;YAChC,MAAMvB,OAAOvB,SAAS+C;YACtB,IAAI,CAACxB,MAAM;YACX,IAAIA,KAAKzB,WAAW,IAAI;gBACtB+C,MAAMf,IAAI,IAAI,IAAI,CAACO,iBAAiB,CAACU;YACvC,OAAO,IAAIxB,KAAKyB,MAAM,MAAMF,MAAMG,QAAQ,CAAC,WAAW;gBACpDJ,MAAMf,IAAI,CAACiB;YACb;QACF;QACA,OAAOF;IACT;IAEQF,iBAAiBF,GAAW,EAAU;QAC5C,MAAMS,aAAatD,KAAKuD,OAAO,CAACV;QAChC,OAAO,CAAC,EAAE,EAAES,WAAWE,OAAO,CAAC,OAAO,IAAIA,OAAO,CAAC,OAAO,KAAK,EAAE,CAAC;IACnE;AACF"}
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":"AAAA,YAAYA,UAAU,OAAO;AAE7B,SAASC,wBAAwB,QAA0B,0BAA0B;AACrF,SACEC,WAAW,EACXC,iBAAiB,EACjBC,WAAW,EACXC,QAAQ,QAEH,yBAAyB;AAChC,SAASC,eAAe,QAAQ,uBAAuB;AAWvD,OAAO,MAAMC;;IACMC,YAAoB;IAErC,YACEC,UAAmC,CAAC,CAAC,EACrC,AAAiBC,SAA0B,IAAIJ,iBAAiB,CAChE;aADiBI,SAAAA;QAEjB,MAAMC,UAAUC,QAAQC,GAAG,CAACC,IAAI,IAAIF,QAAQC,GAAG,CAACE,WAAW,IAAI;QAC/D,IAAI,CAACP,WAAW,GAAGC,QAAQD,WAAW,IAAIR,KAAKgB,IAAI,CAACL,SAAS,OAAO,SAAS;IAC/E;IAEAM,sBAAsBC,SAAwB,EAA2B;QACvE,IAAIA,UAAUC,MAAM,KAAK,GAAG,OAAO;YAAEC,eAAe,EAAE;YAAEC,UAAU,EAAE;QAAC;QAErE,MAAMC,WAAW,IAAI,CAACC,oBAAoB,CAACL;QAC3C,IAAII,SAASH,MAAM,KAAK,GAAG,OAAO;YAAEC,eAAe,EAAE;YAAEC,UAAUH;QAAU;QAE3E,OAAO;YACLE,eAAenB,yBAAyBiB,WAAWI;YACnDD,UAAUH;QACZ;IACF;IAEAK,qBAAqBL,YAA2B,EAAE,EAAiB;QACjE,IAAI,CAAChB,YAAY,IAAI,CAACM,WAAW,GAAG,OAAO,EAAE;QAE7C,MAAMgB,kBAAkB,IAAI,CAACC,qBAAqB,CAACP;QACnD,MAAMI,WAA0B,EAAE;QAClC,KAAK,MAAMI,YAAY,IAAI,CAACC,8BAA8B,GAAI;YAC5D,MAAMC,OAAOvB,SAASqB;YACtB,IAAI,CAACE,MAAM;YAEX,MAAMC,UAAU,IAAI,CAACnB,MAAM,CAACoB,WAAW,CAACJ;YACxC,MAAMK,YAAYF,SAASE,aAAa,IAAI,CAACrB,MAAM,CAACsB,iBAAiB,CAACN;YACtE,MAAMO,aAAajC,KAAKkC,OAAO,CAACR;YAChCJ,SAASa,IAAI,CAAC;gBACZJ;gBACAL;gBACAO;gBACAG,aAAaR,KAAKQ,WAAW,IAAIR,KAAKS,OAAO;gBAC7CC,aAAaT,SAASU,eAAef,gBAAgBgB,GAAG,CAACxC,KAAKyC,QAAQ,CAACR,gBAAgB;YACzF;QACF;QAEA,OAAOX;IACT;IAEAK,iCAA2C;QACzC,OAAO,IAAI,CAACe,iBAAiB,CAAC,IAAI,CAAClC,WAAW;IAChD;IAEAmC,+BAA+BZ,SAAiB,EAAY;QAC1D,IAAI,CAAC5B,kBAAkB4B,YAAY,OAAO,EAAE;QAE5C,OAAO,IAAI,CAACW,iBAAiB,CAAC,IAAI,CAAClC,WAAW,EAAEoC,MAAM,CAAC,CAAClB;YACtD,MAAMe,WAAWzC,KAAKyC,QAAQ,CAACf,UAAU;YACzC,OAAOe,aAAaV,aAAaU,SAASI,QAAQ,CAAC,CAAC,CAAC,EAAEd,WAAW;QACpE;IACF;IAEQN,sBAAsBP,SAAwB,EAAuB;QAC3E,MAAM4B,MAAM,IAAIC;QAChB,KAAK,MAAMC,QAAQ9B,UAAW;YAC5B,IAAI,CAAC8B,KAAKC,GAAG,EAAE;YACfH,IAAII,GAAG,CAAC,IAAI,CAACC,gBAAgB,CAACH,KAAKC,GAAG,GAAGD,KAAKC,GAAG;QACnD;QACA,OAAOH;IACT;IAEQJ,kBAAkBU,GAAW,EAAY;QAC/C,MAAMC,QAAkB,EAAE;QAC1B,KAAK,MAAMC,SAASlD,YAAYgD,KAAM;YACpC,MAAMG,WAAWvD,KAAKgB,IAAI,CAACoC,KAAKE;YAChC,MAAM1B,OAAOvB,SAASkD;YACtB,IAAI,CAAC3B,MAAM;YACX,IAAIA,KAAK1B,WAAW,IAAI;gBACtBmD,MAAMlB,IAAI,IAAI,IAAI,CAACO,iBAAiB,CAACa;YACvC,OAAO,IAAI3B,KAAK4B,MAAM,MAAMF,MAAMT,QAAQ,CAAC,WAAW;gBACpDQ,MAAMlB,IAAI,CAACoB;YACb;QACF;QACA,OAAOF;IACT;IAEQF,iBAAiBF,GAAW,EAAU;QAC5C,MAAMQ,aAAazD,KAAK0D,OAAO,CAACT;QAChC,OAAO,CAAC,EAAE,EAAEQ,WAAWE,OAAO,CAAC,OAAO,IAAIA,OAAO,CAAC,OAAO,KAAK,EAAE,CAAC;IACnE;AACF"}
@@ -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, ...).