@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,197 @@
1
+ import { adapterRegistry } from "./adapters/registry.js";
2
+ /**
3
+ * 会话管理器
4
+ * 管理所有终端会话的生命周期
5
+ */
6
+ export class SessionManager {
7
+ registry;
8
+ sessions = new Map();
9
+ outputCallbacks = [];
10
+ exitCallbacks = [];
11
+ maxBufferedOutput = 200_000;
12
+ constructor(registry = adapterRegistry) {
13
+ this.registry = registry;
14
+ }
15
+ /**
16
+ * 创建或恢复会话
17
+ */
18
+ async createSession(sessionId, plugin) {
19
+ // 如果会话已存在,直接返回(支持重连恢复)
20
+ if (this.sessions.has(sessionId)) {
21
+ console.log(`[SessionManager] Session ${sessionId} already exists, reusing`);
22
+ return;
23
+ }
24
+ const adapter = this.registry.findAdapter(plugin);
25
+ const agentId = await adapter.start(plugin);
26
+ const session = {
27
+ id: sessionId,
28
+ agentId,
29
+ plugin,
30
+ createdAt: Date.now(),
31
+ lastActivity: Date.now(),
32
+ cols: 80,
33
+ rows: 24,
34
+ outputBuffer: "",
35
+ };
36
+ // 设置输出回调
37
+ adapter.onOutput((aid, output) => {
38
+ if (aid === agentId) {
39
+ session.lastActivity = Date.now();
40
+ session.outputBuffer = this.appendToBuffer(session.outputBuffer, output.content);
41
+ session.outputCallback?.(output);
42
+ this.emitOutput(sessionId, output);
43
+ }
44
+ });
45
+ // 设置退出回调
46
+ adapter.onExit((aid, code) => {
47
+ if (aid === agentId) {
48
+ console.log(`[SessionManager] Session ${sessionId} exited with code ${code}`);
49
+ const currentSession = this.sessions.get(sessionId);
50
+ if (currentSession?.agentId === aid) {
51
+ this.sessions.delete(sessionId);
52
+ }
53
+ this.emitExit(sessionId, aid, code);
54
+ }
55
+ });
56
+ this.sessions.set(sessionId, session);
57
+ console.log(`[SessionManager] Created session ${sessionId} with agent ${agentId}`);
58
+ }
59
+ /**
60
+ * 销毁会话
61
+ */
62
+ async destroySession(sessionId) {
63
+ const session = this.sessions.get(sessionId);
64
+ if (!session) {
65
+ console.warn(`[SessionManager] Session not found: ${sessionId}`);
66
+ return;
67
+ }
68
+ const adapter = this.registry.findAdapter(session.plugin);
69
+ await adapter.stop(session.agentId);
70
+ this.sessions.delete(sessionId);
71
+ console.log(`[SessionManager] Destroyed session ${sessionId}`);
72
+ }
73
+ /**
74
+ * 发送输入到会话
75
+ */
76
+ sendInput(sessionId, data) {
77
+ const session = this.sessions.get(sessionId);
78
+ if (!session) {
79
+ console.warn(`[SessionManager] Cannot send input, session not found: ${sessionId}`);
80
+ return;
81
+ }
82
+ session.lastActivity = Date.now();
83
+ const adapter = this.registry.findAdapter(session.plugin);
84
+ // 检查是否有 sendRawInput 方法(PTY 模式)
85
+ if (adapter.sendRawInput) {
86
+ adapter.sendRawInput(session.agentId, data);
87
+ }
88
+ else {
89
+ adapter.sendInput(session.agentId, { text: data });
90
+ }
91
+ }
92
+ /**
93
+ * 调整终端尺寸
94
+ */
95
+ resize(sessionId, cols, rows) {
96
+ const session = this.sessions.get(sessionId);
97
+ if (!session) {
98
+ console.warn(`[SessionManager] Cannot resize, session not found: ${sessionId}`);
99
+ return;
100
+ }
101
+ session.cols = cols;
102
+ session.rows = rows;
103
+ const adapter = this.registry.findAdapter(session.plugin);
104
+ if (adapter.resize) {
105
+ adapter.resize(session.agentId, cols, rows);
106
+ }
107
+ }
108
+ /**
109
+ * 获取会话信息
110
+ */
111
+ getSession(sessionId) {
112
+ return this.sessions.get(sessionId);
113
+ }
114
+ /**
115
+ * 列出所有会话
116
+ */
117
+ listSessions() {
118
+ return Array.from(this.sessions.values());
119
+ }
120
+ getBufferedOutput(sessionId) {
121
+ return this.sessions.get(sessionId)?.outputBuffer || "";
122
+ }
123
+ appendOutput(sessionId, content) {
124
+ if (!content)
125
+ return;
126
+ const session = this.sessions.get(sessionId);
127
+ if (!session)
128
+ return;
129
+ session.lastActivity = Date.now();
130
+ session.outputBuffer = this.appendToBuffer(session.outputBuffer, content);
131
+ const output = {
132
+ timestamp: Date.now(),
133
+ type: "output",
134
+ content,
135
+ };
136
+ session.outputCallback?.(output);
137
+ this.emitOutput(sessionId, output);
138
+ }
139
+ /**
140
+ * 停止所有会话
141
+ */
142
+ async stopAll() {
143
+ console.log(`[SessionManager] Stopping ${this.sessions.size} sessions...`);
144
+ const promises = [];
145
+ for (const [sessionId, session] of this.sessions) {
146
+ const adapter = this.registry.findAdapter(session.plugin);
147
+ promises.push(adapter.stop(session.agentId).catch((err) => {
148
+ console.error(`[SessionManager] Error stopping session ${sessionId}:`, err);
149
+ }));
150
+ }
151
+ await Promise.all(promises);
152
+ this.sessions.clear();
153
+ console.log("[SessionManager] All sessions stopped");
154
+ }
155
+ /**
156
+ * 注册输出回调
157
+ */
158
+ onOutput(callback) {
159
+ this.outputCallbacks.push(callback);
160
+ }
161
+ onExit(callback) {
162
+ this.exitCallbacks.push(callback);
163
+ }
164
+ /**
165
+ * 触发输出事件
166
+ */
167
+ emitOutput(sessionId, output) {
168
+ console.log(`[SessionManager] emitOutput: ${sessionId}, ${output.content.length} chars, ${this.outputCallbacks.length} callbacks`);
169
+ for (const callback of this.outputCallbacks) {
170
+ try {
171
+ callback(sessionId, output);
172
+ }
173
+ catch (err) {
174
+ console.error("[SessionManager] Output callback error:", err);
175
+ }
176
+ }
177
+ }
178
+ emitExit(sessionId, agentId, code) {
179
+ for (const callback of this.exitCallbacks) {
180
+ try {
181
+ callback(sessionId, agentId, code);
182
+ }
183
+ catch (err) {
184
+ console.error("[SessionManager] Exit callback error:", err);
185
+ }
186
+ }
187
+ }
188
+ appendToBuffer(current, chunk) {
189
+ if (!chunk)
190
+ return current;
191
+ const next = current + chunk;
192
+ if (next.length <= this.maxBufferedOutput) {
193
+ return next;
194
+ }
195
+ return next.slice(next.length - this.maxBufferedOutput);
196
+ }
197
+ }
@@ -0,0 +1,7 @@
1
+ export interface TerminalMetadata {
2
+ title?: string;
3
+ activityState?: "ready" | "busy";
4
+ agentSessionId?: string;
5
+ }
6
+ export declare function normalizeWorkerTitleCandidate(raw: string): string | null;
7
+ export declare function extractTerminalMetadata(output: string, agentType: string): TerminalMetadata | null;
@@ -0,0 +1,52 @@
1
+ export function normalizeWorkerTitleCandidate(raw) {
2
+ const cleaned = raw
3
+ .split(/\n{2,}/)[0]
4
+ .split("\n")[0]
5
+ .replace(/Work in your own session.*$/i, "")
6
+ .replace(/Reply to the primary agent now.*$/i, "")
7
+ .replace(/\s+/g, " ")
8
+ .trim();
9
+ const title = cleaned.slice(0, 48);
10
+ if (!title)
11
+ return null;
12
+ if (title.startsWith("/"))
13
+ return null;
14
+ return title;
15
+ }
16
+ export function extractTerminalMetadata(output, agentType) {
17
+ const matches = Array.from(output.matchAll(/\x1b\](?:0|2);([^\x07\x1b]*)(?:\x07|\x1b\\)/g));
18
+ if (!matches.length) {
19
+ return null;
20
+ }
21
+ const rawTitle = matches[matches.length - 1]?.[1]?.trim();
22
+ if (!rawTitle) {
23
+ return null;
24
+ }
25
+ let visibleTitle = rawTitle;
26
+ const metadata = {};
27
+ const metadataMatch = rawTitle.match(/\s+\[ms:([^\]]+)\]\s*$/);
28
+ if (metadataMatch) {
29
+ visibleTitle = rawTitle.slice(0, metadataMatch.index).trim();
30
+ for (const part of metadataMatch[1].split(";")) {
31
+ const [rawKey, ...rawValueParts] = part.split("=");
32
+ const key = rawKey?.trim();
33
+ const value = rawValueParts.join("=").trim();
34
+ if (!key || !value)
35
+ continue;
36
+ if (key === "state" && (value === "ready" || value === "busy")) {
37
+ metadata.activityState = value;
38
+ }
39
+ if (key === "session") {
40
+ metadata.agentSessionId = value;
41
+ }
42
+ }
43
+ }
44
+ const normalized = agentType === "pie"
45
+ ? visibleTitle.replace(/^Pie\s*[·-]\s*/i, "")
46
+ : visibleTitle;
47
+ const title = normalized.trim().replace(/\s+/g, " ").slice(0, 60);
48
+ if (title) {
49
+ metadata.title = title;
50
+ }
51
+ return Object.keys(metadata).length > 0 ? metadata : null;
52
+ }
@@ -0,0 +1 @@
1
+ export * from "@cydm/magic-shell-protocol";
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export * from "@cydm/magic-shell-protocol";
@@ -0,0 +1,15 @@
1
+ import type { ServerMessage } from "./types.js";
2
+ import type { WorkerRegistry } from "./worker-registry.js";
3
+ import type { SessionManager } from "./session-manager.js";
4
+ import type { SpawnWorkerOptions } from "./node.js";
5
+ export interface WorkerControlDeps {
6
+ workerRegistry: WorkerRegistry;
7
+ sessionManager: SessionManager;
8
+ clearSessionTitle(sessionId: string): void;
9
+ broadcastWorkerList(): void;
10
+ spawnWorker(options: SpawnWorkerOptions): Promise<void>;
11
+ }
12
+ export declare function attachWorkerSession(sessionId: string, deps: Pick<WorkerControlDeps, "workerRegistry" | "sessionManager">): ServerMessage[];
13
+ export declare function stopWorkerSession(sessionId: string, deps: WorkerControlDeps): Promise<void>;
14
+ export declare function restartWorkerSession(sessionId: string, deps: WorkerControlDeps): Promise<string | null>;
15
+ export declare function inspectWorkerSession(sessionId: string, deps: Pick<WorkerControlDeps, "workerRegistry" | "sessionManager">): ServerMessage | null;
@@ -0,0 +1,89 @@
1
+ export function attachWorkerSession(sessionId, deps) {
2
+ const worker = deps.workerRegistry.getWorkerBySessionId(sessionId);
3
+ if (!worker) {
4
+ return [];
5
+ }
6
+ deps.workerRegistry.recordEvent(worker.agentId, {
7
+ type: "attached",
8
+ timestamp: Date.now(),
9
+ message: "attached from browser",
10
+ });
11
+ const messages = [
12
+ {
13
+ type: "session",
14
+ sessionId: worker.sessionId,
15
+ agentType: worker.agentType,
16
+ capabilities: worker.capabilities,
17
+ },
18
+ ];
19
+ const bufferedOutput = deps.sessionManager.getBufferedOutput(sessionId);
20
+ if (bufferedOutput) {
21
+ messages.push({
22
+ type: "output",
23
+ sessionId: worker.sessionId,
24
+ data: bufferedOutput,
25
+ });
26
+ }
27
+ return messages;
28
+ }
29
+ export async function stopWorkerSession(sessionId, deps) {
30
+ const worker = deps.workerRegistry.getWorkerBySessionId(sessionId);
31
+ if (!worker) {
32
+ return;
33
+ }
34
+ deps.workerRegistry.updateWorkerStatus(worker.agentId, "stopping");
35
+ deps.clearSessionTitle(sessionId);
36
+ await deps.sessionManager.destroySession(sessionId);
37
+ deps.broadcastWorkerList();
38
+ }
39
+ export async function restartWorkerSession(sessionId, deps) {
40
+ const worker = deps.workerRegistry.getWorkerBySessionId(sessionId);
41
+ if (!worker) {
42
+ return null;
43
+ }
44
+ try {
45
+ deps.workerRegistry.recordEvent(worker.agentId, {
46
+ type: "restarted",
47
+ timestamp: Date.now(),
48
+ message: "restart requested",
49
+ });
50
+ deps.workerRegistry.updateWorkerStatus(worker.agentId, "stopping");
51
+ deps.clearSessionTitle(sessionId);
52
+ await deps.sessionManager.destroySession(sessionId);
53
+ await deps.spawnWorker({
54
+ sessionId: worker.sessionId,
55
+ pluginName: worker.agentType,
56
+ cwd: worker.cwd,
57
+ parentAgentId: worker.parentAgentId,
58
+ });
59
+ return null;
60
+ }
61
+ catch (err) {
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ deps.workerRegistry.recordEvent(worker.agentId, {
64
+ type: "error",
65
+ timestamp: Date.now(),
66
+ message,
67
+ });
68
+ deps.workerRegistry.updateWorkerStatus(worker.agentId, "failed", {
69
+ lastError: message,
70
+ });
71
+ deps.broadcastWorkerList();
72
+ return message;
73
+ }
74
+ }
75
+ export function inspectWorkerSession(sessionId, deps) {
76
+ const worker = deps.workerRegistry.getWorkerBySessionId(sessionId);
77
+ if (!worker) {
78
+ return null;
79
+ }
80
+ deps.workerRegistry.recordEvent(worker.agentId, {
81
+ type: "inspected",
82
+ timestamp: Date.now(),
83
+ message: "detail requested",
84
+ });
85
+ return {
86
+ type: "worker_detail",
87
+ worker: deps.workerRegistry.getWorkerDetail(worker.agentId, deps.sessionManager.getBufferedOutput(worker.sessionId)),
88
+ };
89
+ }
@@ -0,0 +1,25 @@
1
+ import type { AgentRecord } from "./types.js";
2
+ export type WorkerMilestoneReason = "ready" | "attention" | "failed" | "stopped";
3
+ export interface WorkerMilestonePromptContext {
4
+ lastTaskSummary?: string;
5
+ lastUserMessage?: string;
6
+ outputSummary: string;
7
+ }
8
+ export type WorkerNarrationDecision = {
9
+ kind: "none";
10
+ } | {
11
+ kind: "node_update";
12
+ text: string;
13
+ actionLabel: string;
14
+ actionSessionId: string;
15
+ } | {
16
+ kind: "milestone";
17
+ reason: WorkerMilestoneReason;
18
+ fallback: string;
19
+ actionLabel: string;
20
+ actionSessionId: string;
21
+ stateKey: string;
22
+ };
23
+ export declare function buildWorkerMilestonePrompt(worker: AgentRecord, reason: WorkerMilestoneReason, context: WorkerMilestonePromptContext): string;
24
+ export declare function describeCurrentWorkerForNode(worker: AgentRecord, recentOutput: string): string;
25
+ export declare function decideWorkerNarration(previous: AgentRecord | undefined, next: AgentRecord): WorkerNarrationDecision;
@@ -0,0 +1,90 @@
1
+ export function buildWorkerMilestonePrompt(worker, reason, context) {
2
+ return [
3
+ "You are AgentNode, a personal agent coordinator.",
4
+ "Summarize the worker's latest milestone for the user.",
5
+ "Write 1-2 short sentences only.",
6
+ "Match the user's language when it is obvious from the recent message.",
7
+ "Do not mention ANSI, terminals, logs, JSON, or raw control output.",
8
+ "Speak like a calm secretary giving a progress update.",
9
+ "",
10
+ `Reason: ${reason}`,
11
+ `Worker title: ${worker.taskSummary || worker.agentSessionId || `S:${worker.sessionId.slice(-6)}`}`,
12
+ `Worker state: ${[worker.status, worker.phase, worker.activityState].filter(Boolean).join(" · ")}`,
13
+ `Task summary: ${worker.taskSummary || context.lastTaskSummary || ""}`,
14
+ `Attention reason: ${worker.attentionReason || worker.interventionReason || worker.recommendedActionReason || ""}`,
15
+ `Recent user message: ${context.lastUserMessage || ""}`,
16
+ `Recent meaningful output:\n${context.outputSummary}`,
17
+ "",
18
+ "Return only the short update text.",
19
+ ].join("\n");
20
+ }
21
+ export function describeCurrentWorkerForNode(worker, recentOutput) {
22
+ const label = worker.taskSummary || worker.agentSessionId || `S:${worker.sessionId.slice(-6)}`;
23
+ const parts = [`Current task worker: ${label}.`];
24
+ if (worker.phase || worker.activityState) {
25
+ parts.push(`State: ${[worker.status, worker.phase, worker.activityState].filter(Boolean).join(" · ")}.`);
26
+ }
27
+ if (worker.attentionReason) {
28
+ parts.push(`Attention: ${worker.attentionReason}.`);
29
+ }
30
+ else if (worker.recommendedActionReason) {
31
+ parts.push(`Hint: ${worker.recommendedActionReason}.`);
32
+ }
33
+ if (recentOutput) {
34
+ parts.push(`Recent output:\n${recentOutput}`);
35
+ }
36
+ return parts.join("\n");
37
+ }
38
+ export function decideWorkerNarration(previous, next) {
39
+ const label = next.taskSummary || next.agentSessionId || `S:${next.sessionId.slice(-6)}`;
40
+ const stateKey = `${next.status}:${next.phase || ""}:${next.activityState || ""}`;
41
+ if (next.status === "failed") {
42
+ return {
43
+ kind: "milestone",
44
+ reason: "failed",
45
+ fallback: `Worker ${label} failed${next.lastError ? `: ${next.lastError}` : "."}`,
46
+ actionLabel: "ATTACH WORKER",
47
+ actionSessionId: next.sessionId,
48
+ stateKey,
49
+ };
50
+ }
51
+ if (next.status === "stopped") {
52
+ return {
53
+ kind: "milestone",
54
+ reason: "stopped",
55
+ fallback: `Worker ${label} stopped.`,
56
+ actionLabel: "VIEW LAST OUTPUT",
57
+ actionSessionId: next.sessionId,
58
+ stateKey,
59
+ };
60
+ }
61
+ if (previous?.activityState !== "busy" && next.activityState === "busy") {
62
+ return {
63
+ kind: "node_update",
64
+ text: `Worker ${label} is actively working now.`,
65
+ actionLabel: "ATTACH WORKER",
66
+ actionSessionId: next.sessionId,
67
+ };
68
+ }
69
+ if (previous?.activityState === "busy" && next.activityState === "ready") {
70
+ return {
71
+ kind: "milestone",
72
+ reason: "ready",
73
+ fallback: `Worker ${label} looks ready for input or review.`,
74
+ actionLabel: "ATTACH WORKER",
75
+ actionSessionId: next.sessionId,
76
+ stateKey,
77
+ };
78
+ }
79
+ if (previous?.phase !== "attention" && next.phase === "attention") {
80
+ return {
81
+ kind: "milestone",
82
+ reason: "attention",
83
+ fallback: `Worker ${label} may need intervention${next.attentionReason ? `: ${next.attentionReason}` : "."}`,
84
+ actionLabel: "ATTACH WORKER",
85
+ actionSessionId: next.sessionId,
86
+ stateKey,
87
+ };
88
+ }
89
+ return { kind: "none" };
90
+ }
@@ -0,0 +1,6 @@
1
+ export declare function stripAnsi(value: string): string;
2
+ export declare function normalizePrimarySummaryText(value: string): string;
3
+ export declare function isMeaningfulWorkerOutputLine(line: string): boolean;
4
+ export declare function summarizeRecentWorkerOutput(bufferedOutput: string): string;
5
+ export declare function getLastMeaningfulWorkerMessage(bufferedOutput: string): string;
6
+ export declare function getFreshWorkerOutputSummary(agentId: string, bufferedOutput: string, seenSummaries: Map<string, string>): string;
@@ -0,0 +1,72 @@
1
+ export function stripAnsi(value) {
2
+ return String(value || "")
3
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
4
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
5
+ .replace(/\x1b[@-_]/g, "");
6
+ }
7
+ export function normalizePrimarySummaryText(value) {
8
+ return String(value || "")
9
+ .replace(/^```(?:json|text)?/i, "")
10
+ .replace(/```$/i, "")
11
+ .trim();
12
+ }
13
+ export function isMeaningfulWorkerOutputLine(line) {
14
+ const normalized = String(line || "").trim();
15
+ if (!normalized || normalized.length < 4)
16
+ return false;
17
+ if (/^(thinking|ready|booting|working|waiting)$/i.test(normalized))
18
+ return false;
19
+ if (/extension loaded/i.test(normalized))
20
+ return false;
21
+ if (/^\d+(\.\d+)?%\/\d+/i.test(normalized))
22
+ return false;
23
+ if (/^(local-|company-|kimi-|gpt-|claude-|qwen-)/i.test(normalized))
24
+ return false;
25
+ if (/^\/Users\/|^~\//.test(normalized))
26
+ return false;
27
+ if (/^[\u2500-\u257f\s]+$/u.test(normalized))
28
+ return false;
29
+ if (/^[\[\](){}<>\-_=.:;|\\/+*#\s]+$/.test(normalized))
30
+ return false;
31
+ return true;
32
+ }
33
+ export function summarizeRecentWorkerOutput(bufferedOutput) {
34
+ const cleaned = stripAnsi(bufferedOutput)
35
+ .replace(/\r/g, "\n")
36
+ .replace(/\n{3,}/g, "\n\n")
37
+ .trim();
38
+ if (!cleaned)
39
+ return "";
40
+ const lines = cleaned
41
+ .split("\n")
42
+ .map((line) => line.trim())
43
+ .filter((line) => isMeaningfulWorkerOutputLine(line));
44
+ if (!lines.length)
45
+ return "";
46
+ const uniqueTail = Array.from(new Set(lines)).slice(-4);
47
+ const tail = uniqueTail.join("\n");
48
+ return tail.slice(-280);
49
+ }
50
+ export function getLastMeaningfulWorkerMessage(bufferedOutput) {
51
+ const cleaned = stripAnsi(bufferedOutput)
52
+ .replace(/\r/g, "\n")
53
+ .replace(/\n{3,}/g, "\n\n")
54
+ .trim();
55
+ if (!cleaned)
56
+ return "";
57
+ const lines = cleaned
58
+ .split("\n")
59
+ .map((line) => line.trim())
60
+ .filter((line) => isMeaningfulWorkerOutputLine(line));
61
+ return lines.length ? lines[lines.length - 1].slice(-400) : "";
62
+ }
63
+ export function getFreshWorkerOutputSummary(agentId, bufferedOutput, seenSummaries) {
64
+ const summary = summarizeRecentWorkerOutput(bufferedOutput);
65
+ if (!summary)
66
+ return "";
67
+ if (seenSummaries.get(agentId) === summary) {
68
+ return "";
69
+ }
70
+ seenSummaries.set(agentId, summary);
71
+ return summary;
72
+ }
@@ -0,0 +1,45 @@
1
+ import type { AgentRecord, AgentStatus, PluginConfig, RuntimeFocusItem, RuntimeSummary, WorkerDetail, WorkerEvent } from "./types.js";
2
+ export interface CreateWorkerRecordOptions {
3
+ agentId: string;
4
+ sessionId: string;
5
+ nodeId: string;
6
+ plugin: PluginConfig;
7
+ cwd?: string;
8
+ displayName?: string;
9
+ taskSummary?: string;
10
+ parentAgentId?: string;
11
+ }
12
+ export declare class WorkerRegistry {
13
+ private workers;
14
+ private sessionToAgent;
15
+ private recentEvents;
16
+ private snapshots;
17
+ private readonly maxRecentEvents;
18
+ private readonly maxSnapshots;
19
+ private allocateDisplayName;
20
+ createWorkerRecord(options: CreateWorkerRecordOptions): AgentRecord;
21
+ rebindWorkerSession(existingAgentId: string, options: CreateWorkerRecordOptions): AgentRecord | undefined;
22
+ getWorkerByAgentId(agentId: string): AgentRecord | undefined;
23
+ getWorkerBySessionId(sessionId: string): AgentRecord | undefined;
24
+ listWorkers(): AgentRecord[];
25
+ updateWorker(agentId: string, updates: Partial<AgentRecord>): AgentRecord | undefined;
26
+ getWorkerDetail(agentId: string, lastBufferedOutput?: string): WorkerDetail | undefined;
27
+ getRuntimeSummary(): RuntimeSummary;
28
+ getRuntimeFocus(limit?: number): RuntimeFocusItem[];
29
+ updateWorkerStatus(agentId: string, status: AgentStatus, details?: Partial<AgentRecord>): AgentRecord | undefined;
30
+ removeWorker(agentId: string): boolean;
31
+ recordOutput(sessionId: string): AgentRecord | undefined;
32
+ recordOutputActivity(agentId: string, charCount: number): AgentRecord | undefined;
33
+ recordEvent(agentId: string, event: WorkerEvent): void;
34
+ private appendEvent;
35
+ private summarizeEvent;
36
+ private normalizeEvent;
37
+ private inferEventLevel;
38
+ private captureSnapshot;
39
+ private summarizeSnapshot;
40
+ private workerNeedsAttention;
41
+ private deriveWorkerState;
42
+ private refreshAllWorkerStates;
43
+ private refreshWorkerState;
44
+ private hasDerivedStateChanged;
45
+ }