@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.
Files changed (58) hide show
  1. package/dist/adapters/pty-adapter.d.ts +18 -0
  2. package/dist/adapters/pty-adapter.js +99 -0
  3. package/dist/adapters/registry.d.ts +28 -0
  4. package/dist/adapters/registry.js +64 -0
  5. package/dist/adapters/rpc-adapter.d.ts +19 -0
  6. package/dist/adapters/rpc-adapter.js +182 -0
  7. package/dist/adapters/stdio-adapter.d.ts +17 -0
  8. package/dist/adapters/stdio-adapter.js +107 -0
  9. package/dist/adapters/types.d.ts +17 -0
  10. package/dist/adapters/types.js +2 -0
  11. package/dist/claude-exec.d.ts +11 -0
  12. package/dist/claude-exec.js +54 -0
  13. package/dist/claude-worker.d.ts +12 -0
  14. package/dist/claude-worker.js +163 -0
  15. package/dist/codex-exec.d.ts +12 -0
  16. package/dist/codex-exec.js +84 -0
  17. package/dist/codex-worker.d.ts +12 -0
  18. package/dist/codex-worker.js +179 -0
  19. package/dist/directory-browser.d.ts +3 -0
  20. package/dist/directory-browser.js +48 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +2 -0
  23. package/dist/local-direct-server.d.ts +38 -0
  24. package/dist/local-direct-server.js +266 -0
  25. package/dist/node-conversation.d.ts +21 -0
  26. package/dist/node-conversation.js +28 -0
  27. package/dist/node-intent.d.ts +2 -0
  28. package/dist/node-intent.js +40 -0
  29. package/dist/node-reply.d.ts +30 -0
  30. package/dist/node-reply.js +77 -0
  31. package/dist/node.d.ts +132 -0
  32. package/dist/node.js +1954 -0
  33. package/dist/pie-session-control.d.ts +21 -0
  34. package/dist/pie-session-control.js +28 -0
  35. package/dist/plugin-loader.d.ts +19 -0
  36. package/dist/plugin-loader.js +144 -0
  37. package/dist/plugins/pie.json +7 -0
  38. package/dist/primary-agent-bridge.d.ts +69 -0
  39. package/dist/primary-agent-bridge.js +282 -0
  40. package/dist/session-manager.d.ts +66 -0
  41. package/dist/session-manager.js +197 -0
  42. package/dist/terminal-metadata.d.ts +7 -0
  43. package/dist/terminal-metadata.js +52 -0
  44. package/dist/types.d.ts +1 -0
  45. package/dist/types.js +1 -0
  46. package/dist/worker-control.d.ts +15 -0
  47. package/dist/worker-control.js +89 -0
  48. package/dist/worker-narration.d.ts +25 -0
  49. package/dist/worker-narration.js +90 -0
  50. package/dist/worker-output.d.ts +6 -0
  51. package/dist/worker-output.js +72 -0
  52. package/dist/worker-registry.d.ts +45 -0
  53. package/dist/worker-registry.js +501 -0
  54. package/dist/worker-runtime.d.ts +18 -0
  55. package/dist/worker-runtime.js +69 -0
  56. package/dist/ws-client.d.ts +68 -0
  57. package/dist/ws-client.js +193 -0
  58. 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 {};