@ai-devkit/agent-manager 0.33.0 → 0.33.1
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 +11 -2
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +37 -6
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +212 -5
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +1 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/{adapters → providers/copilot}/CopilotAdapter.test.js +19 -14
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/{adapters → providers/gemini}/GeminiCliAdapter.test.js +37 -29
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -0
- package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js +164 -0
- package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js.map +1 -0
- package/dist/__tests__/runtime/HerdrAgentRuntime.test.js +37 -1
- package/dist/__tests__/runtime/HerdrAgentRuntime.test.js.map +1 -1
- package/dist/__tests__/utils/AgentRegistry.test.js +20 -13
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.d.ts +1 -0
- package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +7 -4
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +2 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/{adapters → providers/copilot}/CopilotAdapter.d.ts +11 -25
- package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotAdapter.js +108 -0
- package/dist/providers/copilot/CopilotAdapter.js.map +1 -0
- package/dist/providers/copilot/CopilotAgentMapper.d.ts +9 -0
- package/dist/providers/copilot/CopilotAgentMapper.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotAgentMapper.js +37 -0
- package/dist/providers/copilot/CopilotAgentMapper.js.map +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts +19 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.js +41 -0
- package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -0
- package/dist/providers/copilot/CopilotSessionParser.d.ts +30 -0
- package/dist/providers/copilot/CopilotSessionParser.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotSessionParser.js +181 -0
- package/dist/providers/copilot/CopilotSessionParser.js.map +1 -0
- package/dist/providers/gemini/GeminiAgentMapper.d.ts +9 -0
- package/dist/providers/gemini/GeminiAgentMapper.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiAgentMapper.js +37 -0
- package/dist/providers/gemini/GeminiAgentMapper.js.map +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts +52 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.js +188 -0
- package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -0
- package/dist/providers/gemini/GeminiSessionLocator.d.ts +30 -0
- package/dist/providers/gemini/GeminiSessionLocator.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiSessionLocator.js +142 -0
- package/dist/providers/gemini/GeminiSessionLocator.js.map +1 -0
- package/dist/providers/gemini/GeminiSessionParser.d.ts +94 -0
- package/dist/providers/gemini/GeminiSessionParser.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiSessionParser.js +166 -0
- package/dist/providers/gemini/GeminiSessionParser.js.map +1 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts +18 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js +183 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts +5 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts.map +1 -1
- package/dist/runtime/herdr/HerdrAgentRuntime.js +26 -8
- package/dist/runtime/herdr/HerdrAgentRuntime.js.map +1 -1
- package/dist/runtime/herdr/HerdrResponseParsers.d.ts +2 -0
- package/dist/runtime/herdr/HerdrResponseParsers.d.ts.map +1 -1
- package/dist/runtime/herdr/HerdrResponseParsers.js +12 -1
- package/dist/runtime/herdr/HerdrResponseParsers.js.map +1 -1
- package/dist/utils/AgentRegistry.d.ts.map +1 -1
- package/dist/utils/AgentRegistry.js +5 -5
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +67 -5
- package/src/__tests__/AgentManager.test.ts +231 -5
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +1 -0
- package/src/__tests__/{adapters → providers/copilot}/CopilotAdapter.test.ts +13 -16
- package/src/__tests__/{adapters → providers/gemini}/GeminiCliAdapter.test.ts +39 -31
- package/src/__tests__/runtime/HerdrAgentDiscovery.test.ts +131 -0
- package/src/__tests__/runtime/HerdrAgentRuntime.test.ts +20 -1
- package/src/__tests__/utils/AgentRegistry.test.ts +20 -10
- package/src/adapters/OpenCodeAdapter.ts +8 -4
- package/src/adapters/index.ts +2 -2
- package/src/index.ts +10 -3
- package/src/providers/copilot/CopilotAdapter.ts +145 -0
- package/src/providers/copilot/CopilotAgentMapper.ts +36 -0
- package/src/providers/copilot/CopilotSessionLocator.ts +57 -0
- package/src/providers/copilot/CopilotSessionParser.ts +289 -0
- package/src/providers/gemini/GeminiAgentMapper.ts +36 -0
- package/src/providers/gemini/GeminiCliAdapter.ts +266 -0
- package/src/providers/gemini/GeminiSessionLocator.ts +183 -0
- package/src/providers/gemini/GeminiSessionParser.ts +271 -0
- package/src/runtime/herdr/HerdrAgentDiscovery.ts +178 -0
- package/src/runtime/herdr/HerdrAgentRuntime.ts +33 -7
- package/src/runtime/herdr/HerdrResponseParsers.ts +11 -1
- package/src/utils/AgentRegistry.ts +8 -6
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +0 -1
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +0 -1
- package/dist/adapters/CopilotAdapter.d.ts.map +0 -1
- package/dist/adapters/CopilotAdapter.js +0 -329
- package/dist/adapters/CopilotAdapter.js.map +0 -1
- package/dist/adapters/GeminiCliAdapter.d.ts +0 -98
- package/dist/adapters/GeminiCliAdapter.d.ts.map +0 -1
- package/dist/adapters/GeminiCliAdapter.js +0 -513
- package/dist/adapters/GeminiCliAdapter.js.map +0 -1
- package/src/adapters/CopilotAdapter.ts +0 -483
- package/src/adapters/GeminiCliAdapter.ts +0 -699
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractHerdrAgentPanes,
|
|
3
|
+
fetchHerdrAgentPanes,
|
|
4
|
+
findMatchingHerdrPane,
|
|
5
|
+
} from "../../runtime/herdr/HerdrAgentDiscovery.js";
|
|
6
|
+
import type { HerdrCommandRunner } from "../../runtime/herdr/HerdrCliClient.js";
|
|
7
|
+
|
|
8
|
+
function jsonResponse(value: unknown): { stdout: string; stderr: string } {
|
|
9
|
+
return { stdout: JSON.stringify(value), stderr: "" };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("HerdrAgentDiscovery", () => {
|
|
13
|
+
it("extracts agent panes from pane list responses", () => {
|
|
14
|
+
const panes = extractHerdrAgentPanes({
|
|
15
|
+
result: {
|
|
16
|
+
panes: [
|
|
17
|
+
{
|
|
18
|
+
agent: "codex",
|
|
19
|
+
agent_session: { value: "session-1" },
|
|
20
|
+
cwd: "/repo",
|
|
21
|
+
pane_id: "w32:p1",
|
|
22
|
+
workspace_id: "w32",
|
|
23
|
+
tab_id: "w32:t1",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(panes).toEqual([
|
|
30
|
+
{
|
|
31
|
+
agent: "codex",
|
|
32
|
+
agentSessionId: "session-1",
|
|
33
|
+
cwd: "/repo",
|
|
34
|
+
paneId: "w32:p1",
|
|
35
|
+
workspaceId: "w32",
|
|
36
|
+
tabId: "w32:t1",
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("matches by foreground PID before falling back to ambiguous cwd and type", () => {
|
|
42
|
+
const pane = findMatchingHerdrPane(
|
|
43
|
+
{
|
|
44
|
+
type: "codex",
|
|
45
|
+
projectPath: "/repo",
|
|
46
|
+
sessionId: "pid-64904",
|
|
47
|
+
pid: 64904,
|
|
48
|
+
},
|
|
49
|
+
[
|
|
50
|
+
{ agent: "codex", cwd: "/repo", paneId: "w31:p1", foregroundPids: [44290] },
|
|
51
|
+
{ agent: "codex", cwd: "/repo", paneId: "w32:p1", foregroundPids: [64904] },
|
|
52
|
+
],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(pane?.paneId).toBe("w32:p1");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("prefers a foreground PID match over a shell PID match", () => {
|
|
59
|
+
const pane = findMatchingHerdrPane(
|
|
60
|
+
{
|
|
61
|
+
type: "codex",
|
|
62
|
+
projectPath: "/repo",
|
|
63
|
+
sessionId: "pid-64904",
|
|
64
|
+
pid: 64904,
|
|
65
|
+
},
|
|
66
|
+
[
|
|
67
|
+
{ agent: "codex", cwd: "/repo", paneId: "w31:p1", shellPid: 64904 },
|
|
68
|
+
{ agent: "codex", cwd: "/repo", paneId: "w32:p1", foregroundPids: [64904] },
|
|
69
|
+
],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(pane?.paneId).toBe("w32:p1");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("enriches pane list output with foreground process IDs", async () => {
|
|
76
|
+
const runner = vi
|
|
77
|
+
.fn<HerdrCommandRunner>()
|
|
78
|
+
.mockResolvedValueOnce(
|
|
79
|
+
jsonResponse({
|
|
80
|
+
result: {
|
|
81
|
+
panes: [
|
|
82
|
+
{ agent: "codex", cwd: "/repo", pane_id: "w31:p1" },
|
|
83
|
+
{ agent: "codex", cwd: "/repo", pane_id: "w32:p1" },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
.mockResolvedValueOnce(
|
|
89
|
+
jsonResponse({
|
|
90
|
+
result: {
|
|
91
|
+
process_info: {
|
|
92
|
+
pane_id: "w31:p1",
|
|
93
|
+
shell_pid: 111,
|
|
94
|
+
foreground_processes: [{ pid: 44290 }],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
.mockResolvedValueOnce(
|
|
100
|
+
jsonResponse({
|
|
101
|
+
result: {
|
|
102
|
+
process_info: {
|
|
103
|
+
pane_id: "w32:p1",
|
|
104
|
+
shell_pid: 222,
|
|
105
|
+
foreground_processes: [{ pid: 64904 }],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const panes = await fetchHerdrAgentPanes(runner);
|
|
112
|
+
|
|
113
|
+
expect(runner).toHaveBeenNthCalledWith(1, "herdr", ["pane", "list"]);
|
|
114
|
+
expect(runner).toHaveBeenNthCalledWith(2, "herdr", [
|
|
115
|
+
"pane",
|
|
116
|
+
"process-info",
|
|
117
|
+
"--pane",
|
|
118
|
+
"w31:p1",
|
|
119
|
+
]);
|
|
120
|
+
expect(runner).toHaveBeenNthCalledWith(3, "herdr", [
|
|
121
|
+
"pane",
|
|
122
|
+
"process-info",
|
|
123
|
+
"--pane",
|
|
124
|
+
"w32:p1",
|
|
125
|
+
]);
|
|
126
|
+
expect(panes).toMatchObject([
|
|
127
|
+
{ paneId: "w31:p1", foregroundPids: [44290], shellPid: 111 },
|
|
128
|
+
{ paneId: "w32:p1", foregroundPids: [64904], shellPid: 222 },
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -186,11 +186,30 @@ describe("HerdrAgentRuntime", () => {
|
|
|
186
186
|
agentResponse(),
|
|
187
187
|
processInfoResponse({ shellPid: 12000 }),
|
|
188
188
|
]);
|
|
189
|
-
const runtime = new HerdrAgentRuntime({ runner, env: {} });
|
|
189
|
+
const runtime = new HerdrAgentRuntime({ runner, env: {}, processInfoPollMs: 0 });
|
|
190
190
|
|
|
191
191
|
await expect(runtime.startAgent(startInput)).resolves.toMatchObject({ pid: 12000 });
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
+
it("waits for a foreground process pid before falling back to the pane shell pid", async () => {
|
|
195
|
+
const runner = createRunner([
|
|
196
|
+
workspaceResponse(),
|
|
197
|
+
agentResponse(),
|
|
198
|
+
processInfoResponse({ shellPid: 12000 }),
|
|
199
|
+
processInfoResponse({ shellPid: 12000, pid: 12345, processName: "node" }),
|
|
200
|
+
]);
|
|
201
|
+
const runtime = new HerdrAgentRuntime({
|
|
202
|
+
runner,
|
|
203
|
+
env: {},
|
|
204
|
+
processInfoPollMs: 100,
|
|
205
|
+
processInfoPollIntervalMs: 0,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await expect(runtime.startAgent(startInput)).resolves.toMatchObject({ pid: 12345 });
|
|
209
|
+
expect(runner).toHaveBeenNthCalledWith(3, "herdr", ["pane", "process-info", "--pane", "w1:p1"]);
|
|
210
|
+
expect(runner).toHaveBeenNthCalledWith(4, "herdr", ["pane", "process-info", "--pane", "w1:p1"]);
|
|
211
|
+
});
|
|
212
|
+
|
|
194
213
|
it("passes only explicit extra agent args after the Herdr start separator", async () => {
|
|
195
214
|
const runner = createRunner([
|
|
196
215
|
workspaceResponse(),
|
|
@@ -92,24 +92,34 @@ describe("AgentRegistry", () => {
|
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
it("
|
|
95
|
+
it("preserves existing identity and routing fields for same-pid refreshes", () => {
|
|
96
96
|
const runtimeRef = { session: "default", paneId: "w1:p2", agentName: "requested-name" };
|
|
97
97
|
|
|
98
|
-
registry.register(
|
|
98
|
+
registry.register(
|
|
99
|
+
makeEntry({
|
|
100
|
+
name: "old-name",
|
|
101
|
+
pid: process.pid,
|
|
102
|
+
runtime: "tmux",
|
|
103
|
+
runtimeRef: { session: "old-name" },
|
|
104
|
+
cwd: "/original",
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
99
107
|
registry.register(
|
|
100
108
|
makeEntry({
|
|
101
109
|
name: "requested-name",
|
|
102
110
|
pid: process.pid,
|
|
103
111
|
runtime: "herdr",
|
|
104
112
|
runtimeRef,
|
|
113
|
+
cwd: "/incoming",
|
|
105
114
|
}),
|
|
106
115
|
);
|
|
107
116
|
|
|
108
|
-
expect(registry.lookup("
|
|
109
|
-
expect(registry.lookup("
|
|
117
|
+
expect(registry.lookup("requested-name")).toBeNull();
|
|
118
|
+
expect(registry.lookup("old-name")).toMatchObject({
|
|
110
119
|
pid: process.pid,
|
|
111
|
-
runtime: "
|
|
112
|
-
runtimeRef,
|
|
120
|
+
runtime: "tmux",
|
|
121
|
+
runtimeRef: { session: "old-name" },
|
|
122
|
+
cwd: "/original",
|
|
113
123
|
});
|
|
114
124
|
});
|
|
115
125
|
|
|
@@ -127,11 +137,11 @@ describe("AgentRegistry", () => {
|
|
|
127
137
|
expect(registry.lookup("a")?.runtimeRef).toBeNull();
|
|
128
138
|
});
|
|
129
139
|
|
|
130
|
-
it("
|
|
140
|
+
it("preserves an existing generated fallback for the same pid", () => {
|
|
131
141
|
registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, runtimeRef: null }));
|
|
132
142
|
registry.register(makeEntry({ name: "custom-name", runtimeRef: { session: "custom-name" } }));
|
|
133
|
-
expect(registry.lookup("custom-name")
|
|
134
|
-
expect(registry.lookup(`ai-devkit-${process.pid}`)).toBeNull();
|
|
143
|
+
expect(registry.lookup("custom-name")).toBeNull();
|
|
144
|
+
expect(registry.lookup(`ai-devkit-${process.pid}`)?.runtimeRef).toBeNull();
|
|
135
145
|
expect(registry.list()).toHaveLength(1);
|
|
136
146
|
});
|
|
137
147
|
|
|
@@ -186,7 +196,7 @@ describe("AgentRegistry", () => {
|
|
|
186
196
|
registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, runtimeRef: null }));
|
|
187
197
|
|
|
188
198
|
expect(registry.list()).toHaveLength(1);
|
|
189
|
-
expect(registry.lookup(
|
|
199
|
+
expect(registry.lookup(`ai-devkit-${process.pid}`)?.pid).toBe(process.pid);
|
|
190
200
|
});
|
|
191
201
|
|
|
192
202
|
it("cleans up a cross-type row when its pid has been reused", () => {
|
|
@@ -65,17 +65,21 @@ export class OpenCodeAdapter implements AgentAdapter {
|
|
|
65
65
|
private static readonly IDLE_THRESHOLD_MINUTES = 5;
|
|
66
66
|
|
|
67
67
|
private readonly dbPath: string;
|
|
68
|
+
private readonly cleanup = (): void => this.close();
|
|
68
69
|
private db: Database.Database | null = null;
|
|
69
70
|
|
|
70
71
|
constructor() {
|
|
71
72
|
this.dbPath = OpenCodeAdapter.resolveDbPath();
|
|
72
|
-
|
|
73
|
-
process.once("
|
|
74
|
-
process.once("
|
|
75
|
-
process.once("SIGTERM", cleanup);
|
|
73
|
+
process.once("exit", this.cleanup);
|
|
74
|
+
process.once("SIGINT", this.cleanup);
|
|
75
|
+
process.once("SIGTERM", this.cleanup);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
close(): void {
|
|
79
|
+
process.off("exit", this.cleanup);
|
|
80
|
+
process.off("SIGINT", this.cleanup);
|
|
81
|
+
process.off("SIGTERM", this.cleanup);
|
|
82
|
+
|
|
79
83
|
if (this.db) {
|
|
80
84
|
try {
|
|
81
85
|
this.db.close();
|
package/src/adapters/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { ClaudeCodeAdapter } from "../providers/claude/ClaudeCodeAdapter.js";
|
|
2
2
|
export { CodexAdapter } from "../providers/codex/CodexAdapter.js";
|
|
3
|
-
export { CopilotAdapter } from "
|
|
4
|
-
export { GeminiCliAdapter } from "
|
|
3
|
+
export { CopilotAdapter } from "../providers/copilot/CopilotAdapter.js";
|
|
4
|
+
export { GeminiCliAdapter } from "../providers/gemini/GeminiCliAdapter.js";
|
|
5
5
|
export { GrokCliAdapter } from "./GrokCliAdapter.js";
|
|
6
6
|
export { OpenCodeAdapter } from "./OpenCodeAdapter.js";
|
|
7
7
|
export { PiAdapter } from "../providers/pi/PiAdapter.js";
|
package/src/index.ts
CHANGED
|
@@ -22,8 +22,8 @@ export type {
|
|
|
22
22
|
|
|
23
23
|
export { ClaudeCodeAdapter } from "./providers/claude/ClaudeCodeAdapter.js";
|
|
24
24
|
export { CodexAdapter } from "./providers/codex/CodexAdapter.js";
|
|
25
|
-
export { CopilotAdapter } from "./
|
|
26
|
-
export { GeminiCliAdapter } from "./
|
|
25
|
+
export { CopilotAdapter } from "./providers/copilot/CopilotAdapter.js";
|
|
26
|
+
export { GeminiCliAdapter } from "./providers/gemini/GeminiCliAdapter.js";
|
|
27
27
|
export { GrokCliAdapter } from "./adapters/GrokCliAdapter.js";
|
|
28
28
|
export { OpenCodeAdapter } from "./adapters/OpenCodeAdapter.js";
|
|
29
29
|
export { PiAdapter } from "./providers/pi/PiAdapter.js";
|
|
@@ -43,7 +43,14 @@ export { TerminalFocusManager, TerminalType } from "./terminal/TerminalFocusMana
|
|
|
43
43
|
export type { TerminalLocation } from "./terminal/TerminalFocusManager.js";
|
|
44
44
|
export { TtyWriter } from "./terminal/TtyWriter.js";
|
|
45
45
|
|
|
46
|
-
export type { ListAgentsOptions } from "./AgentManager.js";
|
|
46
|
+
export type { AgentManagerOptions, ListAgentsOptions } from "./AgentManager.js";
|
|
47
|
+
export {
|
|
48
|
+
extractHerdrAgentPanes,
|
|
49
|
+
fetchHerdrAgentPanes,
|
|
50
|
+
findMatchingHerdrPane,
|
|
51
|
+
herdrPaneToRuntimeRef,
|
|
52
|
+
} from "./runtime/herdr/HerdrAgentDiscovery.js";
|
|
53
|
+
export type { HerdrAgentPane } from "./runtime/herdr/HerdrAgentDiscovery.js";
|
|
47
54
|
|
|
48
55
|
export {
|
|
49
56
|
AgentRegistry,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot Adapter
|
|
3
|
+
*
|
|
4
|
+
* Detects running GitHub Copilot CLI agents by:
|
|
5
|
+
* 1. Filtering Copilot processes from a shared asynchronous process snapshot
|
|
6
|
+
* 2. Using snapshot CWD and start-time enrichment
|
|
7
|
+
* 3. Mapping active ~/.copilot/session-state/{sessionId}/inuse.{pid}.lock files to processes
|
|
8
|
+
* 4. Reading events.jsonl as the primary session/conversation source
|
|
9
|
+
* 5. Reading workspace.yaml as a flat fallback metadata source
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
import type {
|
|
14
|
+
AgentAdapter,
|
|
15
|
+
AgentInfo,
|
|
16
|
+
ConversationMessage,
|
|
17
|
+
ListSessionsOptions,
|
|
18
|
+
ProcessInfo,
|
|
19
|
+
SessionSummary,
|
|
20
|
+
AgentDetectionContext,
|
|
21
|
+
} from "../../adapters/AgentAdapter.js";
|
|
22
|
+
import {
|
|
23
|
+
captureProcessSnapshot,
|
|
24
|
+
executableBasename,
|
|
25
|
+
filterByProcessNames,
|
|
26
|
+
findWrapperProcess,
|
|
27
|
+
findWrapperProcessPids,
|
|
28
|
+
} from "../../utils/process.js";
|
|
29
|
+
import { AgentRegistry, type RegistryEntry } from "../../utils/AgentRegistry.js";
|
|
30
|
+
import { CopilotAgentMapper } from "./CopilotAgentMapper.js";
|
|
31
|
+
import { CopilotSessionLocator } from "./CopilotSessionLocator.js";
|
|
32
|
+
import { CopilotSessionParser } from "./CopilotSessionParser.js";
|
|
33
|
+
|
|
34
|
+
export interface CopilotAdapterOptions {
|
|
35
|
+
sessionStateDir?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class CopilotAdapter implements AgentAdapter {
|
|
39
|
+
readonly type = "copilot" as const;
|
|
40
|
+
readonly processNames = ["copilot"] as const;
|
|
41
|
+
|
|
42
|
+
private readonly registry: AgentRegistry;
|
|
43
|
+
private readonly parser: CopilotSessionParser;
|
|
44
|
+
private readonly mapper: CopilotAgentMapper;
|
|
45
|
+
private readonly locator: CopilotSessionLocator;
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
registry: AgentRegistry = AgentRegistry.default(),
|
|
49
|
+
options: CopilotAdapterOptions = {},
|
|
50
|
+
) {
|
|
51
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
52
|
+
this.registry = registry;
|
|
53
|
+
this.parser = new CopilotSessionParser();
|
|
54
|
+
this.mapper = new CopilotAgentMapper(this.parser);
|
|
55
|
+
this.locator = new CopilotSessionLocator({
|
|
56
|
+
sessionStateDir: options.sessionStateDir ?? path.join(homeDir, ".copilot", "session-state"),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
canHandle(processInfo: ProcessInfo): boolean {
|
|
61
|
+
return this.isCopilotExecutable(processInfo.command);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
65
|
+
const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));
|
|
66
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
67
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
68
|
+
if (processes.length === 0) return [];
|
|
69
|
+
|
|
70
|
+
const processByPid = new Map(processes.map((proc) => [proc.pid, proc]));
|
|
71
|
+
const registryEntriesByPid = new Map(this.registry.list().map((entry) => [entry.pid, entry]));
|
|
72
|
+
const matchedPids = new Set<number>();
|
|
73
|
+
const matchedProcesses: ProcessInfo[] = [];
|
|
74
|
+
const agents: AgentInfo[] = [];
|
|
75
|
+
|
|
76
|
+
for (const lock of this.locator.discoverActiveLocks()) {
|
|
77
|
+
const proc = processByPid.get(lock.pid);
|
|
78
|
+
if (!proc) continue;
|
|
79
|
+
|
|
80
|
+
const session = this.parser.readSessionDir(lock.sessionDir, lock.sessionId);
|
|
81
|
+
if (!session) continue;
|
|
82
|
+
|
|
83
|
+
const agent = this.mapper.mapSessionToAgent(session, proc);
|
|
84
|
+
this.applyWrapperRegistryName(agent, proc, processes, registryEntriesByPid);
|
|
85
|
+
agents.push(agent);
|
|
86
|
+
matchedPids.add(proc.pid);
|
|
87
|
+
matchedProcesses.push(proc);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const wrapperPids = findWrapperProcessPids(processes, matchedProcesses);
|
|
91
|
+
for (const proc of processes) {
|
|
92
|
+
if (!matchedPids.has(proc.pid) && !wrapperPids.has(proc.pid)) {
|
|
93
|
+
const agent = this.mapper.mapProcessOnlyAgent(proc);
|
|
94
|
+
this.applyWrapperRegistryName(agent, proc, processes, registryEntriesByPid);
|
|
95
|
+
agents.push(agent);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return agents;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
|
|
103
|
+
return this.parser.getConversation(sessionFilePath, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
|
|
107
|
+
const summaries: SessionSummary[] = [];
|
|
108
|
+
|
|
109
|
+
for (const { sessionDir, sessionId } of this.locator.listSessionDirs()) {
|
|
110
|
+
const session = this.parser.readSessionDir(sessionDir, sessionId);
|
|
111
|
+
if (!session) continue;
|
|
112
|
+
if (opts?.cwd !== undefined && session.projectPath !== opts.cwd) continue;
|
|
113
|
+
|
|
114
|
+
summaries.push({
|
|
115
|
+
type: this.type,
|
|
116
|
+
sessionId: session.sessionId,
|
|
117
|
+
cwd: session.projectPath,
|
|
118
|
+
firstUserMessage: session.firstUserMessage,
|
|
119
|
+
lastActive: session.lastActive,
|
|
120
|
+
startedAt: session.sessionStart,
|
|
121
|
+
sessionFilePath: session.eventsFilePath,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return summaries;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private applyWrapperRegistryName(
|
|
129
|
+
agent: AgentInfo,
|
|
130
|
+
processInfo: ProcessInfo,
|
|
131
|
+
processes: ProcessInfo[],
|
|
132
|
+
registryEntriesByPid: Map<number, RegistryEntry>,
|
|
133
|
+
): void {
|
|
134
|
+
const wrapper = findWrapperProcess(processes, processInfo);
|
|
135
|
+
const wrapperEntry = wrapper ? registryEntriesByPid.get(wrapper.pid) : undefined;
|
|
136
|
+
if (wrapperEntry?.type === this.type) {
|
|
137
|
+
agent.name = wrapperEntry.name;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private isCopilotExecutable(command: string): boolean {
|
|
142
|
+
const base = executableBasename(command);
|
|
143
|
+
return base === "copilot" || base === "copilot.exe";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 { CopilotSessionParser, type CopilotSession } from "./CopilotSessionParser.js";
|
|
5
|
+
|
|
6
|
+
export class CopilotAgentMapper {
|
|
7
|
+
constructor(private readonly parser: CopilotSessionParser = new CopilotSessionParser()) {}
|
|
8
|
+
|
|
9
|
+
mapSessionToAgent(session: CopilotSession, processInfo: ProcessInfo): AgentInfo {
|
|
10
|
+
const projectPath = session.projectPath || processInfo.cwd || "";
|
|
11
|
+
return {
|
|
12
|
+
name: generateAgentName(projectPath, processInfo.pid),
|
|
13
|
+
type: "copilot",
|
|
14
|
+
status: this.parser.determineStatus(session),
|
|
15
|
+
summary: session.summary || "Copilot session active",
|
|
16
|
+
pid: processInfo.pid,
|
|
17
|
+
projectPath,
|
|
18
|
+
sessionId: session.sessionId,
|
|
19
|
+
lastActive: session.lastActive,
|
|
20
|
+
sessionFilePath: session.eventsFilePath,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
mapProcessOnlyAgent(processInfo: ProcessInfo): AgentInfo {
|
|
25
|
+
return {
|
|
26
|
+
name: generateAgentName(processInfo.cwd || "", processInfo.pid),
|
|
27
|
+
type: "copilot",
|
|
28
|
+
status: AgentStatus.RUNNING,
|
|
29
|
+
summary: "Copilot process running",
|
|
30
|
+
pid: processInfo.pid,
|
|
31
|
+
projectPath: processInfo.cwd || "",
|
|
32
|
+
sessionId: `pid-${processInfo.pid}`,
|
|
33
|
+
lastActive: new Date(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { isDirectory, safeReaddir } from "../../utils/session.js";
|
|
3
|
+
|
|
4
|
+
export interface CopilotLock {
|
|
5
|
+
sessionDir: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
pid: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CopilotSessionDir {
|
|
11
|
+
sessionDir: string;
|
|
12
|
+
sessionId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CopilotSessionLocatorOptions {
|
|
16
|
+
sessionStateDir?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CopilotSessionLocator {
|
|
20
|
+
private readonly sessionStateDir: string;
|
|
21
|
+
|
|
22
|
+
constructor(options: CopilotSessionLocatorOptions = {}) {
|
|
23
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
24
|
+
this.sessionStateDir =
|
|
25
|
+
options.sessionStateDir ?? path.join(homeDir, ".copilot", "session-state");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
listSessionDirs(): CopilotSessionDir[] {
|
|
29
|
+
if (!isDirectory(this.sessionStateDir)) return [];
|
|
30
|
+
|
|
31
|
+
const sessionDirs: CopilotSessionDir[] = [];
|
|
32
|
+
for (const sessionId of safeReaddir(this.sessionStateDir)) {
|
|
33
|
+
const sessionDir = path.join(this.sessionStateDir, sessionId);
|
|
34
|
+
if (!isDirectory(sessionDir)) continue;
|
|
35
|
+
|
|
36
|
+
sessionDirs.push({ sessionDir, sessionId });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return sessionDirs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
discoverActiveLocks(): CopilotLock[] {
|
|
43
|
+
const locks: CopilotLock[] = [];
|
|
44
|
+
for (const { sessionDir, sessionId } of this.listSessionDirs()) {
|
|
45
|
+
for (const entry of safeReaddir(sessionDir)) {
|
|
46
|
+
const match = entry.match(/^inuse\.(\d+)\.lock$/);
|
|
47
|
+
if (!match) continue;
|
|
48
|
+
|
|
49
|
+
const pid = Number.parseInt(match[1], 10);
|
|
50
|
+
if (!Number.isFinite(pid)) continue;
|
|
51
|
+
locks.push({ sessionDir, sessionId, pid });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return locks;
|
|
56
|
+
}
|
|
57
|
+
}
|