@ai-devkit/agent-manager 0.15.0 → 0.17.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/.swcrc +27 -0
- package/dist/AgentManager.d.ts +4 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +97 -64
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +564 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js +1354 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
- package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
- package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
- package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
- package/dist/__tests__/utils/matching.test.js +324 -0
- package/dist/__tests__/utils/matching.test.js.map +1 -0
- package/dist/__tests__/utils/process.test.js +177 -0
- package/dist/__tests__/utils/process.test.js.map +1 -0
- package/dist/__tests__/utils/session.test.js +158 -0
- package/dist/__tests__/utils/session.test.js.map +1 -0
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js +8 -7
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +66 -77
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +13 -1
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +208 -132
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/CopilotAdapter.d.ts +44 -0
- package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
- package/dist/adapters/CopilotAdapter.js +314 -0
- package/dist/adapters/CopilotAdapter.js.map +1 -0
- package/dist/adapters/GeminiCliAdapter.d.ts +6 -1
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +160 -149
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +60 -69
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +62 -49
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TmuxManager.d.ts +28 -0
- package/dist/terminal/TmuxManager.d.ts.map +1 -0
- package/dist/terminal/TmuxManager.js +137 -0
- package/dist/terminal/TmuxManager.js.map +1 -0
- package/dist/terminal/TtyWriter.js +37 -16
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/terminal/index.js +1 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/utils/AgentRegistry.d.ts +35 -0
- package/dist/utils/AgentRegistry.d.ts.map +1 -0
- package/dist/utils/AgentRegistry.js +123 -0
- package/dist/utils/AgentRegistry.js.map +1 -0
- package/dist/utils/ClaudeSessionParser.d.ts.map +1 -1
- package/dist/utils/ClaudeSessionParser.js +62 -93
- package/dist/utils/ClaudeSessionParser.js.map +1 -1
- package/dist/utils/agents.d.ts +15 -0
- package/dist/utils/agents.d.ts.map +1 -0
- package/dist/utils/agents.js +41 -0
- package/dist/utils/agents.js.map +1 -0
- package/dist/utils/applescript.js +3 -6
- package/dist/utils/applescript.js.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/matching.js +21 -29
- package/dist/utils/matching.js.map +1 -1
- package/dist/utils/process.js +77 -56
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/session.js +20 -34
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/sortAgents.js +6 -8
- package/dist/utils/sortAgents.js.map +1 -1
- package/package.json +11 -7
- package/src/AgentManager.ts +40 -0
- package/src/__tests__/AgentManager.test.ts +135 -0
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +3 -2
- package/src/__tests__/adapters/CodexAdapter.test.ts +199 -0
- package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +185 -1
- package/src/__tests__/terminal/TmuxManager.test.ts +175 -0
- package/src/__tests__/utils/AgentRegistry.test.ts +223 -0
- package/src/__tests__/utils/ClaudeSessionParser.test.ts +71 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +155 -8
- package/src/adapters/CopilotAdapter.ts +453 -0
- package/src/adapters/GeminiCliAdapter.ts +84 -6
- package/src/adapters/index.ts +1 -0
- package/src/index.ts +7 -0
- package/src/terminal/TmuxManager.ts +118 -0
- package/src/utils/AgentRegistry.ts +139 -0
- package/src/utils/ClaudeSessionParser.ts +21 -10
- package/src/utils/agents.ts +41 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* Per-agent configuration: launch command plus a matcher that recognizes the
|
|
4
|
+
* agent's process in `ps` output. Each matcher knows that agent's distribution
|
|
5
|
+
* quirks (e.g. gemini ships as a Node script so its real binary is in argv[1..]).
|
|
6
|
+
*/ export const AGENTS = {
|
|
7
|
+
claude: {
|
|
8
|
+
command: 'claude',
|
|
9
|
+
matches: matchArgv0('claude')
|
|
10
|
+
},
|
|
11
|
+
codex: {
|
|
12
|
+
command: 'codex',
|
|
13
|
+
matches: matchArgv0('codex')
|
|
14
|
+
},
|
|
15
|
+
opencode: {
|
|
16
|
+
command: 'opencode',
|
|
17
|
+
matches: matchArgv0('opencode')
|
|
18
|
+
},
|
|
19
|
+
gemini_cli: {
|
|
20
|
+
command: 'gemini',
|
|
21
|
+
matches: matchAnyToken('gemini')
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function matchArgv0(name) {
|
|
25
|
+
const lower = name.toLowerCase();
|
|
26
|
+
return (psCommand)=>{
|
|
27
|
+
const token = psCommand.trim().split(/\s+/)[0];
|
|
28
|
+
return token ? path.basename(token).toLowerCase() === lower : false;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function matchAnyToken(name) {
|
|
32
|
+
const lower = name.toLowerCase();
|
|
33
|
+
return (psCommand)=>{
|
|
34
|
+
for (const token of psCommand.trim().split(/\s+/)){
|
|
35
|
+
if (path.basename(token).toLowerCase() === lower) return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/agents.ts"],"sourcesContent":["import path from 'path';\nimport type { AgentType } from '../adapters/AgentAdapter.js';\n\nexport type StartableAgentType = Extract<AgentType, 'claude' | 'codex' | 'gemini_cli' | 'opencode'>;\n\nexport interface AgentConfig {\n /** Shell command to launch the agent (sent to tmux via `send-keys`). */\n command: string;\n /** Returns true if the given `ps` command line belongs to this agent. */\n matches: (psCommand: string) => boolean;\n}\n\n/**\n * Per-agent configuration: launch command plus a matcher that recognizes the\n * agent's process in `ps` output. Each matcher knows that agent's distribution\n * quirks (e.g. gemini ships as a Node script so its real binary is in argv[1..]).\n */\nexport const AGENTS: Record<StartableAgentType, AgentConfig> = {\n claude: { command: 'claude', matches: matchArgv0('claude') },\n codex: { command: 'codex', matches: matchArgv0('codex') },\n opencode: { command: 'opencode', matches: matchArgv0('opencode') },\n gemini_cli: { command: 'gemini', matches: matchAnyToken('gemini') },\n};\n\nfunction matchArgv0(name: string): (psCommand: string) => boolean {\n const lower = name.toLowerCase();\n return (psCommand) => {\n const token = psCommand.trim().split(/\\s+/)[0];\n return token ? path.basename(token).toLowerCase() === lower : false;\n };\n}\n\nfunction matchAnyToken(name: string): (psCommand: string) => boolean {\n const lower = name.toLowerCase();\n return (psCommand) => {\n for (const token of psCommand.trim().split(/\\s+/)) {\n if (path.basename(token).toLowerCase() === lower) return true;\n }\n return false;\n };\n}\n"],"names":["path","AGENTS","claude","command","matches","matchArgv0","codex","opencode","gemini_cli","matchAnyToken","name","lower","toLowerCase","psCommand","token","trim","split","basename"],"mappings":"AAAA,OAAOA,UAAU,OAAO;AAYxB;;;;CAIC,GACD,OAAO,MAAMC,SAAkD;IAC3DC,QAAY;QAAEC,SAAS;QAAYC,SAASC,WAAW;IAAU;IACjEC,OAAY;QAAEH,SAAS;QAAYC,SAASC,WAAW;IAAS;IAChEE,UAAY;QAAEJ,SAAS;QAAYC,SAASC,WAAW;IAAY;IACnEG,YAAY;QAAEL,SAAS;QAAYC,SAASK,cAAc;IAAU;AACxE,EAAE;AAEF,SAASJ,WAAWK,IAAY;IAC5B,MAAMC,QAAQD,KAAKE,WAAW;IAC9B,OAAO,CAACC;QACJ,MAAMC,QAAQD,UAAUE,IAAI,GAAGC,KAAK,CAAC,MAAM,CAAC,EAAE;QAC9C,OAAOF,QAAQd,KAAKiB,QAAQ,CAACH,OAAOF,WAAW,OAAOD,QAAQ;IAClE;AACJ;AAEA,SAASF,cAAcC,IAAY;IAC/B,MAAMC,QAAQD,KAAKE,WAAW;IAC9B,OAAO,CAACC;QACJ,KAAK,MAAMC,SAASD,UAAUE,IAAI,GAAGC,KAAK,CAAC,OAAQ;YAC/C,IAAIhB,KAAKiB,QAAQ,CAACH,OAAOF,WAAW,OAAOD,OAAO,OAAO;QAC7D;QACA,OAAO;IACX;AACJ"}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Escape a string for safe use inside an AppleScript double-quoted string.
|
|
3
3
|
* Backslashes, double quotes, and newlines must be escaped.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
return text
|
|
7
|
-
.replace(/\\/g, '\\\\')
|
|
8
|
-
.replace(/"/g, '\\"')
|
|
9
|
-
.replace(/\r\n|\r|\n/g, '\\n');
|
|
4
|
+
*/ export function escapeAppleScript(text) {
|
|
5
|
+
return text.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r\n|\r|\n/g, '\\n');
|
|
10
6
|
}
|
|
7
|
+
|
|
11
8
|
//# sourceMappingURL=applescript.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/applescript.ts"],"sourcesContent":["/**\n * Escape a string for safe use inside an AppleScript double-quoted string.\n * Backslashes, double quotes, and newlines must be escaped.\n */\nexport function escapeAppleScript(text: string): string {\n return text\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\r\\n|\\r|\\n/g, '\\\\n');\n}\n"],"names":["escapeAppleScript","text","replace"],"mappings":"AAAA;;;CAGC,GACD,OAAO,SAASA,kBAAkBC,IAAY;IAC1C,OAAOA,KACFC,OAAO,CAAC,OAAO,QACfA,OAAO,CAAC,MAAM,OACdA,OAAO,CAAC,eAAe;AAChC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { listAgentProcesses, batchGetProcessCwds, batchGetProcessStartTimes, enr
|
|
|
2
2
|
export { getProcessTty } from './process.js';
|
|
3
3
|
export { batchGetSessionFileBirthtimes } from './session.js';
|
|
4
4
|
export { matchProcessesToSessions, generateAgentName } from './matching.js';
|
|
5
|
+
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["export { listAgentProcesses, batchGetProcessCwds, batchGetProcessStartTimes, enrichProcesses } from './process.js';\nexport { getProcessTty } from './process.js';\nexport { batchGetSessionFileBirthtimes } from './session.js';\nexport type { SessionFile } from './session.js';\nexport { matchProcessesToSessions, generateAgentName } from './matching.js';\nexport type { MatchResult } from './matching.js';\n"],"names":["listAgentProcesses","batchGetProcessCwds","batchGetProcessStartTimes","enrichProcesses","getProcessTty","batchGetSessionFileBirthtimes","matchProcessesToSessions","generateAgentName"],"mappings":"AAAA,SAASA,kBAAkB,EAAEC,mBAAmB,EAAEC,yBAAyB,EAAEC,eAAe,QAAQ,eAAe;AACnH,SAASC,aAAa,QAAQ,eAAe;AAC7C,SAASC,6BAA6B,QAAQ,eAAe;AAE7D,SAASC,wBAAwB,EAAEC,iBAAiB,QAAQ,gBAAgB"}
|
package/dist/utils/matching.js
CHANGED
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shared 1:1 greedy matching algorithm that pairs running processes with session files
|
|
5
5
|
* based on CWD and birth-time proximity to process start time.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** Maximum allowed delta between process start time and session file birth time. */
|
|
9
|
-
const TOLERANCE_MS = 3 * 60 * 1000; // 3 minutes
|
|
6
|
+
*/ import * as path from 'path';
|
|
7
|
+
/** Maximum allowed delta between process start time and session file birth time. */ const TOLERANCE_MS = 3 * 60 * 1000; // 3 minutes
|
|
10
8
|
/**
|
|
11
9
|
* Match processes to session files using 1:1 greedy assignment.
|
|
12
10
|
*
|
|
@@ -18,36 +16,33 @@ const TOLERANCE_MS = 3 * 60 * 1000; // 3 minutes
|
|
|
18
16
|
* 4. Greedily assign: once a process or session is matched, skip it.
|
|
19
17
|
*
|
|
20
18
|
* Adapters must set session.resolvedCwd before calling this function.
|
|
21
|
-
*/
|
|
22
|
-
export function matchProcessesToSessions(processes, sessions) {
|
|
19
|
+
*/ export function matchProcessesToSessions(processes, sessions) {
|
|
23
20
|
// Build all candidate pairs
|
|
24
21
|
const candidates = [];
|
|
25
|
-
for (const proc of processes)
|
|
26
|
-
if (!proc.startTime || !proc.cwd)
|
|
27
|
-
continue;
|
|
22
|
+
for (const proc of processes){
|
|
23
|
+
if (!proc.startTime || !proc.cwd) continue;
|
|
28
24
|
const processStartMs = proc.startTime.getTime();
|
|
29
|
-
for (const session of sessions)
|
|
30
|
-
if (!session.resolvedCwd)
|
|
31
|
-
|
|
32
|
-
if (proc.cwd !== session.resolvedCwd)
|
|
33
|
-
continue;
|
|
25
|
+
for (const session of sessions){
|
|
26
|
+
if (!session.resolvedCwd) continue;
|
|
27
|
+
if (proc.cwd !== session.resolvedCwd) continue;
|
|
34
28
|
const deltaMs = Math.abs(processStartMs - session.birthtimeMs);
|
|
35
|
-
if (deltaMs > TOLERANCE_MS)
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
if (deltaMs > TOLERANCE_MS) continue;
|
|
30
|
+
candidates.push({
|
|
31
|
+
process: proc,
|
|
32
|
+
session,
|
|
33
|
+
deltaMs
|
|
34
|
+
});
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
// Sort by smallest delta first
|
|
41
|
-
candidates.sort((a, b)
|
|
38
|
+
candidates.sort((a, b)=>a.deltaMs - b.deltaMs);
|
|
42
39
|
// Greedy 1:1 assignment
|
|
43
40
|
const matchedPids = new Set();
|
|
44
41
|
const matchedSessionIds = new Set();
|
|
45
42
|
const results = [];
|
|
46
|
-
for (const candidate of candidates)
|
|
47
|
-
if (matchedPids.has(candidate.process.pid))
|
|
48
|
-
|
|
49
|
-
if (matchedSessionIds.has(candidate.session.sessionId))
|
|
50
|
-
continue;
|
|
43
|
+
for (const candidate of candidates){
|
|
44
|
+
if (matchedPids.has(candidate.process.pid)) continue;
|
|
45
|
+
if (matchedSessionIds.has(candidate.session.sessionId)) continue;
|
|
51
46
|
matchedPids.add(candidate.process.pid);
|
|
52
47
|
matchedSessionIds.add(candidate.session.sessionId);
|
|
53
48
|
results.push(candidate);
|
|
@@ -58,13 +53,10 @@ export function matchProcessesToSessions(processes, sessions) {
|
|
|
58
53
|
* Generate a deterministic agent name from CWD and PID.
|
|
59
54
|
*
|
|
60
55
|
* Format: "folder-name-pid" (lowercase kebab-case)
|
|
61
|
-
*/
|
|
62
|
-
export function generateAgentName(cwd, pid) {
|
|
56
|
+
*/ export function generateAgentName(cwd, pid) {
|
|
63
57
|
const folderName = path.basename(cwd) || 'unknown';
|
|
64
|
-
const kebab = folderName
|
|
65
|
-
.toLowerCase()
|
|
66
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
67
|
-
.replace(/^-+|-+$/g, '');
|
|
58
|
+
const kebab = folderName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
68
59
|
return `${kebab || 'unknown'}-${pid}`;
|
|
69
60
|
}
|
|
61
|
+
|
|
70
62
|
//# sourceMappingURL=matching.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/matching.ts"],"sourcesContent":["/**\n * Session Matching Utilities\n *\n * Shared 1:1 greedy matching algorithm that pairs running processes with session files\n * based on CWD and birth-time proximity to process start time.\n */\n\nimport * as path from 'path';\nimport type { ProcessInfo } from '../adapters/AgentAdapter.js';\nimport type { SessionFile } from './session.js';\n\n/** Maximum allowed delta between process start time and session file birth time. */\nconst TOLERANCE_MS = 3 * 60 * 1000; // 3 minutes\n\n/**\n * Result of matching a process to a session file.\n */\nexport interface MatchResult {\n /** The matched process */\n process: ProcessInfo;\n\n /** The matched session file */\n session: SessionFile;\n\n /** Absolute time delta in ms between process start and session birth time */\n deltaMs: number;\n}\n\n/**\n * Match processes to session files using 1:1 greedy assignment.\n *\n * Algorithm:\n * 1. Exclude processes without startTime (they become process-only fallback).\n * 2. Build candidate pairs where process.cwd === session.resolvedCwd\n * and |process.startTime - session.birthtimeMs| <= 3 minutes.\n * 3. Sort candidates by deltaMs ascending (best matches first).\n * 4. Greedily assign: once a process or session is matched, skip it.\n *\n * Adapters must set session.resolvedCwd before calling this function.\n */\nexport function matchProcessesToSessions(\n processes: ProcessInfo[],\n sessions: SessionFile[],\n): MatchResult[] {\n // Build all candidate pairs\n const candidates: Array<{ process: ProcessInfo; session: SessionFile; deltaMs: number }> = [];\n\n for (const proc of processes) {\n if (!proc.startTime || !proc.cwd) continue;\n\n const processStartMs = proc.startTime.getTime();\n\n for (const session of sessions) {\n if (!session.resolvedCwd) continue;\n if (proc.cwd !== session.resolvedCwd) continue;\n\n const deltaMs = Math.abs(processStartMs - session.birthtimeMs);\n if (deltaMs > TOLERANCE_MS) continue;\n\n candidates.push({ process: proc, session, deltaMs });\n }\n }\n\n // Sort by smallest delta first\n candidates.sort((a, b) => a.deltaMs - b.deltaMs);\n\n // Greedy 1:1 assignment\n const matchedPids = new Set<number>();\n const matchedSessionIds = new Set<string>();\n const results: MatchResult[] = [];\n\n for (const candidate of candidates) {\n if (matchedPids.has(candidate.process.pid)) continue;\n if (matchedSessionIds.has(candidate.session.sessionId)) continue;\n\n matchedPids.add(candidate.process.pid);\n matchedSessionIds.add(candidate.session.sessionId);\n results.push(candidate);\n }\n\n return results;\n}\n\n/**\n * Generate a deterministic agent name from CWD and PID.\n *\n * Format: \"folder-name-pid\" (lowercase kebab-case)\n */\nexport function generateAgentName(cwd: string, pid: number): string {\n const folderName = path.basename(cwd) || 'unknown';\n const kebab = folderName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return `${kebab || 'unknown'}-${pid}`;\n}\n"],"names":["path","TOLERANCE_MS","matchProcessesToSessions","processes","sessions","candidates","proc","startTime","cwd","processStartMs","getTime","session","resolvedCwd","deltaMs","Math","abs","birthtimeMs","push","process","sort","a","b","matchedPids","Set","matchedSessionIds","results","candidate","has","pid","sessionId","add","generateAgentName","folderName","basename","kebab","toLowerCase","replace"],"mappings":"AAAA;;;;;CAKC,GAED,YAAYA,UAAU,OAAO;AAI7B,kFAAkF,GAClF,MAAMC,eAAe,IAAI,KAAK,MAAM,YAAY;AAgBhD;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,yBACZC,SAAwB,EACxBC,QAAuB;IAEvB,4BAA4B;IAC5B,MAAMC,aAAqF,EAAE;IAE7F,KAAK,MAAMC,QAAQH,UAAW;QAC1B,IAAI,CAACG,KAAKC,SAAS,IAAI,CAACD,KAAKE,GAAG,EAAE;QAElC,MAAMC,iBAAiBH,KAAKC,SAAS,CAACG,OAAO;QAE7C,KAAK,MAAMC,WAAWP,SAAU;YAC5B,IAAI,CAACO,QAAQC,WAAW,EAAE;YAC1B,IAAIN,KAAKE,GAAG,KAAKG,QAAQC,WAAW,EAAE;YAEtC,MAAMC,UAAUC,KAAKC,GAAG,CAACN,iBAAiBE,QAAQK,WAAW;YAC7D,IAAIH,UAAUZ,cAAc;YAE5BI,WAAWY,IAAI,CAAC;gBAAEC,SAASZ;gBAAMK;gBAASE;YAAQ;QACtD;IACJ;IAEA,+BAA+B;IAC/BR,WAAWc,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEP,OAAO,GAAGQ,EAAER,OAAO;IAE/C,wBAAwB;IACxB,MAAMS,cAAc,IAAIC;IACxB,MAAMC,oBAAoB,IAAID;IAC9B,MAAME,UAAyB,EAAE;IAEjC,KAAK,MAAMC,aAAarB,WAAY;QAChC,IAAIiB,YAAYK,GAAG,CAACD,UAAUR,OAAO,CAACU,GAAG,GAAG;QAC5C,IAAIJ,kBAAkBG,GAAG,CAACD,UAAUf,OAAO,CAACkB,SAAS,GAAG;QAExDP,YAAYQ,GAAG,CAACJ,UAAUR,OAAO,CAACU,GAAG;QACrCJ,kBAAkBM,GAAG,CAACJ,UAAUf,OAAO,CAACkB,SAAS;QACjDJ,QAAQR,IAAI,CAACS;IACjB;IAEA,OAAOD;AACX;AAEA;;;;CAIC,GACD,OAAO,SAASM,kBAAkBvB,GAAW,EAAEoB,GAAW;IACtD,MAAMI,aAAahC,KAAKiC,QAAQ,CAACzB,QAAQ;IACzC,MAAM0B,QAAQF,WACTG,WAAW,GACXC,OAAO,CAAC,eAAe,KACvBA,OAAO,CAAC,YAAY;IACzB,OAAO,GAAGF,SAAS,UAAU,CAAC,EAAEN,KAAK;AACzC"}
|
package/dist/utils/process.js
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shared shell command wrappers for detecting and inspecting running processes.
|
|
5
5
|
* All execFileSync calls for process data live here — adapters must not call execFileSync directly.
|
|
6
|
-
*/
|
|
7
|
-
import * as path from 'path';
|
|
6
|
+
*/ import * as path from 'path';
|
|
8
7
|
import { execFileSync } from 'child_process';
|
|
9
8
|
/**
|
|
10
9
|
* List running processes matching an agent executable name.
|
|
@@ -14,25 +13,25 @@ import { execFileSync } from 'child_process';
|
|
|
14
13
|
*
|
|
15
14
|
* Returned ProcessInfo has pid, command, tty populated.
|
|
16
15
|
* cwd and startTime are NOT populated — call enrichProcesses() to fill them.
|
|
17
|
-
*/
|
|
18
|
-
export function listAgentProcesses(namePattern) {
|
|
16
|
+
*/ export function listAgentProcesses(namePattern) {
|
|
19
17
|
// Validate pattern contains only safe characters (alphanumeric, dash, underscore)
|
|
20
18
|
if (!namePattern || !/^[a-zA-Z0-9_-]+$/.test(namePattern)) {
|
|
21
19
|
return [];
|
|
22
20
|
}
|
|
23
21
|
try {
|
|
24
|
-
const output = execFileSync('ps', [
|
|
22
|
+
const output = execFileSync('ps', [
|
|
23
|
+
'aux'
|
|
24
|
+
], {
|
|
25
|
+
encoding: 'utf-8'
|
|
26
|
+
});
|
|
25
27
|
const lowerPattern = namePattern.toLowerCase();
|
|
26
28
|
const processes = [];
|
|
27
|
-
for (const line of output.trim().split('\n'))
|
|
28
|
-
if (!line.trim())
|
|
29
|
-
continue;
|
|
29
|
+
for (const line of output.trim().split('\n')){
|
|
30
|
+
if (!line.trim()) continue;
|
|
30
31
|
const parts = line.trim().split(/\s+/);
|
|
31
|
-
if (parts.length < 11)
|
|
32
|
-
continue;
|
|
32
|
+
if (parts.length < 11) continue;
|
|
33
33
|
const pid = parseInt(parts[1], 10);
|
|
34
|
-
if (Number.isNaN(pid))
|
|
35
|
-
continue;
|
|
34
|
+
if (Number.isNaN(pid)) continue;
|
|
36
35
|
const tty = parts[6];
|
|
37
36
|
const command = parts.slice(10).join(' ');
|
|
38
37
|
// Check that the executable basename matches exactly
|
|
@@ -46,12 +45,11 @@ export function listAgentProcesses(namePattern) {
|
|
|
46
45
|
pid,
|
|
47
46
|
command,
|
|
48
47
|
cwd: '',
|
|
49
|
-
tty: ttyShort
|
|
48
|
+
tty: ttyShort
|
|
50
49
|
});
|
|
51
50
|
}
|
|
52
51
|
return processes;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
52
|
+
} catch {
|
|
55
53
|
return [];
|
|
56
54
|
}
|
|
57
55
|
}
|
|
@@ -60,37 +58,55 @@ export function listAgentProcesses(namePattern) {
|
|
|
60
58
|
*
|
|
61
59
|
* Single `lsof -a -d cwd -Fn -p PID1,PID2,...` call.
|
|
62
60
|
* Returns partial results — if lsof fails for one PID, others still return.
|
|
63
|
-
*/
|
|
64
|
-
export function batchGetProcessCwds(pids) {
|
|
61
|
+
*/ export function batchGetProcessCwds(pids) {
|
|
65
62
|
const result = new Map();
|
|
66
|
-
if (pids.length === 0)
|
|
67
|
-
return result;
|
|
63
|
+
if (pids.length === 0) return result;
|
|
68
64
|
try {
|
|
69
|
-
const output = execFileSync('lsof', [
|
|
65
|
+
const output = execFileSync('lsof', [
|
|
66
|
+
'-a',
|
|
67
|
+
'-d',
|
|
68
|
+
'cwd',
|
|
69
|
+
'-Fn',
|
|
70
|
+
'-p',
|
|
71
|
+
pids.join(',')
|
|
72
|
+
], {
|
|
73
|
+
encoding: 'utf-8',
|
|
74
|
+
stdio: [
|
|
75
|
+
'pipe',
|
|
76
|
+
'pipe',
|
|
77
|
+
'ignore'
|
|
78
|
+
]
|
|
79
|
+
});
|
|
70
80
|
// lsof output format: p{PID}\nn{path}\np{PID}\nn{path}...
|
|
71
81
|
let currentPid = null;
|
|
72
|
-
for (const line of output.trim().split('\n'))
|
|
82
|
+
for (const line of output.trim().split('\n')){
|
|
73
83
|
if (line.startsWith('p')) {
|
|
74
84
|
currentPid = parseInt(line.slice(1), 10);
|
|
75
|
-
}
|
|
76
|
-
else if (line.startsWith('n') && currentPid !== null) {
|
|
85
|
+
} else if (line.startsWith('n') && currentPid !== null) {
|
|
77
86
|
result.set(currentPid, line.slice(1));
|
|
78
87
|
currentPid = null;
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
90
|
+
} catch {
|
|
83
91
|
// Try per-PID fallback with pwdx (Linux)
|
|
84
|
-
for (const pid of pids)
|
|
92
|
+
for (const pid of pids){
|
|
85
93
|
try {
|
|
86
|
-
const output = execFileSync('pwdx', [
|
|
94
|
+
const output = execFileSync('pwdx', [
|
|
95
|
+
String(pid)
|
|
96
|
+
], {
|
|
97
|
+
encoding: 'utf-8',
|
|
98
|
+
stdio: [
|
|
99
|
+
'pipe',
|
|
100
|
+
'pipe',
|
|
101
|
+
'ignore'
|
|
102
|
+
]
|
|
103
|
+
});
|
|
87
104
|
const match = output.match(/^\d+:\s*(.+)$/);
|
|
88
105
|
if (match) {
|
|
89
106
|
result.set(pid, match[1].trim());
|
|
90
107
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Skip this PID
|
|
108
|
+
} catch {
|
|
109
|
+
// Skip this PID
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
}
|
|
@@ -102,34 +118,35 @@ export function batchGetProcessCwds(pids) {
|
|
|
102
118
|
* Single `ps -o pid=,lstart= -p PID1,PID2,...` call.
|
|
103
119
|
* Uses lstart format which gives full timestamp (e.g., "Thu Feb 5 16:00:57 2026").
|
|
104
120
|
* Returns partial results.
|
|
105
|
-
*/
|
|
106
|
-
export function batchGetProcessStartTimes(pids) {
|
|
121
|
+
*/ export function batchGetProcessStartTimes(pids) {
|
|
107
122
|
const result = new Map();
|
|
108
|
-
if (pids.length === 0)
|
|
109
|
-
return result;
|
|
123
|
+
if (pids.length === 0) return result;
|
|
110
124
|
try {
|
|
111
|
-
const output = execFileSync('ps', [
|
|
112
|
-
|
|
125
|
+
const output = execFileSync('ps', [
|
|
126
|
+
'-o',
|
|
127
|
+
'pid=,lstart=',
|
|
128
|
+
'-p',
|
|
129
|
+
pids.join(',')
|
|
130
|
+
], {
|
|
131
|
+
encoding: 'utf-8'
|
|
132
|
+
});
|
|
133
|
+
for (const rawLine of output.split('\n')){
|
|
113
134
|
const line = rawLine.trim();
|
|
114
|
-
if (!line)
|
|
115
|
-
continue;
|
|
135
|
+
if (!line) continue;
|
|
116
136
|
// Format: " PID DAY MON DD HH:MM:SS YYYY"
|
|
117
137
|
// e.g., " 78070 Wed Mar 18 23:18:01 2026"
|
|
118
138
|
const match = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
119
|
-
if (!match)
|
|
120
|
-
continue;
|
|
139
|
+
if (!match) continue;
|
|
121
140
|
const pid = parseInt(match[1], 10);
|
|
122
141
|
const dateStr = match[2].trim();
|
|
123
|
-
if (!Number.isFinite(pid))
|
|
124
|
-
continue;
|
|
142
|
+
if (!Number.isFinite(pid)) continue;
|
|
125
143
|
const date = new Date(dateStr);
|
|
126
144
|
if (!Number.isNaN(date.getTime())) {
|
|
127
145
|
result.set(pid, date);
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Return whatever we have
|
|
148
|
+
} catch {
|
|
149
|
+
// Return whatever we have
|
|
133
150
|
}
|
|
134
151
|
return result;
|
|
135
152
|
}
|
|
@@ -139,14 +156,12 @@ export function batchGetProcessStartTimes(pids) {
|
|
|
139
156
|
* Calls batchGetProcessCwds and batchGetProcessStartTimes in batched shell calls,
|
|
140
157
|
* then populates each ProcessInfo in-place. Returns partial results —
|
|
141
158
|
* if a PID fails, that process keeps empty cwd / undefined startTime.
|
|
142
|
-
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return processes;
|
|
146
|
-
const pids = processes.map(p => p.pid);
|
|
159
|
+
*/ export function enrichProcesses(processes) {
|
|
160
|
+
if (processes.length === 0) return processes;
|
|
161
|
+
const pids = processes.map((p)=>p.pid);
|
|
147
162
|
const cwdMap = batchGetProcessCwds(pids);
|
|
148
163
|
const startTimeMap = batchGetProcessStartTimes(pids);
|
|
149
|
-
for (const proc of processes)
|
|
164
|
+
for (const proc of processes){
|
|
150
165
|
proc.cwd = cwdMap.get(proc.pid) || '';
|
|
151
166
|
proc.startTime = startTimeMap.get(proc.pid);
|
|
152
167
|
}
|
|
@@ -154,15 +169,21 @@ export function enrichProcesses(processes) {
|
|
|
154
169
|
}
|
|
155
170
|
/**
|
|
156
171
|
* Get the TTY device for a specific process
|
|
157
|
-
*/
|
|
158
|
-
export function getProcessTty(pid) {
|
|
172
|
+
*/ export function getProcessTty(pid) {
|
|
159
173
|
try {
|
|
160
|
-
const output = execFileSync('ps', [
|
|
174
|
+
const output = execFileSync('ps', [
|
|
175
|
+
'-p',
|
|
176
|
+
String(pid),
|
|
177
|
+
'-o',
|
|
178
|
+
'tty='
|
|
179
|
+
], {
|
|
180
|
+
encoding: 'utf-8'
|
|
181
|
+
});
|
|
161
182
|
const tty = output.trim();
|
|
162
183
|
return tty.startsWith('/dev/') ? tty.slice(5) : tty;
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
184
|
+
} catch {
|
|
165
185
|
return '?';
|
|
166
186
|
}
|
|
167
187
|
}
|
|
188
|
+
|
|
168
189
|
//# sourceMappingURL=process.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/process.ts"],"sourcesContent":["/**\n * Process Detection Utilities\n *\n * Shared shell command wrappers for detecting and inspecting running processes.\n * All execFileSync calls for process data live here — adapters must not call execFileSync directly.\n */\n\nimport * as path from 'path';\nimport { execFileSync } from 'child_process';\nimport type { ProcessInfo } from '../adapters/AgentAdapter.js';\n\n/**\n * List running processes matching an agent executable name.\n *\n * Uses `ps aux` then filters in JS for exact executable basename match.\n * This avoids shell pipelines and string interpolation.\n *\n * Returned ProcessInfo has pid, command, tty populated.\n * cwd and startTime are NOT populated — call enrichProcesses() to fill them.\n */\nexport function listAgentProcesses(namePattern: string): ProcessInfo[] {\n // Validate pattern contains only safe characters (alphanumeric, dash, underscore)\n if (!namePattern || !/^[a-zA-Z0-9_-]+$/.test(namePattern)) {\n return [];\n }\n\n try {\n const output = execFileSync('ps', ['aux'], { encoding: 'utf-8' });\n\n const lowerPattern = namePattern.toLowerCase();\n const processes: ProcessInfo[] = [];\n\n for (const line of output.trim().split('\\n')) {\n if (!line.trim()) continue;\n\n const parts = line.trim().split(/\\s+/);\n if (parts.length < 11) continue;\n\n const pid = parseInt(parts[1], 10);\n if (Number.isNaN(pid)) continue;\n\n const tty = parts[6];\n const command = parts.slice(10).join(' ');\n\n // Check that the executable basename matches exactly\n const executable = command.trim().split(/\\s+/)[0] || '';\n const base = path.basename(executable).toLowerCase();\n if (base !== lowerPattern && base !== `${lowerPattern}.exe`) {\n continue;\n }\n\n const ttyShort = tty.startsWith('/dev/') ? tty.slice(5) : tty;\n\n processes.push({\n pid,\n command,\n cwd: '',\n tty: ttyShort,\n });\n }\n\n return processes;\n } catch {\n return [];\n }\n}\n\n/**\n * Batch-get current working directories for multiple PIDs.\n *\n * Single `lsof -a -d cwd -Fn -p PID1,PID2,...` call.\n * Returns partial results — if lsof fails for one PID, others still return.\n */\nexport function batchGetProcessCwds(pids: number[]): Map<number, string> {\n const result = new Map<number, string>();\n if (pids.length === 0) return result;\n\n try {\n const output = execFileSync(\n 'lsof', ['-a', '-d', 'cwd', '-Fn', '-p', pids.join(',')],\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },\n );\n\n // lsof output format: p{PID}\\nn{path}\\np{PID}\\nn{path}...\n let currentPid: number | null = null;\n for (const line of output.trim().split('\\n')) {\n if (line.startsWith('p')) {\n currentPid = parseInt(line.slice(1), 10);\n } else if (line.startsWith('n') && currentPid !== null) {\n result.set(currentPid, line.slice(1));\n currentPid = null;\n }\n }\n } catch {\n // Try per-PID fallback with pwdx (Linux)\n for (const pid of pids) {\n try {\n const output = execFileSync(\n 'pwdx', [String(pid)],\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },\n );\n const match = output.match(/^\\d+:\\s*(.+)$/);\n if (match) {\n result.set(pid, match[1].trim());\n }\n } catch {\n // Skip this PID\n }\n }\n }\n\n return result;\n}\n\n/**\n * Batch-get process start times for multiple PIDs.\n *\n * Single `ps -o pid=,lstart= -p PID1,PID2,...` call.\n * Uses lstart format which gives full timestamp (e.g., \"Thu Feb 5 16:00:57 2026\").\n * Returns partial results.\n */\nexport function batchGetProcessStartTimes(pids: number[]): Map<number, Date> {\n const result = new Map<number, Date>();\n if (pids.length === 0) return result;\n\n try {\n const output = execFileSync(\n 'ps', ['-o', 'pid=,lstart=', '-p', pids.join(',')],\n { encoding: 'utf-8' },\n );\n\n for (const rawLine of output.split('\\n')) {\n const line = rawLine.trim();\n if (!line) continue;\n\n // Format: \" PID DAY MON DD HH:MM:SS YYYY\"\n // e.g., \" 78070 Wed Mar 18 23:18:01 2026\"\n const match = line.match(/^\\s*(\\d+)\\s+(.+)$/);\n if (!match) continue;\n\n const pid = parseInt(match[1], 10);\n const dateStr = match[2].trim();\n\n if (!Number.isFinite(pid)) continue;\n\n const date = new Date(dateStr);\n if (!Number.isNaN(date.getTime())) {\n result.set(pid, date);\n }\n }\n } catch {\n // Return whatever we have\n }\n\n return result;\n}\n\n/**\n * Enrich ProcessInfo array with cwd and startTime.\n *\n * Calls batchGetProcessCwds and batchGetProcessStartTimes in batched shell calls,\n * then populates each ProcessInfo in-place. Returns partial results —\n * if a PID fails, that process keeps empty cwd / undefined startTime.\n */\nexport function enrichProcesses(processes: ProcessInfo[]): ProcessInfo[] {\n if (processes.length === 0) return processes;\n\n const pids = processes.map(p => p.pid);\n const cwdMap = batchGetProcessCwds(pids);\n const startTimeMap = batchGetProcessStartTimes(pids);\n\n for (const proc of processes) {\n proc.cwd = cwdMap.get(proc.pid) || '';\n proc.startTime = startTimeMap.get(proc.pid);\n }\n\n return processes;\n}\n\n/**\n * Get the TTY device for a specific process\n */\nexport function getProcessTty(pid: number): string {\n try {\n const output = execFileSync(\n 'ps', ['-p', String(pid), '-o', 'tty='],\n { encoding: 'utf-8' },\n );\n\n const tty = output.trim();\n return tty.startsWith('/dev/') ? tty.slice(5) : tty;\n } catch {\n return '?';\n }\n}\n"],"names":["path","execFileSync","listAgentProcesses","namePattern","test","output","encoding","lowerPattern","toLowerCase","processes","line","trim","split","parts","length","pid","parseInt","Number","isNaN","tty","command","slice","join","executable","base","basename","ttyShort","startsWith","push","cwd","batchGetProcessCwds","pids","result","Map","stdio","currentPid","set","String","match","batchGetProcessStartTimes","rawLine","dateStr","isFinite","date","Date","getTime","enrichProcesses","map","p","cwdMap","startTimeMap","proc","get","startTime","getProcessTty"],"mappings":"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"}
|
package/dist/utils/session.js
CHANGED
|
@@ -3,25 +3,21 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Utilities for discovering session files and their birth times.
|
|
5
5
|
* Uses Node.js fs APIs to get birth timestamps without reading file contents.
|
|
6
|
-
*/
|
|
7
|
-
import * as fs from 'fs';
|
|
6
|
+
*/ import * as fs from 'fs';
|
|
8
7
|
import * as path from 'path';
|
|
9
8
|
/**
|
|
10
9
|
* Check whether a path exists and is a directory.
|
|
11
10
|
* Returns false on any error (missing path, permission denied, broken symlink, etc.).
|
|
12
|
-
*/
|
|
13
|
-
export function isDirectory(p) {
|
|
11
|
+
*/ export function isDirectory(p) {
|
|
14
12
|
return safeStat(p)?.isDirectory() ?? false;
|
|
15
13
|
}
|
|
16
14
|
/**
|
|
17
15
|
* `fs.statSync` that swallows errors and returns `undefined` on failure.
|
|
18
16
|
* Callers can pull whichever fields they need (mtime, birthtime, ...).
|
|
19
|
-
*/
|
|
20
|
-
export function safeStat(filePath) {
|
|
17
|
+
*/ export function safeStat(filePath) {
|
|
21
18
|
try {
|
|
22
19
|
return fs.statSync(filePath);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
20
|
+
} catch {
|
|
25
21
|
return undefined;
|
|
26
22
|
}
|
|
27
23
|
}
|
|
@@ -29,12 +25,10 @@ export function safeStat(filePath) {
|
|
|
29
25
|
* `fs.readFileSync` (utf-8) that swallows errors and returns `undefined`
|
|
30
26
|
* on failure. Use when an unreadable file should be skipped rather than
|
|
31
27
|
* raised.
|
|
32
|
-
*/
|
|
33
|
-
export function safeReadFile(filePath) {
|
|
28
|
+
*/ export function safeReadFile(filePath) {
|
|
34
29
|
try {
|
|
35
30
|
return fs.readFileSync(filePath, 'utf-8');
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
31
|
+
} catch {
|
|
38
32
|
return undefined;
|
|
39
33
|
}
|
|
40
34
|
}
|
|
@@ -42,21 +36,18 @@ export function safeReadFile(filePath) {
|
|
|
42
36
|
* `fs.readdirSync` that swallows errors and returns `[]` on failure.
|
|
43
37
|
* Useful when walking optional/transient directories where missing or
|
|
44
38
|
* unreadable entries should be skipped silently.
|
|
45
|
-
*/
|
|
46
|
-
export function safeReaddir(dir) {
|
|
39
|
+
*/ export function safeReaddir(dir) {
|
|
47
40
|
try {
|
|
48
41
|
return fs.readdirSync(dir);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
42
|
+
} catch {
|
|
51
43
|
return [];
|
|
52
44
|
}
|
|
53
45
|
}
|
|
54
46
|
/**
|
|
55
47
|
* List entries in a directory that end with `.jsonl`. Returns `[]` on
|
|
56
48
|
* read errors. The result preserves directory order (no sorting).
|
|
57
|
-
*/
|
|
58
|
-
|
|
59
|
-
return safeReaddir(dir).filter((name) => name.endsWith('.jsonl'));
|
|
49
|
+
*/ export function listJsonl(dir) {
|
|
50
|
+
return safeReaddir(dir).filter((name)=>name.endsWith('.jsonl'));
|
|
60
51
|
}
|
|
61
52
|
/**
|
|
62
53
|
* Get birth times for .jsonl session files across multiple directories.
|
|
@@ -65,42 +56,37 @@ export function listJsonl(dir) {
|
|
|
65
56
|
* to get its birth time. No shell commands are used.
|
|
66
57
|
* Returns empty array if no directories have .jsonl files or reads fail.
|
|
67
58
|
* resolvedCwd is left empty — the adapter must set it.
|
|
68
|
-
*/
|
|
69
|
-
|
|
70
|
-
if (dirs.length === 0)
|
|
71
|
-
return [];
|
|
59
|
+
*/ export function batchGetSessionFileBirthtimes(dirs) {
|
|
60
|
+
if (dirs.length === 0) return [];
|
|
72
61
|
const results = [];
|
|
73
|
-
for (const dir of dirs)
|
|
62
|
+
for (const dir of dirs){
|
|
74
63
|
let entries;
|
|
75
64
|
try {
|
|
76
65
|
entries = fs.readdirSync(dir);
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
66
|
+
} catch {
|
|
79
67
|
continue;
|
|
80
68
|
}
|
|
81
|
-
for (const entry of entries)
|
|
82
|
-
if (!entry.endsWith('.jsonl'))
|
|
83
|
-
continue;
|
|
69
|
+
for (const entry of entries){
|
|
70
|
+
if (!entry.endsWith('.jsonl')) continue;
|
|
84
71
|
const filePath = path.join(dir, entry);
|
|
85
72
|
let birthtimeMs;
|
|
86
73
|
try {
|
|
87
74
|
birthtimeMs = fs.statSync(filePath).birthtimeMs;
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
75
|
+
} catch {
|
|
90
76
|
continue;
|
|
91
77
|
}
|
|
92
|
-
if (!Number.isFinite(birthtimeMs) || birthtimeMs <= 0)
|
|
93
|
-
continue;
|
|
78
|
+
if (!Number.isFinite(birthtimeMs) || birthtimeMs <= 0) continue;
|
|
94
79
|
const sessionId = entry.replace(/\.jsonl$/, '');
|
|
95
80
|
results.push({
|
|
96
81
|
sessionId,
|
|
97
82
|
filePath,
|
|
98
83
|
projectDir: dir,
|
|
99
84
|
birthtimeMs,
|
|
100
|
-
resolvedCwd: ''
|
|
85
|
+
resolvedCwd: ''
|
|
101
86
|
});
|
|
102
87
|
}
|
|
103
88
|
}
|
|
104
89
|
return results;
|
|
105
90
|
}
|
|
91
|
+
|
|
106
92
|
//# sourceMappingURL=session.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/session.ts"],"sourcesContent":["/**\n * Session File Utilities\n *\n * Utilities for discovering session files and their birth times.\n * Uses Node.js fs APIs to get birth timestamps without reading file contents.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\n/**\n * Represents a session file with its birth time metadata.\n */\nexport interface SessionFile {\n /** Session identifier (filename without .jsonl extension) */\n sessionId: string;\n\n /** Full path to the session file */\n filePath: string;\n\n /** Parent directory of the session file */\n projectDir: string;\n\n /** File creation time in milliseconds since epoch */\n birthtimeMs: number;\n\n /** CWD this session maps to — set by the adapter after calling batchGetSessionFileBirthtimes() */\n resolvedCwd: string;\n}\n\n/**\n * Check whether a path exists and is a directory.\n * Returns false on any error (missing path, permission denied, broken symlink, etc.).\n */\nexport function isDirectory(p: string): boolean {\n return safeStat(p)?.isDirectory() ?? false;\n}\n\n/**\n * `fs.statSync` that swallows errors and returns `undefined` on failure.\n * Callers can pull whichever fields they need (mtime, birthtime, ...).\n */\nexport function safeStat(filePath: string): fs.Stats | undefined {\n try {\n return fs.statSync(filePath);\n } catch {\n return undefined;\n }\n}\n\n/**\n * `fs.readFileSync` (utf-8) that swallows errors and returns `undefined`\n * on failure. Use when an unreadable file should be skipped rather than\n * raised.\n */\nexport function safeReadFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch {\n return undefined;\n }\n}\n\n/**\n * `fs.readdirSync` that swallows errors and returns `[]` on failure.\n * Useful when walking optional/transient directories where missing or\n * unreadable entries should be skipped silently.\n */\nexport function safeReaddir(dir: string): string[] {\n try {\n return fs.readdirSync(dir);\n } catch {\n return [];\n }\n}\n\n/**\n * List entries in a directory that end with `.jsonl`. Returns `[]` on\n * read errors. The result preserves directory order (no sorting).\n */\nexport function listJsonl(dir: string): string[] {\n return safeReaddir(dir).filter((name) => name.endsWith('.jsonl'));\n}\n\n/**\n * Get birth times for .jsonl session files across multiple directories.\n *\n * Enumerates each directory with readdirSync and stats each .jsonl file\n * to get its birth time. No shell commands are used.\n * Returns empty array if no directories have .jsonl files or reads fail.\n * resolvedCwd is left empty — the adapter must set it.\n */\nexport function batchGetSessionFileBirthtimes(dirs: string[]): SessionFile[] {\n if (dirs.length === 0) return [];\n\n const results: SessionFile[] = [];\n\n for (const dir of dirs) {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith('.jsonl')) continue;\n\n const filePath = path.join(dir, entry);\n\n let birthtimeMs: number;\n try {\n birthtimeMs = fs.statSync(filePath).birthtimeMs;\n } catch {\n continue;\n }\n\n if (!Number.isFinite(birthtimeMs) || birthtimeMs <= 0) continue;\n\n const sessionId = entry.replace(/\\.jsonl$/, '');\n\n results.push({\n sessionId,\n filePath,\n projectDir: dir,\n birthtimeMs,\n resolvedCwd: '',\n });\n }\n }\n\n return results;\n}\n"],"names":["fs","path","isDirectory","p","safeStat","filePath","statSync","undefined","safeReadFile","readFileSync","safeReaddir","dir","readdirSync","listJsonl","filter","name","endsWith","batchGetSessionFileBirthtimes","dirs","length","results","entries","entry","join","birthtimeMs","Number","isFinite","sessionId","replace","push","projectDir","resolvedCwd"],"mappings":"AAAA;;;;;CAKC,GAED,YAAYA,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAsB7B;;;CAGC,GACD,OAAO,SAASC,YAAYC,CAAS;IACjC,OAAOC,SAASD,IAAID,iBAAiB;AACzC;AAEA;;;CAGC,GACD,OAAO,SAASE,SAASC,QAAgB;IACrC,IAAI;QACA,OAAOL,GAAGM,QAAQ,CAACD;IACvB,EAAE,OAAM;QACJ,OAAOE;IACX;AACJ;AAEA;;;;CAIC,GACD,OAAO,SAASC,aAAaH,QAAgB;IACzC,IAAI;QACA,OAAOL,GAAGS,YAAY,CAACJ,UAAU;IACrC,EAAE,OAAM;QACJ,OAAOE;IACX;AACJ;AAEA;;;;CAIC,GACD,OAAO,SAASG,YAAYC,GAAW;IACnC,IAAI;QACA,OAAOX,GAAGY,WAAW,CAACD;IAC1B,EAAE,OAAM;QACJ,OAAO,EAAE;IACb;AACJ;AAEA;;;CAGC,GACD,OAAO,SAASE,UAAUF,GAAW;IACjC,OAAOD,YAAYC,KAAKG,MAAM,CAAC,CAACC,OAASA,KAAKC,QAAQ,CAAC;AAC3D;AAEA;;;;;;;CAOC,GACD,OAAO,SAASC,8BAA8BC,IAAc;IACxD,IAAIA,KAAKC,MAAM,KAAK,GAAG,OAAO,EAAE;IAEhC,MAAMC,UAAyB,EAAE;IAEjC,KAAK,MAAMT,OAAOO,KAAM;QACpB,IAAIG;QACJ,IAAI;YACAA,UAAUrB,GAAGY,WAAW,CAACD;QAC7B,EAAE,OAAM;YACJ;QACJ;QAEA,KAAK,MAAMW,SAASD,QAAS;YACzB,IAAI,CAACC,MAAMN,QAAQ,CAAC,WAAW;YAE/B,MAAMX,WAAWJ,KAAKsB,IAAI,CAACZ,KAAKW;YAEhC,IAAIE;YACJ,IAAI;gBACAA,cAAcxB,GAAGM,QAAQ,CAACD,UAAUmB,WAAW;YACnD,EAAE,OAAM;gBACJ;YACJ;YAEA,IAAI,CAACC,OAAOC,QAAQ,CAACF,gBAAgBA,eAAe,GAAG;YAEvD,MAAMG,YAAYL,MAAMM,OAAO,CAAC,YAAY;YAE5CR,QAAQS,IAAI,CAAC;gBACTF;gBACAtB;gBACAyB,YAAYnB;gBACZa;gBACAO,aAAa;YACjB;QACJ;IACJ;IAEA,OAAOX;AACX"}
|