@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,250 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
function createTerminalManager(options) {
|
|
3
|
+
const terminalViews = new Map();
|
|
4
|
+
let activeTerminalId = null;
|
|
5
|
+
|
|
6
|
+
function getViewport(view) {
|
|
7
|
+
return view?.pane?.querySelector?.(".xterm-viewport") || null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function syncScrollLock(view) {
|
|
11
|
+
const viewport = getViewport(view);
|
|
12
|
+
if (!viewport) {
|
|
13
|
+
view.scrollLockActive = false;
|
|
14
|
+
view.lockedScrollTop = 0;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const distanceFromBottom = Math.max(0, viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight);
|
|
18
|
+
if (distanceFromBottom <= 8) {
|
|
19
|
+
view.scrollLockActive = false;
|
|
20
|
+
view.lockedScrollTop = 0;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
view.scrollLockActive = true;
|
|
24
|
+
view.lockedScrollTop = viewport.scrollTop;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function captureScrollState(view) {
|
|
28
|
+
const viewport = getViewport(view);
|
|
29
|
+
if (!viewport) {
|
|
30
|
+
return { atBottom: true, viewportY: 0, lockActive: false };
|
|
31
|
+
}
|
|
32
|
+
if (view?.scrollLockActive) {
|
|
33
|
+
return {
|
|
34
|
+
atBottom: false,
|
|
35
|
+
viewportY: view.lockedScrollTop ?? viewport.scrollTop,
|
|
36
|
+
lockActive: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const distanceFromBottom = Math.max(0, viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight);
|
|
40
|
+
return {
|
|
41
|
+
atBottom: distanceFromBottom <= 8,
|
|
42
|
+
viewportY: viewport.scrollTop,
|
|
43
|
+
lockActive: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function restoreScrollState(view, state) {
|
|
48
|
+
if (!view || typeof view.term?.scrollToLine !== "function") {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!state || state.atBottom) {
|
|
52
|
+
if (typeof view.term?.scrollToBottom === "function") {
|
|
53
|
+
view.term.scrollToBottom();
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const viewport = getViewport(view);
|
|
58
|
+
if (!viewport) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const maxScrollTop = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
|
|
62
|
+
if (state?.lockActive || view.scrollLockActive) {
|
|
63
|
+
const target = state?.viewportY ?? view.lockedScrollTop ?? viewport.scrollTop;
|
|
64
|
+
viewport.scrollTop = Math.max(0, Math.min(target, maxScrollTop));
|
|
65
|
+
view.scrollLockActive = true;
|
|
66
|
+
view.lockedScrollTop = viewport.scrollTop;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
viewport.scrollTop = Math.max(0, Math.min(state.viewportY ?? 0, maxScrollTop));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createTerminalView(id) {
|
|
73
|
+
const pane = document.createElement("div");
|
|
74
|
+
pane.className = "terminal-pane";
|
|
75
|
+
pane.dataset.sessionId = id;
|
|
76
|
+
options.container.appendChild(pane);
|
|
77
|
+
|
|
78
|
+
const term = new Terminal({
|
|
79
|
+
fontFamily: '"JetBrains Mono", monospace',
|
|
80
|
+
fontSize: options.getFontSize(),
|
|
81
|
+
fontWeight: "500",
|
|
82
|
+
lineHeight: 1.16,
|
|
83
|
+
letterSpacing: 0,
|
|
84
|
+
theme: options.theme,
|
|
85
|
+
cursorBlink: true,
|
|
86
|
+
scrollback: 10000,
|
|
87
|
+
});
|
|
88
|
+
const fitAddon = new FitAddon.FitAddon();
|
|
89
|
+
term.loadAddon(fitAddon);
|
|
90
|
+
term.open(pane);
|
|
91
|
+
fitAddon.fit();
|
|
92
|
+
|
|
93
|
+
term.attachCustomKeyEventHandler((event) => {
|
|
94
|
+
if (event.type !== "keydown") return true;
|
|
95
|
+
if (event.key === "Enter" && event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
96
|
+
if (id === activeTerminalId && options.isConnected()) {
|
|
97
|
+
options.sendInput(id === "__system__" ? null : id, "\n");
|
|
98
|
+
}
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
term.onData((data) => {
|
|
106
|
+
if (id !== activeTerminalId || !options.isConnected()) return;
|
|
107
|
+
options.sendInput(id === "__system__" ? null : id, data);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
term.onResize(({ cols, rows }) => {
|
|
111
|
+
if (id !== activeTerminalId) return;
|
|
112
|
+
options.setTerminalSizeText(`${cols}×${rows}`);
|
|
113
|
+
if (options.isConnected() && id !== "__system__") {
|
|
114
|
+
options.sendResize(id, cols, rows);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const view = {
|
|
119
|
+
id,
|
|
120
|
+
pane,
|
|
121
|
+
term,
|
|
122
|
+
fitAddon,
|
|
123
|
+
scrollLockActive: false,
|
|
124
|
+
lockedScrollTop: 0,
|
|
125
|
+
};
|
|
126
|
+
terminalViews.set(id, view);
|
|
127
|
+
requestAnimationFrame(() => {
|
|
128
|
+
const viewport = getViewport(view);
|
|
129
|
+
if (!viewport) return;
|
|
130
|
+
viewport.addEventListener("scroll", () => {
|
|
131
|
+
syncScrollLock(view);
|
|
132
|
+
}, { passive: true });
|
|
133
|
+
});
|
|
134
|
+
return view;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function ensureView(id = "__system__") {
|
|
138
|
+
return terminalViews.get(id) || createTerminalView(id);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getActiveView() {
|
|
142
|
+
return ensureView(activeTerminalId || "__system__");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function fitView(view) {
|
|
146
|
+
if (!view) return;
|
|
147
|
+
const scrollState = captureScrollState(view);
|
|
148
|
+
const attemptFit = (remaining) => {
|
|
149
|
+
requestAnimationFrame(() => {
|
|
150
|
+
if (!view.pane.isConnected) return;
|
|
151
|
+
const paneWidth = view.pane.clientWidth;
|
|
152
|
+
const paneHeight = view.pane.clientHeight;
|
|
153
|
+
if (paneWidth > 0 && paneHeight > 0) {
|
|
154
|
+
view.fitAddon.fit();
|
|
155
|
+
restoreScrollState(view, scrollState);
|
|
156
|
+
options.setTerminalSizeText(`${view.term.cols}×${view.term.rows}`);
|
|
157
|
+
}
|
|
158
|
+
if (remaining > 0) {
|
|
159
|
+
setTimeout(() => attemptFit(remaining - 1), 80);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
attemptFit(3);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function writeToView(id, data, { newline = false } = {}) {
|
|
167
|
+
const view = ensureView(id || "__system__");
|
|
168
|
+
const term = view.term;
|
|
169
|
+
const writer = newline ? "writeln" : "write";
|
|
170
|
+
const scrollState = captureScrollState(view);
|
|
171
|
+
term[writer](data, () => {
|
|
172
|
+
restoreScrollState(view, scrollState);
|
|
173
|
+
});
|
|
174
|
+
return view;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function setActive(id) {
|
|
178
|
+
activeTerminalId = id;
|
|
179
|
+
for (const [viewId, view] of terminalViews) {
|
|
180
|
+
view.pane.classList.toggle("active", viewId === id);
|
|
181
|
+
}
|
|
182
|
+
const view = ensureView(id);
|
|
183
|
+
view.pane.classList.add("active");
|
|
184
|
+
syncScrollLock(view);
|
|
185
|
+
fitView(view);
|
|
186
|
+
options.setTerminalSizeText(`${view.term.cols}×${view.term.rows}`);
|
|
187
|
+
view.term.focus();
|
|
188
|
+
options.onActiveChange?.(activeTerminalId);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function fitAll() {
|
|
192
|
+
for (const view of terminalViews.values()) {
|
|
193
|
+
fitView(view);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function destroyAll() {
|
|
198
|
+
for (const view of terminalViews.values()) {
|
|
199
|
+
view.term.dispose();
|
|
200
|
+
view.pane.remove();
|
|
201
|
+
}
|
|
202
|
+
terminalViews.clear();
|
|
203
|
+
activeTerminalId = null;
|
|
204
|
+
options.onActiveChange?.(activeTerminalId);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function destroyView(id) {
|
|
208
|
+
if (!id || id === "__system__") return;
|
|
209
|
+
const view = terminalViews.get(id);
|
|
210
|
+
if (!view) return;
|
|
211
|
+
view.term.dispose();
|
|
212
|
+
view.pane.remove();
|
|
213
|
+
terminalViews.delete(id);
|
|
214
|
+
if (activeTerminalId === id) {
|
|
215
|
+
activeTerminalId = null;
|
|
216
|
+
options.onActiveChange?.(activeTerminalId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function updateFontSize(fontSize) {
|
|
221
|
+
for (const view of terminalViews.values()) {
|
|
222
|
+
view.term.options.fontSize = fontSize;
|
|
223
|
+
}
|
|
224
|
+
fitAll();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
createTerminalView,
|
|
229
|
+
ensureView,
|
|
230
|
+
getActiveView,
|
|
231
|
+
setActive,
|
|
232
|
+
fitView,
|
|
233
|
+
fitAll,
|
|
234
|
+
destroyAll,
|
|
235
|
+
destroyView,
|
|
236
|
+
updateFontSize,
|
|
237
|
+
writeToView,
|
|
238
|
+
writelnToView(id, data) {
|
|
239
|
+
return writeToView(id, data, { newline: true });
|
|
240
|
+
},
|
|
241
|
+
getActiveId() {
|
|
242
|
+
return activeTerminalId;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
global.MagicShellRuntimeTerminal = {
|
|
248
|
+
createTerminalManager,
|
|
249
|
+
};
|
|
250
|
+
})(window);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
function createWorkbenchTransport(options) {
|
|
3
|
+
let ws = null;
|
|
4
|
+
let requestCounter = 0;
|
|
5
|
+
|
|
6
|
+
function isOpen() {
|
|
7
|
+
return !!ws && ws.readyState === WebSocket.OPEN;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getSocket() {
|
|
11
|
+
return ws;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function sendRaw(message) {
|
|
15
|
+
if (!isOpen()) return false;
|
|
16
|
+
ws.send(JSON.stringify(message));
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sendControl(controlKind, controlName, payload = {}, target = {}) {
|
|
21
|
+
if (!isOpen()) return null;
|
|
22
|
+
const requestId = `web-${Date.now()}-${requestCounter++}`;
|
|
23
|
+
ws.send(JSON.stringify({
|
|
24
|
+
type: "control",
|
|
25
|
+
controlKind,
|
|
26
|
+
controlName,
|
|
27
|
+
requestId,
|
|
28
|
+
target,
|
|
29
|
+
payload,
|
|
30
|
+
}));
|
|
31
|
+
return requestId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function connect({ relayUrl, nodeId, password }) {
|
|
35
|
+
if (!relayUrl || !nodeId || !password) {
|
|
36
|
+
throw new Error("Please fill in all fields");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) {
|
|
40
|
+
console.log("Already connected, ignoring");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ws = new WebSocket(relayUrl);
|
|
45
|
+
options.onSocketChange?.(ws);
|
|
46
|
+
|
|
47
|
+
ws.onopen = () => {
|
|
48
|
+
console.log("WebSocket connected");
|
|
49
|
+
options.onOpen?.({ relayUrl, nodeId, password });
|
|
50
|
+
ws.send(JSON.stringify({
|
|
51
|
+
type: "auth",
|
|
52
|
+
clientType: "browser",
|
|
53
|
+
nodeId,
|
|
54
|
+
password,
|
|
55
|
+
}));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
ws.onmessage = (event) => {
|
|
59
|
+
try {
|
|
60
|
+
const message = JSON.parse(event.data);
|
|
61
|
+
console.log("Received:", message);
|
|
62
|
+
options.onMessage?.(message);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("Failed to parse message:", err);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
ws.onclose = () => {
|
|
69
|
+
console.log("WebSocket closed");
|
|
70
|
+
ws = null;
|
|
71
|
+
options.onSocketChange?.(null);
|
|
72
|
+
options.onClose?.();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
ws.onerror = (err) => {
|
|
76
|
+
console.error("WebSocket error:", err);
|
|
77
|
+
options.onError?.(err);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function disconnect() {
|
|
84
|
+
if (ws) {
|
|
85
|
+
const closingSocket = ws;
|
|
86
|
+
ws = null;
|
|
87
|
+
options.onSocketChange?.(null);
|
|
88
|
+
closingSocket.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
connect,
|
|
94
|
+
disconnect,
|
|
95
|
+
sendControl,
|
|
96
|
+
sendRaw,
|
|
97
|
+
isOpen,
|
|
98
|
+
getSocket,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
global.MagicShellTransport = {
|
|
103
|
+
createWorkbenchTransport,
|
|
104
|
+
};
|
|
105
|
+
})(window);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
(function (global) {
|
|
2
|
+
function renderCurrentWorkerBar(ctx) {
|
|
3
|
+
const bar = ctx.getBarNode();
|
|
4
|
+
const title = ctx.getTitleNode();
|
|
5
|
+
const subtitle = ctx.getSubtitleNode();
|
|
6
|
+
const stopButton = ctx.getStopButton();
|
|
7
|
+
const restartButton = ctx.getRestartButton();
|
|
8
|
+
const detailButton = ctx.getDetailButton();
|
|
9
|
+
const currentWorker = ctx.workers.find((worker) => worker.sessionId === ctx.sessionId);
|
|
10
|
+
|
|
11
|
+
if (!currentWorker) {
|
|
12
|
+
bar.classList.remove("active");
|
|
13
|
+
title.textContent = "No worker attached";
|
|
14
|
+
subtitle.textContent = "Attach a worker to start";
|
|
15
|
+
stopButton.disabled = true;
|
|
16
|
+
restartButton.disabled = true;
|
|
17
|
+
detailButton.disabled = true;
|
|
18
|
+
ctx.renderWorkerDetailPanel();
|
|
19
|
+
if (!ctx.isNodeViewSelected()) {
|
|
20
|
+
ctx.setActiveTerminal("__system__");
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
bar.classList.add("active");
|
|
26
|
+
title.textContent = currentWorker.displayName || `S:${currentWorker.sessionId.slice(-6)}`;
|
|
27
|
+
const agentSession = currentWorker.agentSessionId ? `PIE:${currentWorker.agentSessionId.slice(-6)} · ` : "";
|
|
28
|
+
const previewLine = ctx.getWorkerPreviewLine(currentWorker);
|
|
29
|
+
subtitle.textContent = `${currentWorker.agentType} · ${ctx.pathBaseName(currentWorker.cwd || ".")} · ${agentSession}${currentWorker.status} · ${ctx.formatWorkerBadge(currentWorker)} · ${ctx.formatLastOutput(currentWorker.lastOutputAt)}${previewLine ? ` · ${previewLine}` : ""}`;
|
|
30
|
+
stopButton.disabled = ctx.pendingStopSessionId === currentWorker.sessionId
|
|
31
|
+
|| currentWorker.status === "stopping"
|
|
32
|
+
|| currentWorker.status === "stopped"
|
|
33
|
+
|| currentWorker.status === "failed";
|
|
34
|
+
restartButton.disabled = ctx.pendingRestartSessionId === currentWorker.sessionId
|
|
35
|
+
|| currentWorker.status === "starting"
|
|
36
|
+
|| currentWorker.status === "stopping";
|
|
37
|
+
detailButton.disabled = false;
|
|
38
|
+
ctx.renderWorkerDetailPanel();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function renderWorkerDetailPanel(ctx) {
|
|
42
|
+
const panel = ctx.getPanelNode();
|
|
43
|
+
const title = ctx.getTitleNode();
|
|
44
|
+
const meta = ctx.getMetaNode();
|
|
45
|
+
const snapshots = ctx.getSnapshotsNode();
|
|
46
|
+
const events = ctx.getEventsNode();
|
|
47
|
+
const output = ctx.getOutputNode();
|
|
48
|
+
if (!ctx.workerDetailOpen || !ctx.workerDetail || !ctx.sessionId || ctx.workerDetail.worker.sessionId !== ctx.sessionId) {
|
|
49
|
+
panel.classList.remove("active");
|
|
50
|
+
meta.textContent = "";
|
|
51
|
+
snapshots.innerHTML = "";
|
|
52
|
+
events.innerHTML = "";
|
|
53
|
+
output.textContent = "";
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
panel.classList.add("active");
|
|
58
|
+
title.textContent = `DETAILS · ${(ctx.workerDetail.worker.displayName || `S:${ctx.workerDetail.worker.sessionId.slice(-6)}`)}`;
|
|
59
|
+
meta.textContent = [
|
|
60
|
+
`plugin: ${ctx.workerDetail.pluginName}`,
|
|
61
|
+
`cwd: ${ctx.workerDetail.worker.cwd}`,
|
|
62
|
+
`session: ${ctx.workerDetail.worker.sessionId}`,
|
|
63
|
+
ctx.workerDetail.worker.agentSessionId ? `pie session: ${ctx.workerDetail.worker.agentSessionId}` : null,
|
|
64
|
+
`status: ${ctx.workerDetail.worker.status}${ctx.workerDetail.worker.coordinationState ? ` · ${ctx.workerDetail.worker.coordinationState}` : ""}${ctx.workerDetail.worker.phase ? ` · ${ctx.workerDetail.worker.phase}` : ""}${ctx.workerDetail.worker.activityState ? ` · ${ctx.workerDetail.worker.activityState}` : ""}`,
|
|
65
|
+
ctx.workerDetail.worker.coordinationTaskSummary ? `delegated task: ${ctx.workerDetail.worker.coordinationTaskSummary}` : null,
|
|
66
|
+
ctx.workerDetail.worker.attentionReason ? `attention: ${ctx.workerDetail.worker.attentionReason}` : null,
|
|
67
|
+
ctx.workerDetail.worker.interventionLevel && ctx.workerDetail.worker.interventionLevel !== "none" ? `intervention: ${ctx.workerDetail.worker.interventionLevel}${ctx.workerDetail.worker.interventionReason ? ` · ${ctx.workerDetail.worker.interventionReason}` : ""}` : null,
|
|
68
|
+
ctx.workerDetail.worker.interventionSince ? `intervention since: ${ctx.formatRelativeTime(ctx.workerDetail.worker.interventionSince)}` : null,
|
|
69
|
+
ctx.workerDetail.worker.recommendedAction && ctx.workerDetail.worker.recommendedAction !== "none" ? `recommended: ${ctx.workerDetail.worker.recommendedAction}${ctx.workerDetail.worker.recommendedActionReason ? ` · ${ctx.workerDetail.worker.recommendedActionReason}` : ""}` : null,
|
|
70
|
+
`created: ${ctx.formatRelativeTime(ctx.workerDetail.worker.createdAt)}`,
|
|
71
|
+
ctx.workerDetail.worker.startedAt ? `active for: ${ctx.formatDuration(Date.now() - ctx.workerDetail.worker.startedAt)}` : null,
|
|
72
|
+
ctx.workerDetail.worker.totalRunMs ? `total run: ${ctx.formatDuration(ctx.workerDetail.worker.totalRunMs)}` : null,
|
|
73
|
+
ctx.workerDetail.worker.lastAttachedAt ? `last attach: ${ctx.formatRelativeTime(ctx.workerDetail.worker.lastAttachedAt)}` : null,
|
|
74
|
+
ctx.workerDetail.worker.stoppedAt ? `last stop: ${ctx.formatRelativeTime(ctx.workerDetail.worker.stoppedAt)}` : null,
|
|
75
|
+
ctx.workerDetail.worker.lastEventSummary ? `last event: ${ctx.workerDetail.worker.lastEventSummary} · ${ctx.formatRelativeTime(ctx.workerDetail.worker.lastEventAt)}` : null,
|
|
76
|
+
`attaches: ${ctx.workerDetail.worker.attachCount || 0} · restarts: ${ctx.workerDetail.worker.restartCount || 0} · inspects: ${ctx.workerDetail.worker.inspectCount || 0}`,
|
|
77
|
+
`output: ${ctx.workerDetail.worker.outputEventCount || 0} events · ${ctx.formatBytes(ctx.workerDetail.worker.outputCharCount || 0)}`,
|
|
78
|
+
].filter(Boolean).join("\n");
|
|
79
|
+
snapshots.innerHTML = (ctx.workerDetail.snapshots || []).slice().reverse().map((snapshot) => `
|
|
80
|
+
<div class="worker-detail-snapshot">${ctx.escapeHtml(ctx.formatEventTime(snapshot.timestamp))} · ${ctx.escapeHtml(snapshot.summary)}</div>
|
|
81
|
+
`).join("");
|
|
82
|
+
events.innerHTML = (ctx.workerDetail.recentEvents || []).slice().reverse().map((event) => `
|
|
83
|
+
<div class="worker-detail-event">${ctx.escapeHtml(ctx.formatEventTime(event.timestamp))}${event.level ? ` · ${ctx.escapeHtml(event.level)}` : ""} · ${ctx.escapeHtml(event.message)}</div>
|
|
84
|
+
`).join("");
|
|
85
|
+
output.textContent = ctx.workerDetail.lastBufferedOutput
|
|
86
|
+
? `RECENT OUTPUT\n${ctx.stripAnsi(ctx.workerDetail.lastBufferedOutput).trimEnd().slice(-1200)}`
|
|
87
|
+
: "RECENT OUTPUT\nNo buffered output yet.";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
global.MagicShellRuntimeWorkerUI = {
|
|
91
|
+
renderCurrentWorkerBar,
|
|
92
|
+
renderWorkerDetailPanel,
|
|
93
|
+
};
|
|
94
|
+
})(window);
|