@ai-devkit/agent-manager 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AgentManager.d.ts +2 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +25 -0
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +74 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/adapters/AntigravityCliAdapter.test.js +381 -0
- package/dist/__tests__/adapters/AntigravityCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +28 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/CodexAdapter.test.js +7 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/KiroAdapter.test.js +607 -0
- package/dist/__tests__/adapters/KiroAdapter.test.js.map +1 -0
- package/dist/__tests__/capacity/claude.test.js +485 -0
- package/dist/__tests__/capacity/claude.test.js.map +1 -0
- package/dist/__tests__/capacity/openai.test.js +672 -0
- package/dist/__tests__/capacity/openai.test.js.map +1 -0
- package/dist/__tests__/capacity/wham.test.js +144 -0
- package/dist/__tests__/capacity/wham.test.js.map +1 -0
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +18 -0
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
- package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
- package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
- package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
- package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +24 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
- package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -1
- package/dist/__tests__/utils/agents.test.js +6 -0
- package/dist/__tests__/utils/agents.test.js.map +1 -1
- package/dist/__tests__/utils/session.test.js +18 -1
- package/dist/__tests__/utils/session.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +9 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/AntigravityCliAdapter.d.ts +57 -0
- package/dist/adapters/AntigravityCliAdapter.d.ts.map +1 -0
- package/dist/adapters/AntigravityCliAdapter.js +273 -0
- package/dist/adapters/AntigravityCliAdapter.js.map +1 -0
- package/dist/adapters/KiroAdapter.d.ts +46 -0
- package/dist/adapters/KiroAdapter.d.ts.map +1 -0
- package/dist/adapters/KiroAdapter.js +344 -0
- package/dist/adapters/KiroAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +3 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/capacity/claude.d.ts +17 -0
- package/dist/capacity/claude.d.ts.map +1 -0
- package/dist/capacity/claude.js +271 -0
- package/dist/capacity/claude.js.map +1 -0
- package/dist/capacity/codex.d.ts +1 -2
- package/dist/capacity/codex.d.ts.map +1 -1
- package/dist/capacity/codex.js +5 -47
- package/dist/capacity/codex.js.map +1 -1
- package/dist/capacity/index.d.ts +11 -0
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +17 -0
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/openai.d.ts +48 -0
- package/dist/capacity/openai.d.ts.map +1 -0
- package/dist/capacity/openai.js +312 -0
- package/dist/capacity/openai.js.map +1 -0
- package/dist/capacity/wham.d.ts +16 -0
- package/dist/capacity/wham.d.ts.map +1 -0
- package/dist/capacity/wham.js +66 -0
- package/dist/capacity/wham.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
- package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
- package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
- package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
- package/dist/providers/codex/CodexAdapter.d.ts +1 -0
- package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
- package/dist/providers/codex/CodexAdapter.js +8 -0
- package/dist/providers/codex/CodexAdapter.js.map +1 -1
- package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
- package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
- package/dist/providers/copilot/CopilotAdapter.js +24 -9
- package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
- package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
- package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
- package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
- package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
- package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
- package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
- package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
- package/dist/providers/grok/GrokAgentMapper.js +36 -0
- package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
- package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
- package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
- package/dist/providers/grok/GrokCliAdapter.js +98 -0
- package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
- package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
- package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
- package/dist/providers/grok/GrokSessionLocator.js +140 -0
- package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
- package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
- package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
- package/dist/providers/grok/GrokSessionParser.js +122 -0
- package/dist/providers/grok/GrokSessionParser.js.map +1 -0
- package/dist/providers/opencode/OpenCodeAdapter.d.ts +1 -0
- package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -1
- package/dist/providers/opencode/OpenCodeAdapter.js +3 -0
- package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -1
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +2 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -1
- package/dist/providers/opencode/OpenCodeSessionLocator.js +31 -12
- package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -1
- package/dist/providers/pi/PiAdapter.d.ts +1 -0
- package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
- package/dist/providers/pi/PiAdapter.js +10 -0
- package/dist/providers/pi/PiAdapter.js.map +1 -1
- package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
- package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
- package/dist/providers/pi/PiSessionLocator.js +8 -1
- package/dist/providers/pi/PiSessionLocator.js.map +1 -1
- package/dist/readiness/AgentReadiness.d.ts +1 -1
- package/dist/readiness/AgentReadiness.d.ts.map +1 -1
- package/dist/readiness/AgentReadiness.js.map +1 -1
- package/dist/runtime/ManagedAgentRuntime.js +3 -1
- package/dist/runtime/ManagedAgentRuntime.js.map +1 -1
- package/dist/runtime/herdr/HerdrAgentDiscovery.js +10 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -1
- package/dist/utils/agents.d.ts +1 -1
- package/dist/utils/agents.d.ts.map +1 -1
- package/dist/utils/agents.js +11 -0
- package/dist/utils/agents.js.map +1 -1
- package/dist/utils/session.d.ts +2 -0
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +3 -0
- package/dist/utils/session.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +34 -0
- package/src/__tests__/AgentManager.test.ts +60 -0
- package/src/__tests__/adapters/AntigravityCliAdapter.test.ts +324 -0
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
- package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
- package/src/__tests__/adapters/KiroAdapter.test.ts +534 -0
- package/src/__tests__/capacity/claude.test.ts +467 -0
- package/src/__tests__/capacity/fixtures/claude-credentials.json +7 -0
- package/src/__tests__/capacity/fixtures/claude-usage-malformed.json +1 -0
- package/src/__tests__/capacity/fixtures/claude-usage-partial.json +15 -0
- package/src/__tests__/capacity/fixtures/claude-usage.json +63 -0
- package/src/__tests__/capacity/fixtures/openai-auth.json +6 -0
- package/src/__tests__/capacity/fixtures/openai-codex-auth.json +9 -0
- package/src/__tests__/capacity/fixtures/openai-usage-completions.json +67 -0
- package/src/__tests__/capacity/fixtures/openai-usage-responses.json +20 -0
- package/src/__tests__/capacity/fixtures/wham-usage.json +32 -0
- package/src/__tests__/capacity/openai.test.ts +570 -0
- package/src/__tests__/capacity/wham.test.ts +104 -0
- package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
- package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
- package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
- package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
- package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
- package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +18 -0
- package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
- package/src/__tests__/utils/agents.test.ts +7 -0
- package/src/__tests__/utils/session.test.ts +11 -1
- package/src/adapters/AgentAdapter.ts +11 -0
- package/src/adapters/AntigravityCliAdapter.ts +356 -0
- package/src/adapters/KiroAdapter.ts +456 -0
- package/src/adapters/index.ts +3 -1
- package/src/capacity/claude.ts +313 -0
- package/src/capacity/codex.ts +25 -134
- package/src/capacity/index.ts +33 -4
- package/src/capacity/openai.ts +361 -0
- package/src/capacity/wham.ts +84 -0
- package/src/index.ts +11 -2
- package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
- package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
- package/src/providers/codex/CodexAdapter.ts +8 -0
- package/src/providers/copilot/CopilotAdapter.ts +26 -10
- package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
- package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
- package/src/providers/grok/GrokAgentMapper.ts +41 -0
- package/src/providers/grok/GrokCliAdapter.ts +125 -0
- package/src/providers/grok/GrokSessionLocator.ts +179 -0
- package/src/providers/grok/GrokSessionParser.ts +166 -0
- package/src/providers/opencode/OpenCodeAdapter.ts +4 -0
- package/src/providers/opencode/OpenCodeSessionLocator.ts +43 -15
- package/src/providers/pi/PiAdapter.ts +12 -0
- package/src/providers/pi/PiSessionLocator.ts +16 -1
- package/src/readiness/AgentReadiness.ts +4 -1
- package/src/runtime/ManagedAgentRuntime.ts +2 -0
- package/src/runtime/herdr/HerdrAgentDiscovery.ts +5 -1
- package/src/utils/agents.ts +11 -1
- package/src/utils/session.ts +11 -0
- package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
- package/dist/adapters/GrokCliAdapter.d.ts +0 -80
- package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
- package/dist/adapters/GrokCliAdapter.js +0 -310
- package/dist/adapters/GrokCliAdapter.js.map +0 -1
- package/src/adapters/GrokCliAdapter.ts +0 -408
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AgentInfo, ProcessInfo } from "../../adapters/AgentAdapter.js";
|
|
2
|
+
import { AgentStatus } from "../../adapters/AgentAdapter.js";
|
|
3
|
+
import { generateAgentName } from "../../utils/matching.js";
|
|
4
|
+
import { GrokSessionParser, type GrokSession } from "./GrokSessionParser.js";
|
|
5
|
+
|
|
6
|
+
export interface GrokSessionAgentInput {
|
|
7
|
+
session: GrokSession;
|
|
8
|
+
processInfo: ProcessInfo;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class GrokAgentMapper {
|
|
12
|
+
constructor(private readonly parser: GrokSessionParser = new GrokSessionParser()) {}
|
|
13
|
+
|
|
14
|
+
mapSessionToAgent({ session, processInfo }: GrokSessionAgentInput): AgentInfo {
|
|
15
|
+
return {
|
|
16
|
+
name: generateAgentName(session.projectPath, processInfo.pid),
|
|
17
|
+
type: "grok_cli",
|
|
18
|
+
status: this.parser.determineStatus(session),
|
|
19
|
+
summary: session.lastUserMessage || "Grok CLI session active",
|
|
20
|
+
pid: processInfo.pid,
|
|
21
|
+
projectPath: session.projectPath,
|
|
22
|
+
sessionId: session.sessionId,
|
|
23
|
+
lastActive: session.lastActive,
|
|
24
|
+
sessionFilePath: session.sessionFilePath,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** `cwd` is already resolved by the locator (registry first, then process cwd). */
|
|
29
|
+
mapProcessOnlyAgent(processInfo: ProcessInfo, cwd: string): AgentInfo {
|
|
30
|
+
return {
|
|
31
|
+
name: generateAgentName(cwd, processInfo.pid),
|
|
32
|
+
type: "grok_cli",
|
|
33
|
+
status: AgentStatus.RUNNING,
|
|
34
|
+
summary: "Grok CLI process running",
|
|
35
|
+
pid: processInfo.pid,
|
|
36
|
+
projectPath: cwd,
|
|
37
|
+
sessionId: `pid-${processInfo.pid}`,
|
|
38
|
+
lastActive: new Date(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentAdapter,
|
|
3
|
+
AgentInfo,
|
|
4
|
+
ProcessInfo,
|
|
5
|
+
ConversationMessage,
|
|
6
|
+
SessionSummary,
|
|
7
|
+
ListSessionsOptions,
|
|
8
|
+
AgentDetectionContext,
|
|
9
|
+
} from "../../adapters/AgentAdapter.js";
|
|
10
|
+
import {
|
|
11
|
+
captureProcessSnapshot,
|
|
12
|
+
executableBasename,
|
|
13
|
+
filterByProcessNames,
|
|
14
|
+
} from "../../utils/process.js";
|
|
15
|
+
import { GrokAgentMapper } from "./GrokAgentMapper.js";
|
|
16
|
+
import { GrokSessionLocator } from "./GrokSessionLocator.js";
|
|
17
|
+
import { GrokSessionParser, type GrokSession } from "./GrokSessionParser.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Grok Build CLI Adapter
|
|
21
|
+
*
|
|
22
|
+
* Detects running Grok Build CLI agents by:
|
|
23
|
+
* 1. Filtering `grok` processes from a shared asynchronous snapshot — Grok is
|
|
24
|
+
* a native binary at ~/.grok/bin/grok, so argv[0] basename is `grok`.
|
|
25
|
+
* 2. Resolving each live process to its working directory via
|
|
26
|
+
* ~/.grok/active_sessions.json, which Grok maintains as a list of
|
|
27
|
+
* { pid, cwd, opened_at } for every running session. The cwd is then encoded
|
|
28
|
+
* into the session group dir ~/.grok/sessions/<encodeURIComponent(cwd)>/, and
|
|
29
|
+
* the most recently active session subdirectory is picked from it. The
|
|
30
|
+
* process cwd from lsof is only a fallback when the PID is not registered.
|
|
31
|
+
* 3. Reading the session transcript from chat_history.jsonl (the authoritative
|
|
32
|
+
* record of the conversation). The last user turn (the text inside
|
|
33
|
+
* <user_query>...</user_query>) is the summary; the file's mtime is the last
|
|
34
|
+
* activity time. summary.json / updates.jsonl are intentionally not used.
|
|
35
|
+
*/
|
|
36
|
+
export class GrokCliAdapter implements AgentAdapter {
|
|
37
|
+
readonly type = "grok_cli" as const;
|
|
38
|
+
readonly processNames = ["grok"] as const;
|
|
39
|
+
|
|
40
|
+
private parser: GrokSessionParser;
|
|
41
|
+
private mapper: GrokAgentMapper;
|
|
42
|
+
private locator: GrokSessionLocator;
|
|
43
|
+
|
|
44
|
+
constructor() {
|
|
45
|
+
this.parser = new GrokSessionParser();
|
|
46
|
+
this.mapper = new GrokAgentMapper(this.parser);
|
|
47
|
+
this.locator = new GrokSessionLocator();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
canHandle(processInfo: ProcessInfo): boolean {
|
|
51
|
+
return this.isGrokExecutable(processInfo.command);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private isGrokExecutable(command: string): boolean {
|
|
55
|
+
const base = executableBasename(command);
|
|
56
|
+
return base === "grok" || base === "grok.exe";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
60
|
+
const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));
|
|
61
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
62
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
63
|
+
if (processes.length === 0) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const agents: AgentInfo[] = [];
|
|
68
|
+
for (const { process: proc, cwd, sessionDir } of this.locator.matchRunningProcesses(
|
|
69
|
+
processes,
|
|
70
|
+
)) {
|
|
71
|
+
const session = sessionDir ? this.parser.readSession(sessionDir, cwd) : null;
|
|
72
|
+
if (session) {
|
|
73
|
+
agents.push(this.mapper.mapSessionToAgent({ session, processInfo: proc }));
|
|
74
|
+
} else {
|
|
75
|
+
agents.push(this.mapper.mapProcessOnlyAgent(proc, cwd));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return agents;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
|
|
82
|
+
return this.parser.getConversation(sessionFilePath, options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
|
|
86
|
+
const filterCwd = opts?.cwd;
|
|
87
|
+
const summaries: SessionSummary[] = [];
|
|
88
|
+
|
|
89
|
+
for (const { sessionDir, defaultCwd } of this.locator.discoverHistoricalSessionDirs()) {
|
|
90
|
+
const session = this.parser.readSession(sessionDir, defaultCwd);
|
|
91
|
+
if (!session) continue;
|
|
92
|
+
|
|
93
|
+
if (filterCwd !== undefined && session.projectPath !== filterCwd) continue;
|
|
94
|
+
|
|
95
|
+
summaries.push(this.toSessionSummary(session));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return summaries;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async findSessionsById(sessionId: string): Promise<SessionSummary[]> {
|
|
102
|
+
const summaries: SessionSummary[] = [];
|
|
103
|
+
for (const { sessionDir, defaultCwd } of this.locator.findHistoricalSessionDirsById(
|
|
104
|
+
sessionId,
|
|
105
|
+
)) {
|
|
106
|
+
const session = this.parser.readSession(sessionDir, defaultCwd);
|
|
107
|
+
if (!session) continue;
|
|
108
|
+
|
|
109
|
+
summaries.push(this.toSessionSummary(session));
|
|
110
|
+
}
|
|
111
|
+
return summaries;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private toSessionSummary(session: GrokSession): SessionSummary {
|
|
115
|
+
return {
|
|
116
|
+
type: this.type,
|
|
117
|
+
sessionId: session.sessionId,
|
|
118
|
+
cwd: session.projectPath,
|
|
119
|
+
firstUserMessage: session.firstUserMessage || "",
|
|
120
|
+
lastActive: session.lastActive,
|
|
121
|
+
startedAt: session.sessionStart,
|
|
122
|
+
sessionFilePath: session.sessionFilePath,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import type { ProcessInfo } from "../../adapters/AgentAdapter.js";
|
|
3
|
+
import {
|
|
4
|
+
isDirectory,
|
|
5
|
+
isSafePathSegment,
|
|
6
|
+
safeReadFile,
|
|
7
|
+
safeReaddir,
|
|
8
|
+
safeStat,
|
|
9
|
+
} from "../../utils/session.js";
|
|
10
|
+
import { CHAT_HISTORY_FILE } from "./GrokSessionParser.js";
|
|
11
|
+
|
|
12
|
+
const ACTIVE_SESSIONS_FILE = "active_sessions.json";
|
|
13
|
+
const CWD_FILE = ".cwd";
|
|
14
|
+
|
|
15
|
+
/** One entry of ~/.grok/active_sessions.json. */
|
|
16
|
+
interface ActiveSessionEntry {
|
|
17
|
+
pid?: number;
|
|
18
|
+
cwd?: string;
|
|
19
|
+
opened_at?: number | string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A live Grok process paired with its resolved cwd and (if any) session dir. */
|
|
23
|
+
export interface GrokProcessMatch {
|
|
24
|
+
process: ProcessInfo;
|
|
25
|
+
cwd: string;
|
|
26
|
+
sessionDir: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A historical session dir plus the cwd decoded from its group dir. */
|
|
30
|
+
export interface GrokSessionDir {
|
|
31
|
+
sessionDir: string;
|
|
32
|
+
defaultCwd: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface GrokSessionLocatorOptions {
|
|
36
|
+
/** Overrides the ~/.grok base directory (defaults to GROK_HOME or ~/.grok). */
|
|
37
|
+
baseDir?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class GrokSessionLocator {
|
|
41
|
+
private readonly baseDir: string;
|
|
42
|
+
private readonly sessionsDir: string;
|
|
43
|
+
|
|
44
|
+
constructor(options: GrokSessionLocatorOptions = {}) {
|
|
45
|
+
// GROK_HOME overrides the ~/.grok base directory; sessions live under
|
|
46
|
+
// <base>/sessions/ and the active-session registry at
|
|
47
|
+
// <base>/active_sessions.json.
|
|
48
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
49
|
+
this.baseDir = options.baseDir ?? (process.env.GROK_HOME || path.join(homeDir, ".grok"));
|
|
50
|
+
this.sessionsDir = path.join(this.baseDir, "sessions");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Pair live Grok processes with their session dirs.
|
|
55
|
+
*
|
|
56
|
+
* active_sessions.json is the authoritative pid -> cwd map for live
|
|
57
|
+
* sessions; the lsof-derived process cwd is only a fallback. The most
|
|
58
|
+
* recently active session under that cwd's group dir is picked.
|
|
59
|
+
*/
|
|
60
|
+
matchRunningProcesses(processes: ProcessInfo[]): GrokProcessMatch[] {
|
|
61
|
+
const pidToCwd = this.readActiveSessions();
|
|
62
|
+
return processes.map((proc) => {
|
|
63
|
+
const cwd = pidToCwd.get(proc.pid) || proc.cwd || "";
|
|
64
|
+
return { process: proc, cwd, sessionDir: cwd ? this.latestSessionDir(cwd) : null };
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Every session dir under ~/.grok/sessions/, across all project groups. */
|
|
69
|
+
discoverHistoricalSessionDirs(): GrokSessionDir[] {
|
|
70
|
+
const results: GrokSessionDir[] = [];
|
|
71
|
+
for (const { groupDir, defaultCwd } of this.listGroups()) {
|
|
72
|
+
for (const sessionDir of this.listSessionDirs(groupDir)) {
|
|
73
|
+
results.push({ sessionDir, defaultCwd });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Session dirs named exactly `sessionId`, across all project groups. */
|
|
80
|
+
findHistoricalSessionDirsById(sessionId: string): GrokSessionDir[] {
|
|
81
|
+
if (!isSafePathSegment(sessionId)) return [];
|
|
82
|
+
|
|
83
|
+
const results: GrokSessionDir[] = [];
|
|
84
|
+
for (const { groupDir, defaultCwd } of this.listGroups()) {
|
|
85
|
+
const sessionDir = path.join(groupDir, sessionId);
|
|
86
|
+
if (isDirectory(sessionDir)) {
|
|
87
|
+
results.push({ sessionDir, defaultCwd });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return results;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Read ~/.grok/active_sessions.json into a pid -> cwd map. Grok writes one
|
|
95
|
+
* { pid, cwd, opened_at } entry per running session and removes it on exit,
|
|
96
|
+
* so this is the reliable way to learn a live process's working directory.
|
|
97
|
+
*/
|
|
98
|
+
private readActiveSessions(): Map<number, string> {
|
|
99
|
+
const map = new Map<number, string>();
|
|
100
|
+
const content = safeReadFile(path.join(this.baseDir, ACTIVE_SESSIONS_FILE));
|
|
101
|
+
if (content === undefined) return map;
|
|
102
|
+
|
|
103
|
+
let entries: unknown;
|
|
104
|
+
try {
|
|
105
|
+
entries = JSON.parse(content);
|
|
106
|
+
} catch {
|
|
107
|
+
return map;
|
|
108
|
+
}
|
|
109
|
+
if (!Array.isArray(entries)) return map;
|
|
110
|
+
|
|
111
|
+
for (const entry of entries as ActiveSessionEntry[]) {
|
|
112
|
+
if (typeof entry?.pid === "number" && typeof entry?.cwd === "string" && entry.cwd) {
|
|
113
|
+
map.set(entry.pid, entry.cwd);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return map;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Return the most recently active session subdirectory for a cwd, i.e. the
|
|
121
|
+
* ~/.grok/sessions/<encodeURIComponent(cwd)>/<id>/ whose chat_history.jsonl
|
|
122
|
+
* was written last. Returns null when the group dir or any transcript is
|
|
123
|
+
* missing.
|
|
124
|
+
*/
|
|
125
|
+
private latestSessionDir(cwd: string): string | null {
|
|
126
|
+
const groupDir = path.join(this.sessionsDir, encodeURIComponent(cwd));
|
|
127
|
+
if (!isDirectory(groupDir)) return null;
|
|
128
|
+
|
|
129
|
+
let best: { dir: string; mtimeMs: number } | null = null;
|
|
130
|
+
for (const sessionDir of this.listSessionDirs(groupDir)) {
|
|
131
|
+
const stat = safeStat(path.join(sessionDir, CHAT_HISTORY_FILE));
|
|
132
|
+
if (!stat) continue;
|
|
133
|
+
if (!best || stat.mtimeMs > best.mtimeMs) {
|
|
134
|
+
best = { dir: sessionDir, mtimeMs: stat.mtimeMs };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return best?.dir ?? null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private listGroups(): Array<{ groupDir: string; defaultCwd: string }> {
|
|
141
|
+
if (!isDirectory(this.sessionsDir)) return [];
|
|
142
|
+
|
|
143
|
+
const groups: Array<{ groupDir: string; defaultCwd: string }> = [];
|
|
144
|
+
for (const groupName of safeReaddir(this.sessionsDir)) {
|
|
145
|
+
const groupDir = path.join(this.sessionsDir, groupName);
|
|
146
|
+
if (!isDirectory(groupDir)) continue;
|
|
147
|
+
groups.push({ groupDir, defaultCwd: this.decodeGroupCwd(groupName, groupDir) });
|
|
148
|
+
}
|
|
149
|
+
return groups;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Full paths of the session subdirectories directly under a group dir,
|
|
154
|
+
* skipping any non-directory entries (e.g. prompt_history.jsonl).
|
|
155
|
+
*/
|
|
156
|
+
private listSessionDirs(groupDir: string): string[] {
|
|
157
|
+
return safeReaddir(groupDir)
|
|
158
|
+
.map((sessionId) => path.join(groupDir, sessionId))
|
|
159
|
+
.filter((sessionDir) => isDirectory(sessionDir));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the working directory a session group dir was created for.
|
|
164
|
+
*
|
|
165
|
+
* The common case is `decodeURIComponent(<group-name>)`. For paths whose
|
|
166
|
+
* encoded form exceeds the filesystem limit Grok uses a slug+hash and records
|
|
167
|
+
* the original path in a `.cwd` file inside the group — prefer that when
|
|
168
|
+
* present.
|
|
169
|
+
*/
|
|
170
|
+
private decodeGroupCwd(groupName: string, groupDir: string): string {
|
|
171
|
+
const fromFile = safeReadFile(path.join(groupDir, CWD_FILE));
|
|
172
|
+
if (fromFile !== undefined && fromFile.trim()) return fromFile.trim();
|
|
173
|
+
try {
|
|
174
|
+
return decodeURIComponent(groupName);
|
|
175
|
+
} catch {
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import type { ConversationMessage } from "../../adapters/AgentAdapter.js";
|
|
3
|
+
import { AgentStatus } from "../../adapters/AgentAdapter.js";
|
|
4
|
+
import { safeReadFile, safeStat } from "../../utils/session.js";
|
|
5
|
+
|
|
6
|
+
export const CHAT_HISTORY_FILE = "chat_history.jsonl";
|
|
7
|
+
const IDLE_THRESHOLD_MINUTES = 5;
|
|
8
|
+
|
|
9
|
+
/** One line of chat_history.jsonl. */
|
|
10
|
+
interface ChatRecord {
|
|
11
|
+
type?: string;
|
|
12
|
+
content?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ChatScan {
|
|
16
|
+
messages: ConversationMessage[];
|
|
17
|
+
firstUserMessage?: string;
|
|
18
|
+
lastUserMessage?: string;
|
|
19
|
+
lastRole?: ConversationMessage["role"];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Parsed state for a single ~/.grok/sessions/<cwd>/<id>/ directory. */
|
|
23
|
+
export interface GrokSession {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
projectPath: string;
|
|
26
|
+
sessionFilePath: string;
|
|
27
|
+
sessionStart: Date;
|
|
28
|
+
lastActive: Date;
|
|
29
|
+
firstUserMessage?: string;
|
|
30
|
+
lastUserMessage?: string;
|
|
31
|
+
lastRole?: ConversationMessage["role"];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class GrokSessionParser {
|
|
35
|
+
/**
|
|
36
|
+
* Parse a session directory into a {@link GrokSession} from its
|
|
37
|
+
* chat_history.jsonl transcript. Returns null when the transcript is
|
|
38
|
+
* missing — i.e. there is no real session to surface.
|
|
39
|
+
*/
|
|
40
|
+
readSession(sessionDir: string, defaultCwd: string): GrokSession | null {
|
|
41
|
+
const chatPath = path.join(sessionDir, CHAT_HISTORY_FILE);
|
|
42
|
+
const chatStat = safeStat(chatPath);
|
|
43
|
+
if (!chatStat) return null;
|
|
44
|
+
|
|
45
|
+
const scan = this.parseChatHistory(chatPath, false);
|
|
46
|
+
const dirStat = safeStat(sessionDir);
|
|
47
|
+
const lastActive = chatStat.mtime;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
sessionId: path.basename(sessionDir),
|
|
51
|
+
projectPath: defaultCwd,
|
|
52
|
+
sessionFilePath: chatPath,
|
|
53
|
+
sessionStart: dirStat?.birthtime || lastActive,
|
|
54
|
+
lastActive,
|
|
55
|
+
firstUserMessage: scan.firstUserMessage,
|
|
56
|
+
lastUserMessage: scan.lastUserMessage,
|
|
57
|
+
lastRole: scan.lastRole,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Accepts a session dir or an explicit chat_history.jsonl path. */
|
|
62
|
+
getConversation(sessionPath: string, options?: { verbose?: boolean }): ConversationMessage[] {
|
|
63
|
+
return this.parseChatHistory(this.resolveChatPath(sessionPath), options?.verbose ?? false)
|
|
64
|
+
.messages;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Determine agent status from parsed session state.
|
|
69
|
+
*
|
|
70
|
+
* - past the idle threshold → IDLE
|
|
71
|
+
* - last transcript turn is an assistant message → WAITING (awaiting user)
|
|
72
|
+
* - otherwise (last turn was a user message, or unknown) → RUNNING
|
|
73
|
+
*/
|
|
74
|
+
determineStatus(session: GrokSession): AgentStatus {
|
|
75
|
+
const diffMinutes = (Date.now() - session.lastActive.getTime()) / 60000;
|
|
76
|
+
if (diffMinutes > IDLE_THRESHOLD_MINUTES) {
|
|
77
|
+
return AgentStatus.IDLE;
|
|
78
|
+
}
|
|
79
|
+
if (session.lastRole === "assistant") {
|
|
80
|
+
return AgentStatus.WAITING;
|
|
81
|
+
}
|
|
82
|
+
return AgentStatus.RUNNING;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Single pass over chat_history.jsonl. Each line is a
|
|
87
|
+
* { type: 'system' | 'user' | 'assistant', content } record where content is
|
|
88
|
+
* either a string or an array of { type: 'text', text } blocks.
|
|
89
|
+
*
|
|
90
|
+
* Grok wraps the real user prompt in <user_query>...</user_query>; the other
|
|
91
|
+
* user records are context injections (<user_info>, <system-reminder>, ...)
|
|
92
|
+
* and are skipped so the summary is the actual prompt, not boilerplate.
|
|
93
|
+
*/
|
|
94
|
+
private parseChatHistory(chatPath: string, verbose: boolean): ChatScan {
|
|
95
|
+
const content = safeReadFile(chatPath);
|
|
96
|
+
if (content === undefined) return { messages: [] };
|
|
97
|
+
|
|
98
|
+
const messages: ConversationMessage[] = [];
|
|
99
|
+
let lastRole: ConversationMessage["role"] | undefined;
|
|
100
|
+
|
|
101
|
+
for (const line of content.trim().split("\n")) {
|
|
102
|
+
if (!line.trim()) continue;
|
|
103
|
+
|
|
104
|
+
let record: ChatRecord;
|
|
105
|
+
try {
|
|
106
|
+
record = JSON.parse(line);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const text = this.extractText(record.content);
|
|
112
|
+
if (record.type === "user") {
|
|
113
|
+
const query = this.extractUserQuery(text);
|
|
114
|
+
if (query === null) continue; // context injection, not a real prompt
|
|
115
|
+
messages.push({ role: "user", content: query });
|
|
116
|
+
lastRole = "user";
|
|
117
|
+
} else if (record.type === "assistant") {
|
|
118
|
+
if (!text) continue;
|
|
119
|
+
messages.push({ role: "assistant", content: text });
|
|
120
|
+
lastRole = "assistant";
|
|
121
|
+
} else if (verbose && record.type === "system") {
|
|
122
|
+
if (!text) continue;
|
|
123
|
+
messages.push({ role: "system", content: text });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const userTurns = messages.filter((m) => m.role === "user");
|
|
128
|
+
return {
|
|
129
|
+
messages,
|
|
130
|
+
firstUserMessage: userTurns[0]?.content,
|
|
131
|
+
lastUserMessage: userTurns[userTurns.length - 1]?.content,
|
|
132
|
+
lastRole,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Flatten a chat record's content (string or text-block array) to text. */
|
|
137
|
+
private extractText(content: unknown): string {
|
|
138
|
+
if (typeof content === "string") return content;
|
|
139
|
+
if (Array.isArray(content)) {
|
|
140
|
+
return content
|
|
141
|
+
.map((block) =>
|
|
142
|
+
block &&
|
|
143
|
+
typeof block === "object" &&
|
|
144
|
+
typeof (block as { text?: unknown }).text === "string"
|
|
145
|
+
? (block as { text: string }).text
|
|
146
|
+
: "",
|
|
147
|
+
)
|
|
148
|
+
.join("");
|
|
149
|
+
}
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Extract the prompt inside <user_query>...</user_query>. Returns null when
|
|
155
|
+
* the record has no such tag (a context injection rather than a prompt).
|
|
156
|
+
*/
|
|
157
|
+
private extractUserQuery(text: string): string | null {
|
|
158
|
+
const match = text.match(/<user_query>\s*([\s\S]*?)\s*<\/user_query>/);
|
|
159
|
+
return match ? match[1].trim() : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Resolve a session dir or an explicit chat_history.jsonl path to the file. */
|
|
163
|
+
private resolveChatPath(sessionPath: string): string {
|
|
164
|
+
return sessionPath.endsWith(".jsonl") ? sessionPath : path.join(sessionPath, CHAT_HISTORY_FILE);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -109,4 +109,8 @@ export class OpenCodeAdapter implements AgentAdapter {
|
|
|
109
109
|
async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
|
|
110
110
|
return this.locator.listSessions(opts);
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
async findSessionsById(sessionId: string): Promise<SessionSummary[]> {
|
|
114
|
+
return this.locator.findSessionsById(sessionId);
|
|
115
|
+
}
|
|
112
116
|
}
|
|
@@ -16,6 +16,12 @@ export interface OpenCodeSession {
|
|
|
16
16
|
timeCreated: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
interface OpenCodeSessionRow {
|
|
20
|
+
id: string;
|
|
21
|
+
directory: string;
|
|
22
|
+
timeCreated: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
export class OpenCodeSessionLocator {
|
|
20
26
|
private readonly dbPath: string;
|
|
21
27
|
private db: Database.Database | null = null;
|
|
@@ -82,7 +88,7 @@ export class OpenCodeSessionLocator {
|
|
|
82
88
|
|
|
83
89
|
try {
|
|
84
90
|
const rows = db
|
|
85
|
-
.prepare<[],
|
|
91
|
+
.prepare<[], OpenCodeSessionRow>(`
|
|
86
92
|
SELECT id, directory, time_created AS timeCreated
|
|
87
93
|
FROM session
|
|
88
94
|
ORDER BY time_created DESC
|
|
@@ -94,20 +100,7 @@ export class OpenCodeSessionLocator {
|
|
|
94
100
|
for (const row of rows) {
|
|
95
101
|
if (opts?.cwd !== undefined && row.directory !== opts.cwd) continue;
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
const lastActive =
|
|
99
|
-
stats.lastTimeUpdated > 0 ? new Date(stats.lastTimeUpdated) : new Date(row.timeCreated);
|
|
100
|
-
const startedAt = new Date(row.timeCreated);
|
|
101
|
-
|
|
102
|
-
summaries.push({
|
|
103
|
-
type: "opencode",
|
|
104
|
-
sessionId: row.id,
|
|
105
|
-
cwd: row.directory,
|
|
106
|
-
firstUserMessage: stats.summary,
|
|
107
|
-
lastActive,
|
|
108
|
-
startedAt,
|
|
109
|
-
sessionFilePath: encodeOpenCodeSessionRef(this.dbPath, row.id),
|
|
110
|
-
});
|
|
103
|
+
summaries.push(this.toSessionSummary(db, row));
|
|
111
104
|
}
|
|
112
105
|
|
|
113
106
|
return summaries;
|
|
@@ -117,6 +110,41 @@ export class OpenCodeSessionLocator {
|
|
|
117
110
|
}
|
|
118
111
|
}
|
|
119
112
|
|
|
113
|
+
findSessionsById(sessionId: string): SessionSummary[] {
|
|
114
|
+
const db = this.openDb();
|
|
115
|
+
if (!db) return [];
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const row = db
|
|
119
|
+
.prepare<[string], OpenCodeSessionRow>(`
|
|
120
|
+
SELECT id, directory, time_created AS timeCreated
|
|
121
|
+
FROM session
|
|
122
|
+
WHERE id = ?
|
|
123
|
+
`)
|
|
124
|
+
.get(sessionId);
|
|
125
|
+
return row ? [this.toSessionSummary(db, row)] : [];
|
|
126
|
+
} catch {
|
|
127
|
+
this.close();
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private toSessionSummary(db: Database.Database, row: OpenCodeSessionRow): SessionSummary {
|
|
133
|
+
const stats = this.parser.getSessionStats(db, row.id);
|
|
134
|
+
const lastActive =
|
|
135
|
+
stats.lastTimeUpdated > 0 ? new Date(stats.lastTimeUpdated) : new Date(row.timeCreated);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
type: "opencode",
|
|
139
|
+
sessionId: row.id,
|
|
140
|
+
cwd: row.directory,
|
|
141
|
+
firstUserMessage: stats.summary,
|
|
142
|
+
lastActive,
|
|
143
|
+
startedAt: new Date(row.timeCreated),
|
|
144
|
+
sessionFilePath: encodeOpenCodeSessionRef(this.dbPath, row.id),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
120
148
|
get dbFilePath(): string {
|
|
121
149
|
return this.dbPath;
|
|
122
150
|
}
|
|
@@ -92,6 +92,18 @@ export class PiAdapter implements AgentAdapter {
|
|
|
92
92
|
return summaries;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
async findSessionsById(sessionId: string): Promise<SessionSummary[]> {
|
|
96
|
+
const summaries: SessionSummary[] = [];
|
|
97
|
+
const filePaths = this.createLocator().findHistoricalSessionFilesById(sessionId);
|
|
98
|
+
for (const filePath of filePaths) {
|
|
99
|
+
const summary = this.parser.fileToSessionSummary(filePath);
|
|
100
|
+
if (summary?.sessionId === sessionId) summaries.push(summary);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (summaries.length > 0) return summaries;
|
|
104
|
+
return (await this.listSessions()).filter((summary) => summary.sessionId === sessionId);
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
private async getPiProcesses(context?: AgentDetectionContext): Promise<ProcessInfo[]> {
|
|
96
108
|
const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));
|
|
97
109
|
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import type { ProcessInfo } from "../../adapters/AgentAdapter.js";
|
|
3
3
|
import { matchProcessesToSessions, type MatchResult } from "../../utils/matching.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isDirectory,
|
|
6
|
+
isSafePathSegment,
|
|
7
|
+
safeReaddir,
|
|
8
|
+
safeStat,
|
|
9
|
+
type SessionFile,
|
|
10
|
+
} from "../../utils/session.js";
|
|
5
11
|
import { PiSessionParser } from "./PiSessionParser.js";
|
|
6
12
|
|
|
7
13
|
export interface PiSessionLocatorOptions {
|
|
@@ -64,6 +70,15 @@ export class PiSessionLocator {
|
|
|
64
70
|
return this.collectJsonlFiles(this.sessionsDir);
|
|
65
71
|
}
|
|
66
72
|
|
|
73
|
+
findHistoricalSessionFilesById(sessionId: string): string[] {
|
|
74
|
+
if (!isSafePathSegment(sessionId)) return [];
|
|
75
|
+
|
|
76
|
+
return this.collectJsonlFiles(this.sessionsDir).filter((filePath) => {
|
|
77
|
+
const basename = path.basename(filePath, ".jsonl");
|
|
78
|
+
return basename === sessionId || basename.endsWith(`_${sessionId}`);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
private buildProjectDirCwdMap(processes: ProcessInfo[]): Map<string, string> {
|
|
68
83
|
const map = new Map<string, string>();
|
|
69
84
|
for (const proc of processes) {
|
|
@@ -12,7 +12,10 @@ const ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]
|
|
|
12
12
|
|
|
13
13
|
export type ReadinessStatus = "pass" | "warn" | "fail";
|
|
14
14
|
export type ReadinessAuthState = "authenticated" | "unauthenticated" | "unknown";
|
|
15
|
-
export type ReadinessAgentType = Exclude<
|
|
15
|
+
export type ReadinessAgentType = Exclude<
|
|
16
|
+
StartableAgentType,
|
|
17
|
+
"gemini_cli" | "kiro" | "antigravity_cli"
|
|
18
|
+
>;
|
|
16
19
|
type ReadinessInfoStatus = ReadinessStatus | "info";
|
|
17
20
|
|
|
18
21
|
type CommandResult = { stdout: string; stderr: string };
|
|
@@ -260,7 +260,9 @@ function agentRuntimeKind(type: StartableAgentType, provider: AgentRuntimeProvid
|
|
|
260
260
|
copilot: "copilot",
|
|
261
261
|
gemini_cli: "gemini",
|
|
262
262
|
grok_cli: "grok",
|
|
263
|
+
antigravity_cli: "agy",
|
|
263
264
|
opencode: "opencode",
|
|
264
265
|
pi: "pi",
|
|
266
|
+
kiro: "kiro",
|
|
265
267
|
}[type];
|
|
266
268
|
}
|