@cydm/magic-shell-agent-node 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/pty-adapter.d.ts +18 -0
- package/dist/adapters/pty-adapter.js +99 -0
- package/dist/adapters/registry.d.ts +28 -0
- package/dist/adapters/registry.js +64 -0
- package/dist/adapters/rpc-adapter.d.ts +19 -0
- package/dist/adapters/rpc-adapter.js +182 -0
- package/dist/adapters/stdio-adapter.d.ts +17 -0
- package/dist/adapters/stdio-adapter.js +107 -0
- package/dist/adapters/types.d.ts +17 -0
- package/dist/adapters/types.js +2 -0
- package/dist/claude-exec.d.ts +11 -0
- package/dist/claude-exec.js +54 -0
- package/dist/claude-worker.d.ts +12 -0
- package/dist/claude-worker.js +163 -0
- package/dist/codex-exec.d.ts +12 -0
- package/dist/codex-exec.js +84 -0
- package/dist/codex-worker.d.ts +12 -0
- package/dist/codex-worker.js +179 -0
- package/dist/directory-browser.d.ts +3 -0
- package/dist/directory-browser.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/local-direct-server.d.ts +38 -0
- package/dist/local-direct-server.js +266 -0
- package/dist/node-conversation.d.ts +21 -0
- package/dist/node-conversation.js +28 -0
- package/dist/node-intent.d.ts +2 -0
- package/dist/node-intent.js +40 -0
- package/dist/node-reply.d.ts +30 -0
- package/dist/node-reply.js +77 -0
- package/dist/node.d.ts +132 -0
- package/dist/node.js +1954 -0
- package/dist/pie-session-control.d.ts +21 -0
- package/dist/pie-session-control.js +28 -0
- package/dist/plugin-loader.d.ts +19 -0
- package/dist/plugin-loader.js +144 -0
- package/dist/plugins/pie.json +7 -0
- package/dist/primary-agent-bridge.d.ts +69 -0
- package/dist/primary-agent-bridge.js +282 -0
- package/dist/session-manager.d.ts +66 -0
- package/dist/session-manager.js +197 -0
- package/dist/terminal-metadata.d.ts +7 -0
- package/dist/terminal-metadata.js +52 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/dist/worker-control.d.ts +15 -0
- package/dist/worker-control.js +89 -0
- package/dist/worker-narration.d.ts +25 -0
- package/dist/worker-narration.js +90 -0
- package/dist/worker-output.d.ts +6 -0
- package/dist/worker-output.js +72 -0
- package/dist/worker-registry.d.ts +45 -0
- package/dist/worker-registry.js +501 -0
- package/dist/worker-runtime.d.ts +18 -0
- package/dist/worker-runtime.js +69 -0
- package/dist/ws-client.d.ts +68 -0
- package/dist/ws-client.js +193 -0
- package/package.json +38 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
const DEFAULT_WORKER_NAMES = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Falcon", "Nova", "Scout"];
|
|
2
|
+
export class WorkerRegistry {
|
|
3
|
+
workers = new Map();
|
|
4
|
+
sessionToAgent = new Map();
|
|
5
|
+
recentEvents = new Map();
|
|
6
|
+
snapshots = new Map();
|
|
7
|
+
maxRecentEvents = 24;
|
|
8
|
+
maxSnapshots = 16;
|
|
9
|
+
allocateDisplayName() {
|
|
10
|
+
const used = new Set(Array.from(this.workers.values())
|
|
11
|
+
.map((worker) => worker.displayName)
|
|
12
|
+
.filter((value) => !!value));
|
|
13
|
+
for (const name of DEFAULT_WORKER_NAMES) {
|
|
14
|
+
if (!used.has(name)) {
|
|
15
|
+
return name;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return `worker-${this.workers.size + 1}`;
|
|
19
|
+
}
|
|
20
|
+
createWorkerRecord(options) {
|
|
21
|
+
const record = {
|
|
22
|
+
agentId: options.agentId,
|
|
23
|
+
agentType: options.plugin.name,
|
|
24
|
+
role: "worker",
|
|
25
|
+
sessionId: options.sessionId,
|
|
26
|
+
nodeId: options.nodeId,
|
|
27
|
+
parentAgentId: options.parentAgentId,
|
|
28
|
+
cwd: options.cwd || options.plugin.cwd || process.cwd(),
|
|
29
|
+
displayName: options.displayName || this.allocateDisplayName(),
|
|
30
|
+
taskSummary: options.taskSummary || "",
|
|
31
|
+
status: "starting",
|
|
32
|
+
phase: "booting",
|
|
33
|
+
attachEndpoint: {
|
|
34
|
+
kind: "session",
|
|
35
|
+
sessionId: options.sessionId,
|
|
36
|
+
},
|
|
37
|
+
controlEndpoint: {
|
|
38
|
+
kind: "local",
|
|
39
|
+
sessionId: options.sessionId,
|
|
40
|
+
},
|
|
41
|
+
createdAt: Date.now(),
|
|
42
|
+
activityUpdatedAt: Date.now(),
|
|
43
|
+
attachCount: 0,
|
|
44
|
+
restartCount: 0,
|
|
45
|
+
inspectCount: 0,
|
|
46
|
+
outputEventCount: 0,
|
|
47
|
+
outputCharCount: 0,
|
|
48
|
+
totalRunMs: 0,
|
|
49
|
+
capabilities: [...options.plugin.capabilities],
|
|
50
|
+
};
|
|
51
|
+
this.workers.set(record.agentId, record);
|
|
52
|
+
this.sessionToAgent.set(record.sessionId, record.agentId);
|
|
53
|
+
this.appendEvent(record.agentId, {
|
|
54
|
+
type: "spawned",
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
message: `${record.agentType} launched in ${record.cwd}`,
|
|
57
|
+
});
|
|
58
|
+
return record;
|
|
59
|
+
}
|
|
60
|
+
rebindWorkerSession(existingAgentId, options) {
|
|
61
|
+
const existing = this.workers.get(existingAgentId);
|
|
62
|
+
if (!existing)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (existingAgentId !== options.agentId) {
|
|
65
|
+
this.workers.delete(existingAgentId);
|
|
66
|
+
const existingEvents = this.recentEvents.get(existingAgentId) || [];
|
|
67
|
+
const existingSnapshots = this.snapshots.get(existingAgentId) || [];
|
|
68
|
+
this.recentEvents.delete(existingAgentId);
|
|
69
|
+
this.snapshots.delete(existingAgentId);
|
|
70
|
+
this.recentEvents.set(options.agentId, existingEvents);
|
|
71
|
+
this.snapshots.set(options.agentId, existingSnapshots);
|
|
72
|
+
}
|
|
73
|
+
const next = {
|
|
74
|
+
...existing,
|
|
75
|
+
agentId: options.agentId,
|
|
76
|
+
agentType: options.plugin.name,
|
|
77
|
+
nodeId: options.nodeId,
|
|
78
|
+
parentAgentId: options.parentAgentId,
|
|
79
|
+
cwd: options.cwd || options.plugin.cwd || existing.cwd,
|
|
80
|
+
displayName: options.displayName || existing.displayName,
|
|
81
|
+
taskSummary: options.taskSummary || existing.taskSummary,
|
|
82
|
+
status: "starting",
|
|
83
|
+
attachEndpoint: {
|
|
84
|
+
kind: "session",
|
|
85
|
+
sessionId: options.sessionId,
|
|
86
|
+
},
|
|
87
|
+
controlEndpoint: {
|
|
88
|
+
kind: "local",
|
|
89
|
+
sessionId: options.sessionId,
|
|
90
|
+
},
|
|
91
|
+
capabilities: [...options.plugin.capabilities],
|
|
92
|
+
startedAt: undefined,
|
|
93
|
+
lastAttachedAt: undefined,
|
|
94
|
+
stoppedAt: undefined,
|
|
95
|
+
exitCode: undefined,
|
|
96
|
+
lastError: undefined,
|
|
97
|
+
outputEventCount: existing.outputEventCount || 0,
|
|
98
|
+
outputCharCount: existing.outputCharCount || 0,
|
|
99
|
+
lastEventAt: undefined,
|
|
100
|
+
lastEventSummary: undefined,
|
|
101
|
+
};
|
|
102
|
+
this.workers.set(options.agentId, next);
|
|
103
|
+
this.sessionToAgent.set(options.sessionId, options.agentId);
|
|
104
|
+
this.appendEvent(options.agentId, {
|
|
105
|
+
type: "restarted",
|
|
106
|
+
timestamp: Date.now(),
|
|
107
|
+
message: `${next.agentType} relaunched in ${next.cwd}`,
|
|
108
|
+
});
|
|
109
|
+
return next;
|
|
110
|
+
}
|
|
111
|
+
getWorkerByAgentId(agentId) {
|
|
112
|
+
return this.refreshWorkerState(agentId);
|
|
113
|
+
}
|
|
114
|
+
getWorkerBySessionId(sessionId) {
|
|
115
|
+
const agentId = this.sessionToAgent.get(sessionId);
|
|
116
|
+
return agentId ? this.refreshWorkerState(agentId) : undefined;
|
|
117
|
+
}
|
|
118
|
+
listWorkers() {
|
|
119
|
+
return this.refreshAllWorkerStates().sort((a, b) => a.createdAt - b.createdAt);
|
|
120
|
+
}
|
|
121
|
+
updateWorker(agentId, updates) {
|
|
122
|
+
const existing = this.workers.get(agentId);
|
|
123
|
+
if (!existing)
|
|
124
|
+
return undefined;
|
|
125
|
+
const next = this.deriveWorkerState({ ...existing, ...updates });
|
|
126
|
+
this.workers.set(agentId, next);
|
|
127
|
+
this.captureSnapshot(existing, next);
|
|
128
|
+
return next;
|
|
129
|
+
}
|
|
130
|
+
getWorkerDetail(agentId, lastBufferedOutput) {
|
|
131
|
+
const worker = this.getWorkerByAgentId(agentId);
|
|
132
|
+
if (!worker)
|
|
133
|
+
return undefined;
|
|
134
|
+
return {
|
|
135
|
+
worker,
|
|
136
|
+
pluginName: worker.agentType,
|
|
137
|
+
lastBufferedOutput,
|
|
138
|
+
recentEvents: [...(this.recentEvents.get(agentId) || [])],
|
|
139
|
+
snapshots: [...(this.snapshots.get(agentId) || [])],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
getRuntimeSummary() {
|
|
143
|
+
const workers = this.listWorkers();
|
|
144
|
+
const liveWorkers = workers.filter((worker) => worker.status !== "stopped" && worker.status !== "failed");
|
|
145
|
+
const busyWorkers = liveWorkers.filter((worker) => worker.activityState === "busy").length;
|
|
146
|
+
const waitingWorkers = liveWorkers.filter((worker) => worker.activityState === "ready").length;
|
|
147
|
+
const quietWorkers = liveWorkers.filter((worker) => worker.phase === "quiet").length;
|
|
148
|
+
const attentionWorkers = liveWorkers.filter((worker) => this.workerNeedsAttention(worker)).length;
|
|
149
|
+
const failedWorkers = workers.filter((worker) => worker.status === "failed").length;
|
|
150
|
+
const watchWorkers = liveWorkers.filter((worker) => worker.interventionLevel === "watch").length;
|
|
151
|
+
const suggestedWorkers = liveWorkers.filter((worker) => worker.interventionLevel === "suggested").length;
|
|
152
|
+
const requiredWorkers = workers.filter((worker) => worker.interventionLevel === "required").length;
|
|
153
|
+
const observeWorkers = liveWorkers.filter((worker) => worker.recommendedAction === "observe").length;
|
|
154
|
+
const attachWorkers = liveWorkers.filter((worker) => worker.recommendedAction === "attach").length;
|
|
155
|
+
const restartWorkers = workers.filter((worker) => worker.recommendedAction === "restart").length;
|
|
156
|
+
const closeWorkers = workers.filter((worker) => worker.recommendedAction === "close").length;
|
|
157
|
+
const totalRestarts = workers.reduce((sum, worker) => sum + (worker.restartCount || 0), 0);
|
|
158
|
+
const totalAttaches = workers.reduce((sum, worker) => sum + (worker.attachCount || 0), 0);
|
|
159
|
+
const totalOutputChars = workers.reduce((sum, worker) => sum + (worker.outputCharCount || 0), 0);
|
|
160
|
+
const lastUpdatedAt = workers.reduce((latest, worker) => Math.max(latest, worker.lastEventAt || 0, worker.lastOutputAt || 0, worker.activityUpdatedAt || 0, worker.stoppedAt || 0, worker.startedAt || 0, worker.createdAt || 0), 0);
|
|
161
|
+
return {
|
|
162
|
+
totalWorkers: workers.length,
|
|
163
|
+
liveWorkers: liveWorkers.length,
|
|
164
|
+
closedWorkers: workers.length - liveWorkers.length,
|
|
165
|
+
busyWorkers,
|
|
166
|
+
waitingWorkers,
|
|
167
|
+
quietWorkers,
|
|
168
|
+
attentionWorkers,
|
|
169
|
+
failedWorkers,
|
|
170
|
+
watchWorkers,
|
|
171
|
+
suggestedWorkers,
|
|
172
|
+
requiredWorkers,
|
|
173
|
+
observeWorkers,
|
|
174
|
+
attachWorkers,
|
|
175
|
+
restartWorkers,
|
|
176
|
+
closeWorkers,
|
|
177
|
+
totalRestarts,
|
|
178
|
+
totalAttaches,
|
|
179
|
+
totalOutputChars,
|
|
180
|
+
lastUpdatedAt,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
getRuntimeFocus(limit = 5) {
|
|
184
|
+
const priority = (worker) => {
|
|
185
|
+
switch (worker.recommendedAction) {
|
|
186
|
+
case "restart":
|
|
187
|
+
return 5;
|
|
188
|
+
case "attach":
|
|
189
|
+
return worker.interventionLevel === "required" ? 4 : 3;
|
|
190
|
+
case "observe":
|
|
191
|
+
return worker.interventionLevel === "watch" ? 2 : 1;
|
|
192
|
+
case "close":
|
|
193
|
+
return 0;
|
|
194
|
+
default:
|
|
195
|
+
return -1;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
return this.listWorkers()
|
|
199
|
+
.filter((worker) => worker.recommendedAction && worker.recommendedAction !== "none")
|
|
200
|
+
.sort((a, b) => {
|
|
201
|
+
const byPriority = priority(b) - priority(a);
|
|
202
|
+
if (byPriority !== 0)
|
|
203
|
+
return byPriority;
|
|
204
|
+
return (b.lastEventAt || b.activityUpdatedAt || b.lastOutputAt || b.createdAt)
|
|
205
|
+
- (a.lastEventAt || a.activityUpdatedAt || a.lastOutputAt || a.createdAt);
|
|
206
|
+
})
|
|
207
|
+
.slice(0, limit)
|
|
208
|
+
.map((worker) => ({
|
|
209
|
+
agentId: worker.agentId,
|
|
210
|
+
sessionId: worker.sessionId,
|
|
211
|
+
title: worker.displayName || worker.taskSummary || worker.agentSessionId || `S:${worker.sessionId.slice(-6)}`,
|
|
212
|
+
displayName: worker.displayName,
|
|
213
|
+
agentType: worker.agentType,
|
|
214
|
+
coordinationState: worker.coordinationState,
|
|
215
|
+
phase: worker.phase,
|
|
216
|
+
interventionLevel: worker.interventionLevel,
|
|
217
|
+
recommendedAction: worker.recommendedAction,
|
|
218
|
+
reason: worker.recommendedActionReason || worker.interventionReason || worker.attentionReason || worker.lastEventSummary,
|
|
219
|
+
updatedAt: worker.lastEventAt || worker.activityUpdatedAt || worker.lastOutputAt || worker.createdAt,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
updateWorkerStatus(agentId, status, details = {}) {
|
|
223
|
+
const updates = { ...details, status };
|
|
224
|
+
if (status === "running" && updates.startedAt === undefined) {
|
|
225
|
+
updates.startedAt = Date.now();
|
|
226
|
+
}
|
|
227
|
+
if ((status === "stopped" || status === "failed") && updates.stoppedAt === undefined) {
|
|
228
|
+
updates.stoppedAt = Date.now();
|
|
229
|
+
}
|
|
230
|
+
const next = this.updateWorker(agentId, updates);
|
|
231
|
+
if (next) {
|
|
232
|
+
if ((status === "stopped" || status === "failed") && next.startedAt) {
|
|
233
|
+
const totalRunMs = (next.totalRunMs || 0) + Math.max(0, Date.now() - next.startedAt);
|
|
234
|
+
this.updateWorker(agentId, { totalRunMs });
|
|
235
|
+
}
|
|
236
|
+
this.appendEvent(agentId, {
|
|
237
|
+
type: status === "stopped" ? "stopped" : "state_changed",
|
|
238
|
+
timestamp: Date.now(),
|
|
239
|
+
message: `status -> ${status}`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return next;
|
|
243
|
+
}
|
|
244
|
+
removeWorker(agentId) {
|
|
245
|
+
const worker = this.workers.get(agentId);
|
|
246
|
+
if (!worker)
|
|
247
|
+
return false;
|
|
248
|
+
this.sessionToAgent.delete(worker.sessionId);
|
|
249
|
+
return this.workers.delete(agentId);
|
|
250
|
+
}
|
|
251
|
+
recordOutput(sessionId) {
|
|
252
|
+
const worker = this.getWorkerBySessionId(sessionId);
|
|
253
|
+
if (!worker)
|
|
254
|
+
return undefined;
|
|
255
|
+
return this.recordOutputActivity(worker.agentId, 0);
|
|
256
|
+
}
|
|
257
|
+
recordOutputActivity(agentId, charCount) {
|
|
258
|
+
const worker = this.getWorkerByAgentId(agentId);
|
|
259
|
+
if (!worker)
|
|
260
|
+
return undefined;
|
|
261
|
+
return this.updateWorker(worker.agentId, {
|
|
262
|
+
lastOutputAt: Date.now(),
|
|
263
|
+
status: worker.status === "starting" ? "running" : worker.status,
|
|
264
|
+
outputEventCount: (worker.outputEventCount || 0) + 1,
|
|
265
|
+
outputCharCount: (worker.outputCharCount || 0) + Math.max(0, charCount),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
recordEvent(agentId, event) {
|
|
269
|
+
this.appendEvent(agentId, event);
|
|
270
|
+
}
|
|
271
|
+
appendEvent(agentId, event) {
|
|
272
|
+
const normalizedEvent = this.normalizeEvent(event);
|
|
273
|
+
const next = [...(this.recentEvents.get(agentId) || []), normalizedEvent].slice(-this.maxRecentEvents);
|
|
274
|
+
this.recentEvents.set(agentId, next);
|
|
275
|
+
const worker = this.workers.get(agentId);
|
|
276
|
+
if (worker) {
|
|
277
|
+
const updates = {
|
|
278
|
+
lastEventAt: normalizedEvent.timestamp,
|
|
279
|
+
lastEventSummary: this.summarizeEvent(normalizedEvent.message),
|
|
280
|
+
lastEventLevel: normalizedEvent.level,
|
|
281
|
+
};
|
|
282
|
+
if (normalizedEvent.type === "attached") {
|
|
283
|
+
updates.attachCount = (worker.attachCount || 0) + 1;
|
|
284
|
+
updates.lastAttachedAt = normalizedEvent.timestamp;
|
|
285
|
+
}
|
|
286
|
+
if (normalizedEvent.type === "restarted") {
|
|
287
|
+
updates.restartCount = (worker.restartCount || 0) + 1;
|
|
288
|
+
}
|
|
289
|
+
if (normalizedEvent.type === "inspected") {
|
|
290
|
+
updates.inspectCount = (worker.inspectCount || 0) + 1;
|
|
291
|
+
}
|
|
292
|
+
this.workers.set(agentId, {
|
|
293
|
+
...worker,
|
|
294
|
+
...updates,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
summarizeEvent(message) {
|
|
299
|
+
return message.replace(/\s+/g, " ").trim().slice(0, 96);
|
|
300
|
+
}
|
|
301
|
+
normalizeEvent(event) {
|
|
302
|
+
return {
|
|
303
|
+
...event,
|
|
304
|
+
level: event.level || this.inferEventLevel(event),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
inferEventLevel(event) {
|
|
308
|
+
switch (event.type) {
|
|
309
|
+
case "spawned":
|
|
310
|
+
case "attached":
|
|
311
|
+
case "restarted":
|
|
312
|
+
case "inspected":
|
|
313
|
+
return "control";
|
|
314
|
+
case "error":
|
|
315
|
+
return "error";
|
|
316
|
+
case "stopped":
|
|
317
|
+
return "warning";
|
|
318
|
+
default:
|
|
319
|
+
return "info";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
captureSnapshot(previous, next) {
|
|
323
|
+
const changed = previous.status !== next.status
|
|
324
|
+
|| previous.phase !== next.phase
|
|
325
|
+
|| previous.activityState !== next.activityState
|
|
326
|
+
|| previous.interventionLevel !== next.interventionLevel;
|
|
327
|
+
if (!changed)
|
|
328
|
+
return;
|
|
329
|
+
const snapshot = {
|
|
330
|
+
timestamp: Date.now(),
|
|
331
|
+
status: next.status,
|
|
332
|
+
phase: next.phase,
|
|
333
|
+
activityState: next.activityState,
|
|
334
|
+
interventionLevel: next.interventionLevel,
|
|
335
|
+
summary: this.summarizeSnapshot(next),
|
|
336
|
+
};
|
|
337
|
+
const history = [...(this.snapshots.get(next.agentId) || []), snapshot].slice(-this.maxSnapshots);
|
|
338
|
+
this.snapshots.set(next.agentId, history);
|
|
339
|
+
}
|
|
340
|
+
summarizeSnapshot(worker) {
|
|
341
|
+
const parts = [worker.status];
|
|
342
|
+
if (worker.coordinationState && worker.coordinationState !== "idle")
|
|
343
|
+
parts.push(worker.coordinationState);
|
|
344
|
+
if (worker.phase)
|
|
345
|
+
parts.push(worker.phase);
|
|
346
|
+
if (worker.activityState)
|
|
347
|
+
parts.push(worker.activityState);
|
|
348
|
+
if (worker.interventionLevel && worker.interventionLevel !== "none") {
|
|
349
|
+
parts.push(worker.interventionLevel);
|
|
350
|
+
}
|
|
351
|
+
return parts.join(" · ");
|
|
352
|
+
}
|
|
353
|
+
workerNeedsAttention(worker) {
|
|
354
|
+
if (worker.status === "blocked" || worker.status === "failed")
|
|
355
|
+
return true;
|
|
356
|
+
if (worker.activityState !== "busy" || !worker.activityUpdatedAt || !worker.lastOutputAt)
|
|
357
|
+
return false;
|
|
358
|
+
return (Date.now() - worker.activityUpdatedAt) > 90_000 && (Date.now() - worker.lastOutputAt) > 45_000;
|
|
359
|
+
}
|
|
360
|
+
deriveWorkerState(worker) {
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
let phase = worker.phase;
|
|
363
|
+
let activityState = worker.activityState;
|
|
364
|
+
let attentionReason = undefined;
|
|
365
|
+
let interventionLevel = "none";
|
|
366
|
+
let interventionReason = undefined;
|
|
367
|
+
let recommendedAction = "none";
|
|
368
|
+
let recommendedActionReason = undefined;
|
|
369
|
+
if (worker.status === "starting" || worker.status === "stopping") {
|
|
370
|
+
phase = "booting";
|
|
371
|
+
}
|
|
372
|
+
else if (worker.status === "stopped" || worker.status === "failed") {
|
|
373
|
+
phase = "closed";
|
|
374
|
+
if (worker.status === "failed") {
|
|
375
|
+
attentionReason = worker.lastError || "worker failed";
|
|
376
|
+
interventionLevel = "required";
|
|
377
|
+
interventionReason = attentionReason;
|
|
378
|
+
recommendedAction = "restart";
|
|
379
|
+
recommendedActionReason = "worker failed";
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
recommendedAction = "close";
|
|
383
|
+
recommendedActionReason = "worker already stopped";
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else if (worker.status === "blocked") {
|
|
387
|
+
phase = "attention";
|
|
388
|
+
attentionReason = worker.lastError || "worker reported blocked";
|
|
389
|
+
interventionLevel = "required";
|
|
390
|
+
interventionReason = attentionReason;
|
|
391
|
+
recommendedAction = "attach";
|
|
392
|
+
recommendedActionReason = attentionReason;
|
|
393
|
+
}
|
|
394
|
+
else if (this.workerNeedsAttention(worker)) {
|
|
395
|
+
phase = "attention";
|
|
396
|
+
attentionReason = "busy for a while without fresh output";
|
|
397
|
+
interventionLevel = "suggested";
|
|
398
|
+
interventionReason = attentionReason;
|
|
399
|
+
recommendedAction = "attach";
|
|
400
|
+
recommendedActionReason = attentionReason;
|
|
401
|
+
}
|
|
402
|
+
else if (worker.coordinationState === "delegating" || worker.coordinationState === "waiting_for_result") {
|
|
403
|
+
phase = "working";
|
|
404
|
+
activityState = "busy";
|
|
405
|
+
recommendedAction = "observe";
|
|
406
|
+
recommendedActionReason = worker.coordinationTaskSummary
|
|
407
|
+
? `delegated turn in progress: ${worker.coordinationTaskSummary}`
|
|
408
|
+
: "delegated turn in progress";
|
|
409
|
+
}
|
|
410
|
+
else if (worker.coordinationState === "result_received" || worker.coordinationState === "integrating_result") {
|
|
411
|
+
phase = "waiting";
|
|
412
|
+
activityState = "ready";
|
|
413
|
+
recommendedAction = "observe";
|
|
414
|
+
recommendedActionReason = "delegated turn completed";
|
|
415
|
+
}
|
|
416
|
+
else if (worker.activityState === "busy") {
|
|
417
|
+
phase = "working";
|
|
418
|
+
recommendedAction = "observe";
|
|
419
|
+
recommendedActionReason = "worker is actively working";
|
|
420
|
+
}
|
|
421
|
+
else if (worker.activityState === "ready") {
|
|
422
|
+
const outputAgeMs = worker.lastOutputAt ? now - worker.lastOutputAt : null;
|
|
423
|
+
if (outputAgeMs !== null && outputAgeMs > 600_000) {
|
|
424
|
+
phase = "quiet";
|
|
425
|
+
interventionLevel = "watch";
|
|
426
|
+
interventionReason = "quiet for a while";
|
|
427
|
+
recommendedAction = "observe";
|
|
428
|
+
recommendedActionReason = "worker has been quiet for a while";
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
phase = "waiting";
|
|
432
|
+
recommendedAction = "attach";
|
|
433
|
+
recommendedActionReason = "worker appears ready for input";
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else if (worker.lastOutputAt && (now - worker.lastOutputAt) < 45_000) {
|
|
437
|
+
phase = "working";
|
|
438
|
+
recommendedAction = "observe";
|
|
439
|
+
recommendedActionReason = "worker has recent activity";
|
|
440
|
+
}
|
|
441
|
+
else if (worker.lastOutputAt && (now - worker.lastOutputAt) > 600_000) {
|
|
442
|
+
phase = "quiet";
|
|
443
|
+
interventionLevel = "watch";
|
|
444
|
+
interventionReason = "quiet for a while";
|
|
445
|
+
recommendedAction = "observe";
|
|
446
|
+
recommendedActionReason = "worker has been idle for a while";
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
phase = "waiting";
|
|
450
|
+
recommendedAction = "attach";
|
|
451
|
+
recommendedActionReason = "worker is waiting";
|
|
452
|
+
}
|
|
453
|
+
let interventionSince = undefined;
|
|
454
|
+
if (interventionLevel && interventionLevel !== "none") {
|
|
455
|
+
interventionSince = worker.interventionLevel === interventionLevel
|
|
456
|
+
? worker.interventionSince || now
|
|
457
|
+
: now;
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
...worker,
|
|
461
|
+
activityState,
|
|
462
|
+
phase,
|
|
463
|
+
attentionReason,
|
|
464
|
+
interventionLevel,
|
|
465
|
+
interventionReason,
|
|
466
|
+
interventionSince,
|
|
467
|
+
recommendedAction,
|
|
468
|
+
recommendedActionReason,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
refreshAllWorkerStates() {
|
|
472
|
+
const refreshed = [];
|
|
473
|
+
for (const worker of this.workers.values()) {
|
|
474
|
+
refreshed.push(this.refreshWorkerState(worker.agentId) || worker);
|
|
475
|
+
}
|
|
476
|
+
return refreshed;
|
|
477
|
+
}
|
|
478
|
+
refreshWorkerState(agentId) {
|
|
479
|
+
const existing = this.workers.get(agentId);
|
|
480
|
+
if (!existing)
|
|
481
|
+
return undefined;
|
|
482
|
+
const next = this.deriveWorkerState(existing);
|
|
483
|
+
if (this.hasDerivedStateChanged(existing, next)) {
|
|
484
|
+
this.workers.set(agentId, next);
|
|
485
|
+
this.captureSnapshot(existing, next);
|
|
486
|
+
return next;
|
|
487
|
+
}
|
|
488
|
+
return existing;
|
|
489
|
+
}
|
|
490
|
+
hasDerivedStateChanged(previous, next) {
|
|
491
|
+
return previous.status !== next.status
|
|
492
|
+
|| previous.phase !== next.phase
|
|
493
|
+
|| previous.activityState !== next.activityState
|
|
494
|
+
|| previous.attentionReason !== next.attentionReason
|
|
495
|
+
|| previous.interventionLevel !== next.interventionLevel
|
|
496
|
+
|| previous.interventionReason !== next.interventionReason
|
|
497
|
+
|| previous.interventionSince !== next.interventionSince
|
|
498
|
+
|| previous.recommendedAction !== next.recommendedAction
|
|
499
|
+
|| previous.recommendedActionReason !== next.recommendedActionReason;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PluginConfig, ServerMessage } from "./types.js";
|
|
2
|
+
import type { SessionManager } from "./session-manager.js";
|
|
3
|
+
import type { WorkerRegistry } from "./worker-registry.js";
|
|
4
|
+
export interface SpawnWorkerRuntimeOptions {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
pluginName: string;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
displayName?: string;
|
|
9
|
+
taskSummary?: string;
|
|
10
|
+
parentAgentId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SpawnWorkerRuntimeDeps {
|
|
13
|
+
nodeId: string;
|
|
14
|
+
plugins: Map<string, PluginConfig>;
|
|
15
|
+
sessionManager: SessionManager;
|
|
16
|
+
workerRegistry: WorkerRegistry;
|
|
17
|
+
}
|
|
18
|
+
export declare function spawnManagedWorker(options: SpawnWorkerRuntimeOptions, deps: SpawnWorkerRuntimeDeps): Promise<ServerMessage>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function normalizeWorkerCwd(cwd, pluginCwd) {
|
|
4
|
+
const value = (cwd || pluginCwd || "").trim();
|
|
5
|
+
if (!value)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (value === "~") {
|
|
8
|
+
return os.homedir();
|
|
9
|
+
}
|
|
10
|
+
if (value.startsWith("~/")) {
|
|
11
|
+
return path.join(os.homedir(), value.slice(2));
|
|
12
|
+
}
|
|
13
|
+
if (path.isAbsolute(value)) {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return path.resolve(pluginCwd || process.cwd(), value);
|
|
17
|
+
}
|
|
18
|
+
export async function spawnManagedWorker(options, deps) {
|
|
19
|
+
const plugin = deps.plugins.get(options.pluginName);
|
|
20
|
+
if (!plugin) {
|
|
21
|
+
throw new Error(`Plugin not found: ${options.pluginName}`);
|
|
22
|
+
}
|
|
23
|
+
const runtimePlugin = {
|
|
24
|
+
...plugin,
|
|
25
|
+
args: plugin.name === "pie"
|
|
26
|
+
? [...(plugin.args || []), "--session-id", options.sessionId]
|
|
27
|
+
: plugin.args,
|
|
28
|
+
cwd: normalizeWorkerCwd(options.cwd, plugin.cwd),
|
|
29
|
+
};
|
|
30
|
+
await deps.sessionManager.createSession(options.sessionId, runtimePlugin);
|
|
31
|
+
const session = deps.sessionManager.getSession(options.sessionId);
|
|
32
|
+
if (!session) {
|
|
33
|
+
throw new Error(`Session failed to start: ${options.sessionId}`);
|
|
34
|
+
}
|
|
35
|
+
const existing = deps.workerRegistry.getWorkerBySessionId(options.sessionId);
|
|
36
|
+
if (!existing) {
|
|
37
|
+
deps.workerRegistry.createWorkerRecord({
|
|
38
|
+
agentId: session.agentId,
|
|
39
|
+
sessionId: options.sessionId,
|
|
40
|
+
nodeId: deps.nodeId,
|
|
41
|
+
plugin: runtimePlugin,
|
|
42
|
+
cwd: runtimePlugin.cwd,
|
|
43
|
+
displayName: options.displayName,
|
|
44
|
+
taskSummary: options.taskSummary,
|
|
45
|
+
parentAgentId: options.parentAgentId,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
deps.workerRegistry.rebindWorkerSession(existing.agentId, {
|
|
50
|
+
agentId: session.agentId,
|
|
51
|
+
sessionId: options.sessionId,
|
|
52
|
+
nodeId: deps.nodeId,
|
|
53
|
+
plugin: runtimePlugin,
|
|
54
|
+
cwd: runtimePlugin.cwd,
|
|
55
|
+
displayName: existing.displayName,
|
|
56
|
+
taskSummary: existing.taskSummary,
|
|
57
|
+
parentAgentId: existing.parentAgentId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
deps.workerRegistry.updateWorkerStatus(session.agentId, "running", {
|
|
61
|
+
startedAt: Date.now(),
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
type: "session",
|
|
65
|
+
sessionId: options.sessionId,
|
|
66
|
+
agentType: runtimePlugin.name,
|
|
67
|
+
capabilities: runtimePlugin.capabilities,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ClientMessage, ServerMessage } from "@cydm/magic-shell-protocol";
|
|
2
|
+
export interface WebSocketClientOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
nodeId: string;
|
|
5
|
+
password: string;
|
|
6
|
+
autoReconnect?: boolean;
|
|
7
|
+
reconnectDelay?: number;
|
|
8
|
+
maxReconnectDelay?: number;
|
|
9
|
+
}
|
|
10
|
+
type MessageHandler = (message: ServerMessage | ClientMessage) => void;
|
|
11
|
+
type StatusHandler = (status: "connected" | "disconnected" | "error", error?: Error) => void;
|
|
12
|
+
/**
|
|
13
|
+
* WebSocket 客户端
|
|
14
|
+
* 负责与 Relay Server 保持连接,自动重连
|
|
15
|
+
*/
|
|
16
|
+
export declare class WebSocketClient {
|
|
17
|
+
private options;
|
|
18
|
+
private ws?;
|
|
19
|
+
private messageHandlers;
|
|
20
|
+
private statusHandlers;
|
|
21
|
+
private reconnectAttempts;
|
|
22
|
+
private reconnectTimer?;
|
|
23
|
+
private isConnected;
|
|
24
|
+
private isConnecting;
|
|
25
|
+
private manualDisconnect;
|
|
26
|
+
constructor(options: WebSocketClientOptions);
|
|
27
|
+
/**
|
|
28
|
+
* 连接到 Relay Server
|
|
29
|
+
*/
|
|
30
|
+
connect(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* 断开连接
|
|
33
|
+
*/
|
|
34
|
+
disconnect(): void;
|
|
35
|
+
/**
|
|
36
|
+
* 发送消息
|
|
37
|
+
*/
|
|
38
|
+
send(message: ClientMessage | ServerMessage): void;
|
|
39
|
+
/**
|
|
40
|
+
* 注册消息处理器
|
|
41
|
+
*/
|
|
42
|
+
onMessage(handler: MessageHandler): void;
|
|
43
|
+
/**
|
|
44
|
+
* 注册状态处理器
|
|
45
|
+
*/
|
|
46
|
+
onStatus(handler: StatusHandler): void;
|
|
47
|
+
/**
|
|
48
|
+
* 获取连接状态
|
|
49
|
+
*/
|
|
50
|
+
getStatus(): "connected" | "disconnected" | "connecting";
|
|
51
|
+
/**
|
|
52
|
+
* 处理断开连接
|
|
53
|
+
*/
|
|
54
|
+
private handleDisconnect;
|
|
55
|
+
/**
|
|
56
|
+
* 安排重连
|
|
57
|
+
*/
|
|
58
|
+
private scheduleReconnect;
|
|
59
|
+
/**
|
|
60
|
+
* 触发消息事件
|
|
61
|
+
*/
|
|
62
|
+
private emitMessage;
|
|
63
|
+
/**
|
|
64
|
+
* 触发状态事件
|
|
65
|
+
*/
|
|
66
|
+
private emitStatus;
|
|
67
|
+
}
|
|
68
|
+
export {};
|