@cydm/magic-shell-agent-node 0.1.0 → 0.1.2
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/local-direct-server.js +3 -2
- package/dist/workbench/index.html +3827 -0
- package/dist/workbench/runtime-control.js +180 -0
- package/dist/workbench/runtime-messages.js +80 -0
- package/dist/workbench/runtime-node-ui.js +102 -0
- package/dist/workbench/runtime-render.js +180 -0
- package/dist/workbench/runtime-terminal.js +250 -0
- package/dist/workbench/runtime-transport.js +105 -0
- package/dist/workbench/runtime-worker-ui.js +94 -0
- package/dist/workbench/test.html +325 -0
- package/package.json +10 -3
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
function hasUsablePrimaryAgent(primaryAgent) {
|
|
3
|
+
return !!primaryAgent && primaryAgent.status !== "disabled" && primaryAgent.status !== "missing";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function applyPrimaryReply(payload, ctx) {
|
|
7
|
+
ctx.pendingNodeReply = false;
|
|
8
|
+
ctx.pushNodeTranscript("assistant", payload?.text || "Primary agent did not return a message.", {
|
|
9
|
+
actionLabel: payload?.actionLabel,
|
|
10
|
+
actionType: payload?.actionType,
|
|
11
|
+
actionTaskSummary: payload?.actionTaskSummary,
|
|
12
|
+
actionSessionId: payload?.actionSessionId,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function applyRuntimeSnapshot(snapshot, ctx) {
|
|
17
|
+
const previousWorkers = ctx.workers || [];
|
|
18
|
+
ctx.workers = snapshot.workers || [];
|
|
19
|
+
ctx.runtimeSummary = snapshot.runtime || null;
|
|
20
|
+
ctx.runtimeFocus = snapshot.focus || [];
|
|
21
|
+
ctx.primaryAgent = snapshot.primary || null;
|
|
22
|
+
ctx.syncNodeTranscriptFromServer(snapshot.nodeHistory);
|
|
23
|
+
|
|
24
|
+
const liveSessionIds = new Set(ctx.workers.map((worker) => worker.sessionId));
|
|
25
|
+
const currentWorker = ctx.sessionId
|
|
26
|
+
? ctx.workers.find((worker) => worker.sessionId === ctx.sessionId)
|
|
27
|
+
: null;
|
|
28
|
+
|
|
29
|
+
for (const previousWorker of previousWorkers) {
|
|
30
|
+
if (!liveSessionIds.has(previousWorker.sessionId)) {
|
|
31
|
+
ctx.destroyTerminalView(previousWorker.sessionId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (ctx.pendingSpawnSessionId && ctx.workers.some((worker) => worker.sessionId === ctx.pendingSpawnSessionId)) {
|
|
36
|
+
if (ctx.pendingSpawnAutoAttach) {
|
|
37
|
+
// Once the worker is visible in runtime state, perform a real attach so
|
|
38
|
+
// the browser connection is routed to the new session via the relay.
|
|
39
|
+
ctx.attachWorker(ctx.pendingSpawnSessionId, true);
|
|
40
|
+
} else {
|
|
41
|
+
ctx.pushNodeTranscript(
|
|
42
|
+
"assistant",
|
|
43
|
+
"A worker is ready in the sidebar.\nSession · S:" + ctx.pendingSpawnSessionId.slice(-6),
|
|
44
|
+
{
|
|
45
|
+
actionSessionId: ctx.pendingSpawnSessionId,
|
|
46
|
+
actionLabel: "ATTACH",
|
|
47
|
+
actionType: "attach",
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
ctx.pendingSpawnSessionId = null;
|
|
52
|
+
ctx.pendingSpawnAutoAttach = true;
|
|
53
|
+
ctx.resetSpawnButton();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (ctx.pendingStopSessionId) {
|
|
57
|
+
const pendingStopWorker = ctx.workers.find((worker) => worker.sessionId === ctx.pendingStopSessionId);
|
|
58
|
+
if (!pendingStopWorker || pendingStopWorker.status === "stopped" || pendingStopWorker.status === "failed") {
|
|
59
|
+
ctx.pendingStopSessionId = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (ctx.pendingRestartSessionId) {
|
|
64
|
+
const pendingRestartWorker = ctx.workers.find((worker) => worker.sessionId === ctx.pendingRestartSessionId);
|
|
65
|
+
if (pendingRestartWorker && pendingRestartWorker.status === "running") {
|
|
66
|
+
ctx.pendingRestartSessionId = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (ctx.sessionId && !ctx.workers.some((worker) => worker.sessionId === ctx.sessionId)) {
|
|
71
|
+
const closedSessionId = ctx.sessionId;
|
|
72
|
+
ctx.writeSystemNotice("WORKER CLOSED: " + closedSessionId.slice(-8));
|
|
73
|
+
ctx.clearPreferredSession();
|
|
74
|
+
ctx.returnToPrimaryChat({ clearSession: true });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
currentWorker
|
|
79
|
+
&& ctx.pendingRestartSessionId !== currentWorker.sessionId
|
|
80
|
+
&& (currentWorker.status === "stopped" || currentWorker.status === "failed")
|
|
81
|
+
) {
|
|
82
|
+
ctx.writeSystemNotice("WORKER INACTIVE: " + currentWorker.sessionId.slice(-8));
|
|
83
|
+
if (ctx.preferredSessionId === currentWorker.sessionId) {
|
|
84
|
+
ctx.clearPreferredSession();
|
|
85
|
+
}
|
|
86
|
+
ctx.returnToPrimaryChat({ clearSession: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ctx.renderWorkers();
|
|
90
|
+
|
|
91
|
+
if (ctx.sessionId && ctx.workerDetailOpen && ctx.workerDetail && ctx.workerDetail.worker.sessionId === ctx.sessionId) {
|
|
92
|
+
ctx.refreshCurrentWorkerDetail();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!ctx.sessionId && ctx.workers.length) {
|
|
96
|
+
const preferredWorker = ctx.preferredSessionId
|
|
97
|
+
? ctx.workers.find((worker) => worker.sessionId === ctx.preferredSessionId)
|
|
98
|
+
: null;
|
|
99
|
+
const firstAttachable = ctx.workers.find((worker) => worker.status !== "stopped" && worker.status !== "failed");
|
|
100
|
+
const targetWorker = preferredWorker || firstAttachable || ctx.workers[0];
|
|
101
|
+
if (!ctx.isNodeViewSelected() && targetWorker.status !== "stopped" && targetWorker.status !== "failed") {
|
|
102
|
+
ctx.attachWorker(targetWorker.sessionId, true);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handleControlMessage(msg, ctx) {
|
|
108
|
+
if (msg.controlKind === "result" && msg.controlName === "get_primary_agent" && msg.payload && msg.payload.primary) {
|
|
109
|
+
ctx.primaryAgent = msg.payload.primary || null;
|
|
110
|
+
ctx.renderNodeHome();
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (msg.controlKind === "result" && msg.controlName === "send_primary_message") {
|
|
115
|
+
if (msg.ok === false) {
|
|
116
|
+
ctx.pendingNodeReply = false;
|
|
117
|
+
ctx.pushNodeTranscript(
|
|
118
|
+
"assistant",
|
|
119
|
+
msg.error || msg.payload?.error || "Primary agent request failed before it produced a reply.",
|
|
120
|
+
);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (msg.payload) {
|
|
124
|
+
applyPrimaryReply(msg.payload, ctx);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
ctx.pendingNodeReply = false;
|
|
128
|
+
ctx.pushNodeTranscript("assistant", "Primary agent did not return a usable reply.");
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (msg.controlKind === "result" && (msg.controlName === "get_runtime_summary" || msg.controlName === "list_workers") && msg.payload) {
|
|
133
|
+
applyRuntimeSnapshot(msg.payload, ctx);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (msg.controlKind === "result" && msg.controlName === "get_worker_detail" && msg.payload && msg.payload.worker) {
|
|
138
|
+
ctx.workerDetail = msg.payload.worker || null;
|
|
139
|
+
ctx.renderWorkerDetailPanel();
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (msg.controlKind === "result" && msg.controlName === "browse_directories" && msg.payload) {
|
|
144
|
+
ctx.dirBrowserPath = msg.payload.path || null;
|
|
145
|
+
ctx.dirBrowserParentPath = msg.payload.parentPath || null;
|
|
146
|
+
ctx.dirBrowserRepoRoot = msg.payload.repoRoot || null;
|
|
147
|
+
ctx.dirBrowserEntries = msg.payload.entries || [];
|
|
148
|
+
ctx.renderDirBrowser();
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (msg.controlKind === "event" && msg.controlName === "runtime.snapshot" && msg.payload) {
|
|
153
|
+
applyRuntimeSnapshot(msg.payload, ctx);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (msg.controlKind === "event" && msg.controlName === "worker.detail" && msg.payload && msg.payload.worker) {
|
|
158
|
+
ctx.workerDetail = msg.payload.worker || null;
|
|
159
|
+
ctx.renderWorkerDetailPanel();
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (msg.controlKind === "event" && msg.controlName === "directory.list" && msg.payload) {
|
|
164
|
+
ctx.dirBrowserPath = msg.payload.path || null;
|
|
165
|
+
ctx.dirBrowserParentPath = msg.payload.parentPath || null;
|
|
166
|
+
ctx.dirBrowserRepoRoot = msg.payload.repoRoot || null;
|
|
167
|
+
ctx.dirBrowserEntries = msg.payload.entries || [];
|
|
168
|
+
ctx.renderDirBrowser();
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
global.MagicShellRuntimeControl = {
|
|
176
|
+
hasUsablePrimaryAgent,
|
|
177
|
+
applyRuntimeSnapshot,
|
|
178
|
+
handleControlMessage,
|
|
179
|
+
};
|
|
180
|
+
})(window);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
function handleWorkbenchMessage(msg, ctx) {
|
|
3
|
+
switch (msg.type) {
|
|
4
|
+
case "auth_success":
|
|
5
|
+
ctx.writeTerminalLine("__system__", "\r\n[ SYSTEM ONLINE ]");
|
|
6
|
+
ctx.sessionId = null;
|
|
7
|
+
ctx.setSessionDisplay("----");
|
|
8
|
+
ctx.syncWorkerPluginPreference?.();
|
|
9
|
+
ctx.requestWorkerList();
|
|
10
|
+
return true;
|
|
11
|
+
|
|
12
|
+
case "output":
|
|
13
|
+
if (msg.sessionId) {
|
|
14
|
+
ctx.writeTerminal(msg.sessionId, msg.data);
|
|
15
|
+
} else {
|
|
16
|
+
ctx.writeTerminal("__system__", msg.data);
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
|
|
20
|
+
case "error":
|
|
21
|
+
ctx.writeTerminalLine(ctx.activeTerminalId || "__system__", `\r\n[ ERROR: ${msg.error} ]\r\n`);
|
|
22
|
+
return true;
|
|
23
|
+
|
|
24
|
+
case "session":
|
|
25
|
+
ctx.requestWorkerList();
|
|
26
|
+
return true;
|
|
27
|
+
|
|
28
|
+
case "dir_list":
|
|
29
|
+
ctx.dirBrowserPath = msg.path || null;
|
|
30
|
+
ctx.dirBrowserParentPath = msg.parentPath || null;
|
|
31
|
+
ctx.dirBrowserRepoRoot = msg.repoRoot || null;
|
|
32
|
+
ctx.dirBrowserEntries = msg.entries || [];
|
|
33
|
+
ctx.renderDirBrowser();
|
|
34
|
+
return true;
|
|
35
|
+
|
|
36
|
+
case "worker_list":
|
|
37
|
+
global.MagicShellRuntimeControl.applyRuntimeSnapshot({
|
|
38
|
+
workers: msg.workers || [],
|
|
39
|
+
runtime: msg.runtime || null,
|
|
40
|
+
focus: msg.focus || [],
|
|
41
|
+
primary: msg.primary || null,
|
|
42
|
+
nodeHistory: msg.nodeHistory || [],
|
|
43
|
+
}, ctx.getRuntimeControlContext());
|
|
44
|
+
return true;
|
|
45
|
+
|
|
46
|
+
case "worker_detail":
|
|
47
|
+
ctx.workerDetail = msg.worker || null;
|
|
48
|
+
ctx.renderWorkerDetailPanel();
|
|
49
|
+
return true;
|
|
50
|
+
|
|
51
|
+
case "node_reply":
|
|
52
|
+
ctx.pendingNodeReply = false;
|
|
53
|
+
ctx.pushNodeTranscript("assistant", msg.text || "Primary agent did not return a message.", {
|
|
54
|
+
actionLabel: msg.actionLabel,
|
|
55
|
+
actionType: msg.actionType,
|
|
56
|
+
actionTaskSummary: msg.actionTaskSummary,
|
|
57
|
+
actionSessionId: msg.actionSessionId,
|
|
58
|
+
});
|
|
59
|
+
return true;
|
|
60
|
+
|
|
61
|
+
case "attached":
|
|
62
|
+
if (msg.sessionId) {
|
|
63
|
+
ctx.sessionId = msg.sessionId;
|
|
64
|
+
ctx.setSessionDisplay(msg.sessionId.slice(-8));
|
|
65
|
+
ctx.renderWorkers();
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
|
|
69
|
+
case "control":
|
|
70
|
+
return global.MagicShellRuntimeControl.handleControlMessage(msg, ctx.getRuntimeControlContext());
|
|
71
|
+
|
|
72
|
+
default:
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
global.MagicShellRuntimeMessages = {
|
|
78
|
+
handleWorkbenchMessage,
|
|
79
|
+
};
|
|
80
|
+
})(window);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
function renderNodeDetailPanel(ctx) {
|
|
3
|
+
const panel = ctx.getPanelNode();
|
|
4
|
+
const toggle = ctx.getToggleNode();
|
|
5
|
+
panel.classList.toggle("active", ctx.nodeDetailOpen && ctx.isNodeViewSelected());
|
|
6
|
+
toggle.textContent = ctx.nodeDetailOpen ? "HIDE" : "OVERVIEW";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getPrimaryTitle(primaryAgent) {
|
|
10
|
+
return "Agent";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function renderNodeHome(ctx) {
|
|
14
|
+
const thread = ctx.getThreadNode();
|
|
15
|
+
const coordination = ctx.getCoordinationNode();
|
|
16
|
+
const coordinationText = ctx.getCoordinationTextNode();
|
|
17
|
+
const input = ctx.getInputNode();
|
|
18
|
+
const workers = ctx.workers;
|
|
19
|
+
const summary = ctx.runtimeSummary || {
|
|
20
|
+
liveWorkers: workers.filter((worker) => worker.status !== "stopped" && worker.status !== "failed").length,
|
|
21
|
+
waitingWorkers: workers.filter((worker) => worker.activityState === "ready" && worker.status !== "stopped" && worker.status !== "failed").length,
|
|
22
|
+
busyWorkers: workers.filter((worker) => worker.activityState === "busy" && worker.status !== "stopped" && worker.status !== "failed").length,
|
|
23
|
+
};
|
|
24
|
+
const primaryTitle = getPrimaryTitle(ctx.primaryAgent);
|
|
25
|
+
const primaryLabel = ctx.primaryAgent
|
|
26
|
+
? `${ctx.primaryAgent.pluginName} · ${ctx.primaryAgent.status}`
|
|
27
|
+
: "no primary";
|
|
28
|
+
ctx.setNodeNavTitle(primaryTitle);
|
|
29
|
+
ctx.setNodeHomeTitle(primaryTitle);
|
|
30
|
+
ctx.setNodeNavMeta(`${primaryLabel} · ${summary.liveWorkers || 0} live · ${summary.waitingWorkers || 0} waiting · ${summary.busyWorkers || 0} busy`);
|
|
31
|
+
ctx.setNodeNavBadge(summary.busyWorkers ? `${summary.busyWorkers} busy` : "idle");
|
|
32
|
+
ctx.setNodeHomeBadge(summary.liveWorkers ? `${summary.liveWorkers} live` : "idle");
|
|
33
|
+
const primaryReady = global.MagicShellRuntimeControl.hasUsablePrimaryAgent(ctx.primaryAgent);
|
|
34
|
+
input.disabled = ctx.pendingNodeReply || !ctx.isConnected() || !primaryReady;
|
|
35
|
+
input.placeholder = ctx.pendingNodeReply
|
|
36
|
+
? "Primary agent is thinking..."
|
|
37
|
+
: primaryReady
|
|
38
|
+
? "Ask the primary agent to coordinate work."
|
|
39
|
+
: "Primary agent unavailable. Use the worker controls directly.";
|
|
40
|
+
|
|
41
|
+
if (!primaryReady) {
|
|
42
|
+
coordination.className = "node-coordination-state";
|
|
43
|
+
coordinationText.textContent = "Primary agent unavailable. Use the runtime controls directly.";
|
|
44
|
+
} else if (ctx.pendingNodeReply) {
|
|
45
|
+
coordination.className = `node-coordination-state active ${summary.busyWorkers > 0 ? "waiting" : ""}`.trim();
|
|
46
|
+
coordinationText.textContent = summary.busyWorkers > 0
|
|
47
|
+
? "Primary is delegating work and waiting for a worker result."
|
|
48
|
+
: "Primary is reviewing the request.";
|
|
49
|
+
} else if (summary.busyWorkers > 0) {
|
|
50
|
+
coordination.className = "node-coordination-state active waiting";
|
|
51
|
+
coordinationText.textContent = `Runtime has ${summary.busyWorkers} busy worker${summary.busyWorkers > 1 ? "s" : ""}. Open the worker view to inspect live execution.`;
|
|
52
|
+
} else if (summary.waitingWorkers > 0) {
|
|
53
|
+
coordination.className = "node-coordination-state active";
|
|
54
|
+
coordinationText.textContent = `Runtime has ${summary.waitingWorkers} worker${summary.waitingWorkers > 1 ? "s" : ""} ready for follow-up.`;
|
|
55
|
+
} else {
|
|
56
|
+
coordination.className = "node-coordination-state";
|
|
57
|
+
coordinationText.textContent = "Primary coordination idle.";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const merged = [...ctx.nodeTranscript].slice(-18);
|
|
61
|
+
if (!primaryReady) {
|
|
62
|
+
merged.push({
|
|
63
|
+
id: "node-primary-unavailable",
|
|
64
|
+
role: "assistant",
|
|
65
|
+
text: "Primary agent unavailable. Magic Shell is still usable as a runtime workbench: spawn workers, inspect runtime state, browse directories, and attach directly.",
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!merged.length) {
|
|
71
|
+
thread.innerHTML = '<div class="node-chat-empty">Primary agent is ready.</div>';
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const previousScrollTop = thread.scrollTop;
|
|
76
|
+
const distanceFromBottom = thread.scrollHeight - thread.scrollTop - thread.clientHeight;
|
|
77
|
+
const shouldStickToBottom = distanceFromBottom < 24;
|
|
78
|
+
|
|
79
|
+
thread.innerHTML = merged.map((entry) => `
|
|
80
|
+
<div class="node-turn ${entry.role} ${entry.pending ? "pending" : ""}">
|
|
81
|
+
<div>${ctx.escapeHtml(entry.text)}</div>
|
|
82
|
+
${(entry.actionSessionId || entry.actionType === "spawn") ? `
|
|
83
|
+
<div class="node-turn-action-row">
|
|
84
|
+
<button class="node-turn-action" onclick="runNodeAction('${entry.id}')">${ctx.escapeHtml(entry.actionLabel || "ATTACH")}</button>
|
|
85
|
+
</div>
|
|
86
|
+
` : ""}
|
|
87
|
+
${entry.pending ? '<div class="node-turn-status">Working...</div>' : ""}
|
|
88
|
+
<div class="node-turn-meta">${ctx.escapeHtml(ctx.formatRelativeTime(entry.timestamp))}</div>
|
|
89
|
+
</div>
|
|
90
|
+
`).join("");
|
|
91
|
+
if (shouldStickToBottom) {
|
|
92
|
+
thread.scrollTop = thread.scrollHeight;
|
|
93
|
+
} else {
|
|
94
|
+
thread.scrollTop = previousScrollTop;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
global.MagicShellRuntimeNodeUI = {
|
|
99
|
+
renderNodeDetailPanel,
|
|
100
|
+
renderNodeHome,
|
|
101
|
+
};
|
|
102
|
+
})(window);
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
function renderRuntimeSummary(ctx) {
|
|
3
|
+
const node = ctx.getRuntimeSummaryNode();
|
|
4
|
+
const workers = ctx.workers;
|
|
5
|
+
const summary = ctx.runtimeSummary || {
|
|
6
|
+
liveWorkers: workers.filter((worker) => worker.status !== "stopped" && worker.status !== "failed").length,
|
|
7
|
+
closedWorkers: workers.filter((worker) => worker.status === "stopped" || worker.status === "failed").length,
|
|
8
|
+
busyWorkers: workers.filter((worker) => worker.activityState === "busy" && worker.status !== "stopped" && worker.status !== "failed").length,
|
|
9
|
+
waitingWorkers: workers.filter((worker) => worker.activityState === "ready" && worker.status !== "stopped" && worker.status !== "failed").length,
|
|
10
|
+
attentionWorkers: workers.filter((worker) => worker.phase === "attention" || ctx.workerNeedsAttention(worker)).length,
|
|
11
|
+
quietWorkers: workers.filter((worker) => worker.phase === "quiet").length,
|
|
12
|
+
failedWorkers: workers.filter((worker) => worker.status === "failed").length,
|
|
13
|
+
watchWorkers: workers.filter((worker) => worker.interventionLevel === "watch").length,
|
|
14
|
+
suggestedWorkers: workers.filter((worker) => worker.interventionLevel === "suggested").length,
|
|
15
|
+
requiredWorkers: workers.filter((worker) => worker.interventionLevel === "required").length,
|
|
16
|
+
observeWorkers: workers.filter((worker) => worker.recommendedAction === "observe").length,
|
|
17
|
+
attachWorkers: workers.filter((worker) => worker.recommendedAction === "attach").length,
|
|
18
|
+
restartWorkers: workers.filter((worker) => worker.recommendedAction === "restart").length,
|
|
19
|
+
closeWorkers: workers.filter((worker) => worker.recommendedAction === "close").length,
|
|
20
|
+
totalRestarts: workers.reduce((sum, worker) => sum + (worker.restartCount || 0), 0),
|
|
21
|
+
totalAttaches: workers.reduce((sum, worker) => sum + (worker.attachCount || 0), 0),
|
|
22
|
+
totalOutputChars: workers.reduce((sum, worker) => sum + (worker.outputCharCount || 0), 0),
|
|
23
|
+
};
|
|
24
|
+
node.innerHTML = [
|
|
25
|
+
["LIVE", summary.liveWorkers],
|
|
26
|
+
["BUSY", summary.busyWorkers],
|
|
27
|
+
["WAITING", summary.waitingWorkers],
|
|
28
|
+
["QUIET", summary.quietWorkers],
|
|
29
|
+
["ATTN", summary.attentionWorkers],
|
|
30
|
+
["FAILED", summary.failedWorkers],
|
|
31
|
+
["WATCH", summary.watchWorkers],
|
|
32
|
+
["SUGGEST", summary.suggestedWorkers],
|
|
33
|
+
["REQUIRED", summary.requiredWorkers],
|
|
34
|
+
["OBSERVE", summary.observeWorkers],
|
|
35
|
+
["ATTACH", summary.attachWorkers],
|
|
36
|
+
["RESTART", summary.restartWorkers],
|
|
37
|
+
["CLOSE", summary.closeWorkers],
|
|
38
|
+
["ATTACHES", summary.totalAttaches],
|
|
39
|
+
["RESTARTS", summary.totalRestarts],
|
|
40
|
+
["OUTPUT", ctx.formatBytes(summary.totalOutputChars)],
|
|
41
|
+
["CLOSED", summary.closedWorkers],
|
|
42
|
+
].map(([label, value]) => `
|
|
43
|
+
<div class="runtime-summary-stat">
|
|
44
|
+
<div class="runtime-summary-label">${ctx.escapeHtml(label)}</div>
|
|
45
|
+
<div class="runtime-summary-value">${ctx.escapeHtml(value)}</div>
|
|
46
|
+
</div>
|
|
47
|
+
`).join("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderRuntimeFocus(ctx) {
|
|
51
|
+
const node = ctx.getRuntimeFocusNode();
|
|
52
|
+
const focusItems = ctx.runtimeFocus || [];
|
|
53
|
+
if (!focusItems.length) {
|
|
54
|
+
node.className = "runtime-focus-empty";
|
|
55
|
+
node.textContent = "No immediate actions.";
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
node.className = "runtime-focus-list";
|
|
59
|
+
node.innerHTML = focusItems.map((item) => `
|
|
60
|
+
<div class="runtime-focus-item" onclick="attachWorker('${item.sessionId}')">
|
|
61
|
+
<div class="runtime-focus-head">
|
|
62
|
+
<span>${ctx.escapeHtml(ctx.formatFocusAction(item))} · ${ctx.escapeHtml(item.agentType)} · ${ctx.escapeHtml(item.title)}</span>
|
|
63
|
+
<span>${ctx.escapeHtml(ctx.formatRelativeTime(item.updatedAt))}</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="runtime-focus-body">${ctx.escapeHtml(item.reason || `${item.phase || "worker"} needs attention`)}</div>
|
|
66
|
+
</div>
|
|
67
|
+
`).join("");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function renderRuntimeFeed(ctx) {
|
|
71
|
+
const node = ctx.getRuntimeFeedNode();
|
|
72
|
+
const recent = [...ctx.workers]
|
|
73
|
+
.filter((worker) => worker.lastEventAt && worker.lastEventSummary && !ctx.isControlNoise(worker.lastEventSummary))
|
|
74
|
+
.sort((a, b) => (b.lastEventAt || 0) - (a.lastEventAt || 0))
|
|
75
|
+
.slice(0, 6);
|
|
76
|
+
if (!recent.length) {
|
|
77
|
+
node.className = "runtime-feed-empty";
|
|
78
|
+
node.innerHTML = "No worker activity yet.";
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
node.className = "runtime-feed-list";
|
|
82
|
+
node.innerHTML = recent.map((worker) => {
|
|
83
|
+
const label = worker.displayName || worker.taskSummary || `S:${worker.sessionId.slice(-6)}`;
|
|
84
|
+
const eventLevel = worker.lastEventLevel ? `<span class="runtime-level ${worker.lastEventLevel}">${ctx.escapeHtml(worker.lastEventLevel)}</span>` : "";
|
|
85
|
+
return `
|
|
86
|
+
<div class="runtime-feed-item">
|
|
87
|
+
<div class="runtime-feed-head">
|
|
88
|
+
<span>${ctx.escapeHtml(worker.agentType)} · ${ctx.escapeHtml(label)} ${eventLevel}</span>
|
|
89
|
+
<span>${ctx.escapeHtml(ctx.formatRelativeTime(worker.lastEventAt))}</span>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="runtime-feed-body">${ctx.escapeHtml(worker.lastEventSummary || "")}</div>
|
|
92
|
+
</div>
|
|
93
|
+
`;
|
|
94
|
+
}).join("");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function renderWorkers(ctx) {
|
|
98
|
+
const list = ctx.getWorkerListNode();
|
|
99
|
+
const nodeCard = ctx.getNodeNavCard();
|
|
100
|
+
if (ctx.selectedViewId !== "node" && !ctx.workers.some((worker) => worker.sessionId === ctx.selectedViewId)) {
|
|
101
|
+
ctx.selectNodeHome();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
renderRuntimeSummary(ctx);
|
|
105
|
+
renderRuntimeFocus(ctx);
|
|
106
|
+
renderRuntimeFeed(ctx);
|
|
107
|
+
ctx.renderNodeHome();
|
|
108
|
+
nodeCard.classList.toggle("active", ctx.isNodeViewSelected());
|
|
109
|
+
if (!ctx.workers.length) {
|
|
110
|
+
list.innerHTML = '<div class="worker-empty">No workers yet.</div>';
|
|
111
|
+
ctx.updateWorkspaceView();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const orderedWorkers = [...ctx.workers].sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
116
|
+
const liveWorkers = orderedWorkers.filter((worker) => worker.status !== "stopped" && worker.status !== "failed");
|
|
117
|
+
const closedWorkers = orderedWorkers.filter((worker) => worker.status === "stopped" || worker.status === "failed");
|
|
118
|
+
|
|
119
|
+
const renderWorkerCard = (worker) => {
|
|
120
|
+
const active = ctx.selectedViewId === worker.sessionId ? "active" : "";
|
|
121
|
+
const summary = worker.taskSummary || "";
|
|
122
|
+
const displayName = worker.displayName || `S:${worker.sessionId.slice(-6)}`;
|
|
123
|
+
const cwd = worker.cwd || ".";
|
|
124
|
+
const recent = ctx.formatLastOutput(worker.lastOutputAt);
|
|
125
|
+
const recentEvent = ctx.getWorkerPreviewLine(worker);
|
|
126
|
+
const recentEventAt = worker.lastEventAt ? ` · ${ctx.formatRelativeTime(worker.lastEventAt)}` : "";
|
|
127
|
+
const clickable = worker.status !== "stopped" && worker.status !== "failed";
|
|
128
|
+
const inactive = clickable ? "" : "inactive";
|
|
129
|
+
const badge = ctx.formatWorkerBadge(worker);
|
|
130
|
+
const attention = ctx.workerNeedsAttention(worker) ? "needs-attention" : "";
|
|
131
|
+
const location = ctx.pathBaseName(cwd);
|
|
132
|
+
const label = worker.agentSessionId
|
|
133
|
+
? `PIE:${worker.agentSessionId.slice(-6)}`
|
|
134
|
+
: `S:${worker.sessionId.slice(-6)}`;
|
|
135
|
+
const action = ctx.formatRecommendedAction(worker);
|
|
136
|
+
return `
|
|
137
|
+
<div class="worker-item ${active} ${inactive} ${attention} ${worker.status}" data-session-id="${worker.sessionId}" ${clickable ? `onclick="attachWorker('${worker.sessionId}')"` : ""}>
|
|
138
|
+
<div class="worker-title-row">
|
|
139
|
+
<div class="worker-type">${ctx.escapeHtml(displayName)}</div>
|
|
140
|
+
<div class="worker-label">${label}</div>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="worker-meta">${ctx.escapeHtml(location)}${summary ? ` · ${ctx.escapeHtml(summary)}` : ""}</div>
|
|
143
|
+
<div class="worker-event">${ctx.escapeHtml(recentEvent)}${ctx.escapeHtml(recentEventAt)}</div>
|
|
144
|
+
<div class="worker-badge">${badge}${action ? ` · ${ctx.escapeHtml(action)}` : ""}</div>
|
|
145
|
+
<div class="worker-session">${worker.sessionId.slice(-8)} · ${recent}</div>
|
|
146
|
+
</div>
|
|
147
|
+
`;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const liveMarkup = liveWorkers.length
|
|
151
|
+
? `<div class="worker-section-list">${liveWorkers.map(renderWorkerCard).join("")}</div>`
|
|
152
|
+
: '<div class="worker-empty">No live workers.</div>';
|
|
153
|
+
const closedToggle = closedWorkers.length
|
|
154
|
+
? `
|
|
155
|
+
<button class="worker-section-toggle" onclick="toggleClosedWorkers()">
|
|
156
|
+
<span>${ctx.showClosedWorkers ? "HIDE" : "SHOW"} CLOSED</span>
|
|
157
|
+
<span>${closedWorkers.length}</span>
|
|
158
|
+
</button>
|
|
159
|
+
`
|
|
160
|
+
: "";
|
|
161
|
+
const closedMarkup = closedWorkers.length && ctx.showClosedWorkers
|
|
162
|
+
? `<div class="worker-section-list">${closedWorkers.map(renderWorkerCard).join("")}</div>`
|
|
163
|
+
: "";
|
|
164
|
+
|
|
165
|
+
list.innerHTML = `
|
|
166
|
+
<div class="worker-section">${liveMarkup}</div>
|
|
167
|
+
${closedToggle ? `<div class="worker-section">${closedToggle}${closedMarkup}</div>` : ""}
|
|
168
|
+
`;
|
|
169
|
+
ctx.setWorkerHeaderLabel(`SESSIONS ${liveWorkers.length}${closedWorkers.length ? ` · ${closedWorkers.length} CLOSED` : ""}`);
|
|
170
|
+
ctx.renderCwdShortcuts();
|
|
171
|
+
ctx.updateWorkspaceView();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
global.MagicShellRuntimeRender = {
|
|
175
|
+
renderRuntimeSummary,
|
|
176
|
+
renderRuntimeFocus,
|
|
177
|
+
renderRuntimeFeed,
|
|
178
|
+
renderWorkers,
|
|
179
|
+
};
|
|
180
|
+
})(window);
|