@ai-devkit/agent-manager 0.26.1 → 0.26.3
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 +5 -2
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +21 -4
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +184 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +13 -4
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/CodexAdapter.test.js +13 -4
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/CopilotAdapter.test.js +7 -2
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +18 -5
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/GrokCliAdapter.test.js +7 -2
- package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +13 -4
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/PiAdapter.test.js +32 -4
- package/dist/__tests__/adapters/PiAdapter.test.js.map +1 -1
- package/dist/__tests__/utils/AgentRegistry.test.js +68 -46
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
- package/dist/__tests__/utils/process.test.js +90 -2
- package/dist/__tests__/utils/process.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +9 -3
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.d.ts +5 -4
- package/dist/adapters/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +11 -7
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +5 -4
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +11 -7
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/CopilotAdapter.d.ts +5 -4
- package/dist/adapters/CopilotAdapter.d.ts.map +1 -1
- package/dist/adapters/CopilotAdapter.js +11 -7
- package/dist/adapters/CopilotAdapter.js.map +1 -1
- package/dist/adapters/GeminiCliAdapter.d.ts +6 -5
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +12 -8
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/adapters/GrokCliAdapter.d.ts +3 -2
- package/dist/adapters/GrokCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GrokCliAdapter.js +10 -6
- package/dist/adapters/GrokCliAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.d.ts +5 -4
- package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +11 -7
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/PiAdapter.d.ts +3 -2
- package/dist/adapters/PiAdapter.d.ts.map +1 -1
- package/dist/adapters/PiAdapter.js +12 -9
- package/dist/adapters/PiAdapter.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/database/connection.d.ts +22 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +58 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/index.d.ts +4 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +4 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/migrations/001_initial.sql +12 -0
- package/dist/database/schema.d.ts +4 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +47 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/AgentRegistry.d.ts +7 -3
- package/dist/utils/AgentRegistry.d.ts.map +1 -1
- package/dist/utils/AgentRegistry.js +108 -57
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/process.d.ts +9 -1
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +155 -10
- package/dist/utils/process.js.map +1 -1
- package/package.json +2 -2
- package/src/AgentManager.ts +28 -4
- package/src/__tests__/AgentManager.test.ts +126 -0
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +16 -5
- package/src/__tests__/adapters/CodexAdapter.test.ts +16 -5
- package/src/__tests__/adapters/CopilotAdapter.test.ts +8 -1
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +20 -4
- package/src/__tests__/adapters/GrokCliAdapter.test.ts +8 -1
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +16 -5
- package/src/__tests__/adapters/PiAdapter.test.ts +28 -5
- package/src/__tests__/utils/AgentRegistry.test.ts +48 -40
- package/src/__tests__/utils/process.test.ts +72 -1
- package/src/adapters/AgentAdapter.ts +11 -3
- package/src/adapters/ClaudeCodeAdapter.ts +10 -7
- package/src/adapters/CodexAdapter.ts +10 -7
- package/src/adapters/CopilotAdapter.ts +16 -7
- package/src/adapters/GeminiCliAdapter.ts +17 -8
- package/src/adapters/GrokCliAdapter.ts +9 -6
- package/src/adapters/OpenCodeAdapter.ts +10 -7
- package/src/adapters/PiAdapter.ts +10 -9
- package/src/adapters/index.ts +1 -1
- package/src/database/connection.ts +74 -0
- package/src/database/index.ts +7 -0
- package/src/database/migrations/001_initial.sql +12 -0
- package/src/database/schema.ts +62 -0
- package/src/index.ts +2 -0
- package/src/utils/AgentRegistry.ts +109 -49
- package/src/utils/index.ts +1 -1
- package/src/utils/process.ts +160 -10
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Codex Adapter
|
|
3
3
|
*
|
|
4
4
|
* Detects running Codex agents by:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. Filtering Codex processes from a shared asynchronous process snapshot
|
|
6
|
+
* 2. Using snapshot CWD and start-time enrichment
|
|
7
7
|
* 3. Matching exact PID-to-session metadata from ~/.codex/ai-devkit/sessions.json
|
|
8
8
|
* 4. Discovering session files from ~/.codex/sessions/YYYY/MM/DD/ via shared batchGetSessionFileBirthtimes()
|
|
9
9
|
* 5. Setting resolvedCwd from session_meta first line
|
|
@@ -20,9 +20,10 @@ import type {
|
|
|
20
20
|
ConversationMessage,
|
|
21
21
|
SessionSummary,
|
|
22
22
|
ListSessionsOptions,
|
|
23
|
+
AgentDetectionContext,
|
|
23
24
|
} from './AgentAdapter.js';
|
|
24
25
|
import { AgentStatus } from './AgentAdapter.js';
|
|
25
|
-
import {
|
|
26
|
+
import { captureProcessSnapshot, executableBasename, filterByProcessNames } from '../utils/process.js';
|
|
26
27
|
import { batchGetSessionFileBirthtimes, isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
|
|
27
28
|
import type { SessionFile } from '../utils/session.js';
|
|
28
29
|
import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
|
|
@@ -88,6 +89,7 @@ interface MappingMatchResult {
|
|
|
88
89
|
|
|
89
90
|
export class CodexAdapter implements AgentAdapter {
|
|
90
91
|
readonly type = 'codex' as const;
|
|
92
|
+
readonly processNames = ['codex'] as const;
|
|
91
93
|
|
|
92
94
|
private static readonly IDLE_THRESHOLD_MINUTES = 5;
|
|
93
95
|
/** Include session files around process start day to recover long-lived processes. */
|
|
@@ -111,8 +113,10 @@ export class CodexAdapter implements AgentAdapter {
|
|
|
111
113
|
/**
|
|
112
114
|
* Detect running Codex agents
|
|
113
115
|
*/
|
|
114
|
-
async detectAgents(): Promise<AgentInfo[]> {
|
|
115
|
-
const
|
|
116
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
117
|
+
const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
|
|
118
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
119
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
116
120
|
if (processes.length === 0) return [];
|
|
117
121
|
|
|
118
122
|
const { cachedAgents, remaining } = this.tryRegistryCache(processes);
|
|
@@ -657,8 +661,7 @@ export class CodexAdapter implements AgentAdapter {
|
|
|
657
661
|
}
|
|
658
662
|
|
|
659
663
|
private isCodexExecutable(command: string): boolean {
|
|
660
|
-
const
|
|
661
|
-
const base = path.basename(executable).toLowerCase();
|
|
664
|
+
const base = executableBasename(command);
|
|
662
665
|
return base === 'codex' || base === 'codex.exe';
|
|
663
666
|
}
|
|
664
667
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Copilot Adapter
|
|
3
3
|
*
|
|
4
4
|
* Detects running GitHub Copilot CLI agents by:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. Filtering Copilot processes from a shared asynchronous process snapshot
|
|
6
|
+
* 2. Using snapshot CWD and start-time enrichment
|
|
7
7
|
* 3. Mapping active ~/.copilot/session-state/{sessionId}/inuse.{pid}.lock files to processes
|
|
8
8
|
* 4. Reading events.jsonl as the primary session/conversation source
|
|
9
9
|
* 5. Reading workspace.yaml as a flat fallback metadata source
|
|
@@ -17,9 +17,16 @@ import type {
|
|
|
17
17
|
ListSessionsOptions,
|
|
18
18
|
ProcessInfo,
|
|
19
19
|
SessionSummary,
|
|
20
|
+
AgentDetectionContext,
|
|
20
21
|
} from './AgentAdapter.js';
|
|
21
22
|
import { AgentStatus } from './AgentAdapter.js';
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
captureProcessSnapshot,
|
|
25
|
+
executableBasename,
|
|
26
|
+
filterByProcessNames,
|
|
27
|
+
findWrapperProcess,
|
|
28
|
+
findWrapperProcessPids,
|
|
29
|
+
} from '../utils/process.js';
|
|
23
30
|
import { generateAgentName } from '../utils/matching.js';
|
|
24
31
|
import { isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
|
|
25
32
|
import { AgentRegistry, type RegistryEntry } from '../utils/AgentRegistry.js';
|
|
@@ -89,6 +96,7 @@ interface CopilotEventSummary {
|
|
|
89
96
|
|
|
90
97
|
export class CopilotAdapter implements AgentAdapter {
|
|
91
98
|
readonly type = 'copilot' as const;
|
|
99
|
+
readonly processNames = ['copilot'] as const;
|
|
92
100
|
|
|
93
101
|
private static readonly IDLE_THRESHOLD_MINUTES = 5;
|
|
94
102
|
private static readonly VERBOSE_SYSTEM_EVENTS = new Set([
|
|
@@ -120,8 +128,10 @@ export class CopilotAdapter implements AgentAdapter {
|
|
|
120
128
|
return this.isCopilotExecutable(processInfo.command);
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
async detectAgents(): Promise<AgentInfo[]> {
|
|
124
|
-
const
|
|
131
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
132
|
+
const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
|
|
133
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
134
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
125
135
|
if (processes.length === 0) return [];
|
|
126
136
|
|
|
127
137
|
const processByPid = new Map(processes.map((proc) => [proc.pid, proc]));
|
|
@@ -457,8 +467,7 @@ export class CopilotAdapter implements AgentAdapter {
|
|
|
457
467
|
}
|
|
458
468
|
|
|
459
469
|
private isCopilotExecutable(command: string): boolean {
|
|
460
|
-
const
|
|
461
|
-
const base = path.basename(executable).toLowerCase();
|
|
470
|
+
const base = executableBasename(command);
|
|
462
471
|
return base === 'copilot' || base === 'copilot.exe';
|
|
463
472
|
}
|
|
464
473
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Gemini CLI Adapter
|
|
3
3
|
*
|
|
4
4
|
* Detects running Gemini CLI agents by:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. Filtering Gemini Node processes from a shared asynchronous process snapshot
|
|
6
|
+
* 2. Using snapshot CWD and start-time enrichment
|
|
7
7
|
* 3. Discovering session files from ~/.gemini/tmp/<shortId>/chats/session-*.json
|
|
8
8
|
* 4. Matching sessions to processes via shared matchProcessesToSessions()
|
|
9
9
|
* using sha256(cwd) === session.projectHash as the resolvedCwd source
|
|
@@ -20,9 +20,16 @@ import type {
|
|
|
20
20
|
ConversationMessage,
|
|
21
21
|
SessionSummary,
|
|
22
22
|
ListSessionsOptions,
|
|
23
|
+
AgentDetectionContext,
|
|
23
24
|
} from './AgentAdapter.js';
|
|
24
25
|
import { AgentStatus } from './AgentAdapter.js';
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
captureProcessSnapshot,
|
|
28
|
+
executableBasename,
|
|
29
|
+
filterByProcessNames,
|
|
30
|
+
findWrapperProcess,
|
|
31
|
+
findWrapperProcessPids,
|
|
32
|
+
} from '../utils/process.js';
|
|
26
33
|
import { isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
|
|
27
34
|
import type { SessionFile } from '../utils/session.js';
|
|
28
35
|
import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
|
|
@@ -81,6 +88,7 @@ interface GeminiSession {
|
|
|
81
88
|
|
|
82
89
|
export class GeminiCliAdapter implements AgentAdapter {
|
|
83
90
|
readonly type = 'gemini_cli' as const;
|
|
91
|
+
readonly processNames = ['node'] as const;
|
|
84
92
|
|
|
85
93
|
private static readonly IDLE_THRESHOLD_MINUTES = 5;
|
|
86
94
|
private static readonly SESSION_FILE_PREFIX = 'session-';
|
|
@@ -109,12 +117,13 @@ export class GeminiCliAdapter implements AgentAdapter {
|
|
|
109
117
|
* binary). The primary running process is therefore the Node runtime
|
|
110
118
|
* itself, and `ps aux` lists it as `node /path/to/gemini ...` with
|
|
111
119
|
* argv[0] = `node`. We scan the Node process pool via the shared
|
|
112
|
-
*
|
|
120
|
+
* snapshot and keep only those whose command line references the gemini
|
|
113
121
|
* executable or script via isGeminiExecutable().
|
|
114
122
|
*/
|
|
115
|
-
async detectAgents(): Promise<AgentInfo[]> {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
123
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
124
|
+
const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
|
|
125
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
126
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
118
127
|
if (processes.length === 0) return [];
|
|
119
128
|
|
|
120
129
|
const wrapperPids = findWrapperProcessPids(processes);
|
|
@@ -541,7 +550,7 @@ export class GeminiCliAdapter implements AgentAdapter {
|
|
|
541
550
|
// other adapters' argv[0]-only check because the Node-script
|
|
542
551
|
// distribution puts the real gemini path in argv[1..], not argv[0].
|
|
543
552
|
for (const token of command.trim().split(/\s+/)) {
|
|
544
|
-
const base =
|
|
553
|
+
const base = executableBasename(token);
|
|
545
554
|
if (base === 'gemini' || base === 'gemini.exe' || base === 'gemini.js') {
|
|
546
555
|
return true;
|
|
547
556
|
}
|
|
@@ -6,9 +6,10 @@ import type {
|
|
|
6
6
|
ConversationMessage,
|
|
7
7
|
SessionSummary,
|
|
8
8
|
ListSessionsOptions,
|
|
9
|
+
AgentDetectionContext,
|
|
9
10
|
} from './AgentAdapter.js';
|
|
10
11
|
import { AgentStatus } from './AgentAdapter.js';
|
|
11
|
-
import {
|
|
12
|
+
import { captureProcessSnapshot, executableBasename, filterByProcessNames } from '../utils/process.js';
|
|
12
13
|
import { isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
|
|
13
14
|
import { generateAgentName } from '../utils/matching.js';
|
|
14
15
|
|
|
@@ -16,7 +17,7 @@ import { generateAgentName } from '../utils/matching.js';
|
|
|
16
17
|
* Grok Build CLI Adapter
|
|
17
18
|
*
|
|
18
19
|
* Detects running Grok Build CLI agents by:
|
|
19
|
-
* 1.
|
|
20
|
+
* 1. Filtering `grok` processes from a shared asynchronous snapshot — Grok is
|
|
20
21
|
* a native binary at ~/.grok/bin/grok, so argv[0] basename is `grok`.
|
|
21
22
|
* 2. Resolving each live process to its working directory via
|
|
22
23
|
* ~/.grok/active_sessions.json, which Grok maintains as a list of
|
|
@@ -69,6 +70,7 @@ interface GrokSession {
|
|
|
69
70
|
|
|
70
71
|
export class GrokCliAdapter implements AgentAdapter {
|
|
71
72
|
readonly type = 'grok_cli' as const;
|
|
73
|
+
readonly processNames = ['grok'] as const;
|
|
72
74
|
|
|
73
75
|
private base: string;
|
|
74
76
|
private sessionsDir: string;
|
|
@@ -87,13 +89,14 @@ export class GrokCliAdapter implements AgentAdapter {
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
private isGrokExecutable(command: string): boolean {
|
|
90
|
-
const
|
|
91
|
-
const base = path.basename(executable).toLowerCase();
|
|
92
|
+
const base = executableBasename(command);
|
|
92
93
|
return base === 'grok' || base === 'grok.exe';
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
async detectAgents(): Promise<AgentInfo[]> {
|
|
96
|
-
const
|
|
96
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
97
|
+
const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
|
|
98
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
99
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
97
100
|
if (processes.length === 0) {
|
|
98
101
|
return [];
|
|
99
102
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* OpenCode Adapter
|
|
3
3
|
*
|
|
4
4
|
* Detects running OpenCode agents by:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. Filtering OpenCode processes from a shared asynchronous process snapshot
|
|
6
|
+
* 2. Using snapshot CWD and start-time enrichment
|
|
7
7
|
* 3. Querying OpenCode's SQLite DB (~/.local/share/opencode/opencode.db) to
|
|
8
8
|
* find the session matching each process's CWD and read status from message.time.completed
|
|
9
9
|
*
|
|
@@ -21,9 +21,10 @@ import type {
|
|
|
21
21
|
ConversationMessage,
|
|
22
22
|
SessionSummary,
|
|
23
23
|
ListSessionsOptions,
|
|
24
|
+
AgentDetectionContext,
|
|
24
25
|
} from './AgentAdapter.js';
|
|
25
26
|
import { AgentStatus } from './AgentAdapter.js';
|
|
26
|
-
import {
|
|
27
|
+
import { captureProcessSnapshot, executableBasename, filterByProcessNames } from '../utils/process.js';
|
|
27
28
|
import { generateAgentName } from '../utils/matching.js';
|
|
28
29
|
|
|
29
30
|
const SESSION_REF_SEP = '::';
|
|
@@ -55,6 +56,7 @@ interface OpenCodeSessionStats {
|
|
|
55
56
|
|
|
56
57
|
export class OpenCodeAdapter implements AgentAdapter {
|
|
57
58
|
readonly type = 'opencode' as const;
|
|
59
|
+
readonly processNames = ['opencode'] as const;
|
|
58
60
|
|
|
59
61
|
private static readonly IDLE_THRESHOLD_MINUTES = 5;
|
|
60
62
|
|
|
@@ -84,13 +86,14 @@ export class OpenCodeAdapter implements AgentAdapter {
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
canHandle(processInfo: ProcessInfo): boolean {
|
|
87
|
-
const
|
|
88
|
-
const base = path.basename(exe);
|
|
89
|
+
const base = executableBasename(processInfo.command);
|
|
89
90
|
return base === 'opencode' || base === 'opencode.exe';
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
async detectAgents(): Promise<AgentInfo[]> {
|
|
93
|
-
const
|
|
93
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
94
|
+
const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
|
|
95
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
96
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
94
97
|
if (processes.length === 0) return [];
|
|
95
98
|
|
|
96
99
|
const db = this.openDb();
|
|
@@ -17,9 +17,10 @@ import type {
|
|
|
17
17
|
ConversationMessage,
|
|
18
18
|
SessionSummary,
|
|
19
19
|
ListSessionsOptions,
|
|
20
|
+
AgentDetectionContext,
|
|
20
21
|
} from './AgentAdapter.js';
|
|
21
22
|
import { AgentStatus } from './AgentAdapter.js';
|
|
22
|
-
import {
|
|
23
|
+
import { captureProcessSnapshot, executableBasename, filterByProcessNames } from '../utils/process.js';
|
|
23
24
|
import { isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
|
|
24
25
|
import type { SessionFile } from '../utils/session.js';
|
|
25
26
|
import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
|
|
@@ -66,6 +67,7 @@ interface TrackerAgentResult {
|
|
|
66
67
|
|
|
67
68
|
export class PiAdapter implements AgentAdapter {
|
|
68
69
|
readonly type = 'pi' as const;
|
|
70
|
+
readonly processNames = ['pi', 'node'] as const;
|
|
69
71
|
|
|
70
72
|
private static readonly IDLE_THRESHOLD_MINUTES = 5;
|
|
71
73
|
|
|
@@ -86,8 +88,10 @@ export class PiAdapter implements AgentAdapter {
|
|
|
86
88
|
return this.isPiExecutable(processInfo.command);
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
async detectAgents(): Promise<AgentInfo[]> {
|
|
90
|
-
const
|
|
91
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
92
|
+
const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
|
|
93
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
94
|
+
const processes = this.listPiProcesses(relevant);
|
|
91
95
|
if (processes.length === 0) return [];
|
|
92
96
|
|
|
93
97
|
const { cachedAgents, remaining } = this.tryRegistryCache(processes);
|
|
@@ -149,12 +153,9 @@ export class PiAdapter implements AgentAdapter {
|
|
|
149
153
|
return agents;
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
private listPiProcesses(): ProcessInfo[] {
|
|
156
|
+
private listPiProcesses(snapshot: readonly ProcessInfo[]): ProcessInfo[] {
|
|
153
157
|
const byPid = new Map<number, ProcessInfo>();
|
|
154
|
-
for (const proc of
|
|
155
|
-
if (this.canHandle(proc)) byPid.set(proc.pid, proc);
|
|
156
|
-
}
|
|
157
|
-
for (const proc of listAgentProcesses('node')) {
|
|
158
|
+
for (const proc of snapshot) {
|
|
158
159
|
if (this.canHandle(proc)) byPid.set(proc.pid, proc);
|
|
159
160
|
}
|
|
160
161
|
return Array.from(byPid.values());
|
|
@@ -547,7 +548,7 @@ export class PiAdapter implements AgentAdapter {
|
|
|
547
548
|
|
|
548
549
|
private isPiExecutable(command: string): boolean {
|
|
549
550
|
for (const token of command.trim().split(/\s+/)) {
|
|
550
|
-
const base =
|
|
551
|
+
const base = executableBasename(token);
|
|
551
552
|
if (base === 'pi' || base === 'pi.exe' || base === 'pi.js') return true;
|
|
552
553
|
}
|
|
553
554
|
return false;
|
package/src/adapters/index.ts
CHANGED
|
@@ -6,4 +6,4 @@ export { GrokCliAdapter } from './GrokCliAdapter.js';
|
|
|
6
6
|
export { OpenCodeAdapter } from './OpenCodeAdapter.js';
|
|
7
7
|
export { PiAdapter } from './PiAdapter.js';
|
|
8
8
|
export { AgentStatus } from './AgentAdapter.js';
|
|
9
|
-
export type { AgentAdapter, AgentType, AgentInfo, ProcessInfo } from './AgentAdapter.js';
|
|
9
|
+
export type { AgentAdapter, AgentType, AgentInfo, ProcessInfo, AgentDetectionContext } from './AgentAdapter.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { mkdirSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { initializeSchema } from './schema.js';
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_AGENT_REGISTRY_DB_PATH = join(homedir(), '.ai-devkit', 'agents.db');
|
|
8
|
+
|
|
9
|
+
export interface DatabaseOptions {
|
|
10
|
+
dbPath?: string;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
readonly?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveAgentRegistryDbPath(filePath?: string): string {
|
|
16
|
+
if (!filePath) return DEFAULT_AGENT_REGISTRY_DB_PATH;
|
|
17
|
+
return filePath.endsWith('.json') ? filePath.replace(/\.json$/, '.db') : filePath;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class DatabaseConnection {
|
|
21
|
+
private db: Database.Database;
|
|
22
|
+
private readonly dbPath: string;
|
|
23
|
+
|
|
24
|
+
constructor(options: DatabaseOptions = {}) {
|
|
25
|
+
this.dbPath = options.dbPath ?? DEFAULT_AGENT_REGISTRY_DB_PATH;
|
|
26
|
+
mkdirSync(dirname(this.dbPath), { recursive: true });
|
|
27
|
+
|
|
28
|
+
this.db = new Database(this.dbPath, {
|
|
29
|
+
readonly: options.readonly ?? false,
|
|
30
|
+
verbose: options.verbose ? console.log : undefined,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.configure();
|
|
34
|
+
initializeSchema(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private configure(): void {
|
|
38
|
+
this.db.pragma('journal_mode = WAL');
|
|
39
|
+
this.db.pragma('foreign_keys = ON');
|
|
40
|
+
this.db.pragma('synchronous = NORMAL');
|
|
41
|
+
this.db.pragma('busy_timeout = 5000');
|
|
42
|
+
this.db.pragma('mmap_size = 268435456');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get instance(): Database.Database {
|
|
46
|
+
return this.db;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get path(): string {
|
|
50
|
+
return this.dbPath;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
query<T>(sql: string, params: unknown[] = []): T[] {
|
|
54
|
+
return this.db.prepare(sql).all(...params) as T[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
queryOne<T>(sql: string, params: unknown[] = []): T | undefined {
|
|
58
|
+
return this.db.prepare(sql).get(...params) as T | undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
execute(sql: string, params: unknown[] = []): Database.RunResult {
|
|
62
|
+
return this.db.prepare(sql).run(...params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
transaction<T>(fn: () => T): T {
|
|
66
|
+
return this.db.transaction(fn)();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
close(): void {
|
|
70
|
+
if (this.db.open) {
|
|
71
|
+
this.db.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
2
|
+
type TEXT NOT NULL,
|
|
3
|
+
pid INTEGER NOT NULL,
|
|
4
|
+
name TEXT NOT NULL UNIQUE,
|
|
5
|
+
tmux_session TEXT NOT NULL DEFAULT '',
|
|
6
|
+
cwd TEXT NOT NULL DEFAULT '',
|
|
7
|
+
started_at TEXT NOT NULL,
|
|
8
|
+
session_id TEXT NOT NULL DEFAULT '',
|
|
9
|
+
session_file_path TEXT NOT NULL DEFAULT '',
|
|
10
|
+
updated_at TEXT NOT NULL,
|
|
11
|
+
PRIMARY KEY (type, pid)
|
|
12
|
+
);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import type { DatabaseConnection } from './connection.js';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export function getSchemaVersion(db: DatabaseConnection): number {
|
|
9
|
+
const result = db.instance.pragma('user_version') as { user_version: number }[];
|
|
10
|
+
return result[0]?.user_version ?? 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function setSchemaVersion(db: DatabaseConnection, version: number): void {
|
|
14
|
+
db.instance.pragma(`user_version = ${version}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getMigrationsDir(): string {
|
|
18
|
+
return join(__dirname, 'migrations');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Migration {
|
|
22
|
+
version: number;
|
|
23
|
+
path: string;
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getMigrationFiles(): Migration[] {
|
|
28
|
+
const migrationsDir = getMigrationsDir();
|
|
29
|
+
|
|
30
|
+
let files: string[];
|
|
31
|
+
try {
|
|
32
|
+
files = readdirSync(migrationsDir).filter((f) => f.endsWith('.sql')).sort();
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return files.map((file) => {
|
|
38
|
+
const match = file.match(/^(\d+)_(.+)\.sql$/);
|
|
39
|
+
if (!match || !match[1] || !match[2]) {
|
|
40
|
+
throw new Error(`Invalid migration filename: ${file}. Expected format: 001_name.sql`);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
version: parseInt(match[1], 10),
|
|
44
|
+
name: match[2],
|
|
45
|
+
path: join(migrationsDir, file),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function initializeSchema(db: DatabaseConnection): void {
|
|
51
|
+
const currentVersion = getSchemaVersion(db);
|
|
52
|
+
const pendingMigrations = getMigrationFiles().filter((m) => m.version > currentVersion);
|
|
53
|
+
|
|
54
|
+
for (const migration of pendingMigrations) {
|
|
55
|
+
const sql = readFileSync(migration.path, 'utf-8');
|
|
56
|
+
|
|
57
|
+
db.transaction(() => {
|
|
58
|
+
db.instance.exec(sql);
|
|
59
|
+
setSchemaVersion(db, migration.version);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type {
|
|
|
16
16
|
ConversationMessage,
|
|
17
17
|
SessionSummary,
|
|
18
18
|
ListSessionsOptions,
|
|
19
|
+
AgentDetectionContext,
|
|
19
20
|
} from './adapters/AgentAdapter.js';
|
|
20
21
|
|
|
21
22
|
export { TerminalFocusManager, TerminalType } from './terminal/TerminalFocusManager.js';
|
|
@@ -23,6 +24,7 @@ export type { TerminalLocation } from './terminal/TerminalFocusManager.js';
|
|
|
23
24
|
export { TtyWriter } from './terminal/TtyWriter.js';
|
|
24
25
|
|
|
25
26
|
export { getProcessTty } from './utils/process.js';
|
|
27
|
+
export { captureProcessSnapshot, executableBasename, filterByProcessNames } from './utils/process.js';
|
|
26
28
|
export type { AgentSortKey } from './utils/sortAgents.js';
|
|
27
29
|
export type { ListAgentsOptions } from './AgentManager.js';
|
|
28
30
|
|