@nimbus-sh/worker 0.1.5 → 0.1.6
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/README.md +37 -0
- package/dist/_shared/crypto.d.ts +15 -0
- package/dist/_shared/crypto.d.ts.map +1 -0
- package/dist/_shared/crypto.js +122 -0
- package/dist/facets/manager.d.ts +24 -0
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +279 -70
- package/dist/facets/process.d.ts +11 -2
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +187 -21
- package/dist/facets/vite-dev-server.d.ts +2 -0
- package/dist/facets/vite-dev-server.d.ts.map +1 -1
- package/dist/facets/vite-dev-server.js +39 -12
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/loaders/child-process/spawn-facet.d.ts +1 -3
- package/dist/loaders/child-process/spawn-facet.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-facet.js +1 -12
- package/dist/loaders/child-process/spawn-pool.d.ts +1 -1
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +1 -6
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +47 -27
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +2 -1
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +41 -13
- package/dist/npm/resolver.d.ts.map +1 -1
- package/dist/npm/resolver.js +70 -35
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +45 -21
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +29 -8
- package/dist/runtime/barrel-synthesizer.d.ts +7 -0
- package/dist/runtime/barrel-synthesizer.d.ts.map +1 -1
- package/dist/runtime/barrel-synthesizer.js +11 -0
- package/dist/runtime/clang-runner.d.ts.map +1 -1
- package/dist/runtime/clang-runner.js +1 -8
- package/dist/runtime/esbuild-service.d.ts +12 -5
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +28 -14
- package/dist/runtime/node-runner.d.ts +2 -0
- package/dist/runtime/node-runner.d.ts.map +1 -1
- package/dist/runtime/node-shims.d.ts +4 -2
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +404 -55
- package/dist/runtime/os-contracts.d.ts +128 -0
- package/dist/runtime/os-contracts.d.ts.map +1 -0
- package/dist/runtime/os-contracts.js +1 -0
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +32 -7
- package/dist/runtime/port-registry.d.ts +5 -0
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +22 -1
- package/dist/runtime/pyodide-runtime-assets.d.ts +19 -0
- package/dist/runtime/pyodide-runtime-assets.d.ts.map +1 -0
- package/dist/runtime/pyodide-runtime-assets.js +44 -0
- package/dist/runtime/python-repl.d.ts +3 -1
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +11 -29
- package/dist/runtime/python-runner.d.ts +8 -9
- package/dist/runtime/python-runner.d.ts.map +1 -1
- package/dist/runtime/python-runner.js +897 -61
- package/dist/runtime/python-socket-shim.d.ts +11 -0
- package/dist/runtime/python-socket-shim.d.ts.map +1 -0
- package/dist/runtime/python-socket-shim.js +526 -0
- package/dist/runtime/require-resolver.d.ts.map +1 -1
- package/dist/runtime/require-resolver.js +2 -11
- package/dist/runtime/ruby-gems.d.ts +25 -0
- package/dist/runtime/ruby-gems.d.ts.map +1 -0
- package/dist/runtime/ruby-gems.js +582 -0
- package/dist/runtime/ruby-runner.d.ts +2 -4
- package/dist/runtime/ruby-runner.d.ts.map +1 -1
- package/dist/runtime/ruby-runner.js +335 -34
- package/dist/runtime/runtime-catalog.d.ts +16 -4
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +21 -10
- package/dist/runtime/runtime-registry.d.ts +3 -0
- package/dist/runtime/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/runtime-registry.js +4 -0
- package/dist/runtime/sqlite-runtime-fs-bridge.d.ts +43 -0
- package/dist/runtime/sqlite-runtime-fs-bridge.d.ts.map +1 -0
- package/dist/runtime/sqlite-runtime-fs-bridge.js +224 -0
- package/dist/runtime/static-server.d.ts +3 -0
- package/dist/runtime/static-server.d.ts.map +1 -0
- package/dist/runtime/static-server.js +121 -0
- package/dist/runtime/vfs-snapshot.d.ts +57 -0
- package/dist/runtime/vfs-snapshot.d.ts.map +1 -0
- package/dist/runtime/vfs-snapshot.js +141 -0
- package/dist/runtime/virtual-socket-kernel.d.ts +16 -0
- package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -0
- package/dist/runtime/virtual-socket-kernel.js +360 -0
- package/dist/runtime/wasi-instance.d.ts +6 -3
- package/dist/runtime/wasi-instance.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.d.ts +1 -25
- package/dist/runtime/wasm-runner.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.js +1 -160
- package/dist/session/agent.d.ts +43 -0
- package/dist/session/agent.d.ts.map +1 -0
- package/dist/session/agent.js +1252 -0
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +15 -16
- package/dist/session/nimbus-session.d.ts +13 -2
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +55 -7
- package/dist/session/programmatic.d.ts +10 -0
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +121 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +19 -1
- package/dist/session/rpc.d.ts +11 -0
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +56 -43
- package/dist/session/supervisor-rpc.d.ts +15 -0
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +32 -0
- package/dist/session/ws.d.ts +4 -3
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +11 -1
- package/dist/shell/unix-commands.js +12 -16
- package/dist/vfs/path.d.ts +8 -4
- package/dist/vfs/path.d.ts.map +1 -1
- package/dist/vfs/path.js +16 -4
- package/dist/vfs/sqlite-vfs.d.ts +2 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +11 -0
- package/dist/vfs/symlink-registry.d.ts +7 -1
- package/dist/vfs/symlink-registry.d.ts.map +1 -1
- package/dist/vfs/symlink-registry.js +16 -11
- package/package.json +4 -1
- package/public/index.html +240 -0
- package/public/s/index.html +1172 -146
- package/runtime-contracts/pyodide-workerd-adapter.json +18 -0
- package/scripts/bundle-runtime.mjs +99 -24
package/public/s/index.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
.toolbar .sep { width: 1px; height: 16px; background: #30363d; margin: 0 4px; }
|
|
30
30
|
.toolbar .vite-status { font-size: 11px; color: #8b949e; margin-left: auto; }
|
|
31
31
|
.toolbar .vite-status.running { color: #3fb950; }
|
|
32
|
-
/* Process count button. Sits next to the
|
|
32
|
+
/* Process count button. Sits next to the workspace buttons. Hidden
|
|
33
33
|
(display:none) until at least one process has been seen in this
|
|
34
34
|
session so it doesn't clutter a brand-new session's toolbar. */
|
|
35
35
|
.toolbar #btnProcs { display: none; gap: 6px; align-items: center; font-family: ui-monospace, Menlo, monospace; }
|
|
@@ -96,7 +96,35 @@
|
|
|
96
96
|
/* Preview panel */
|
|
97
97
|
.panel-preview { flex: 1; display: flex; flex-direction: column; background: #0d1117; overflow: hidden; min-width: 0; }
|
|
98
98
|
.panel-preview.hidden { display: none; }
|
|
99
|
-
.preview-bar { display: flex; align-items: center; gap: 6px; padding: 3px 8px; background: #161b22; border-bottom: 1px solid #30363d; flex-shrink: 0; }
|
|
99
|
+
.preview-bar { display: flex; align-items: center; gap: 6px; padding: 3px 8px; background: #161b22; border-bottom: 1px solid #30363d; flex-shrink: 0; min-width: 0; }
|
|
100
|
+
.preview-tabs {
|
|
101
|
+
display: flex; align-items: center; gap: 3px; min-width: 110px; max-width: 45%;
|
|
102
|
+
overflow-x: auto; scrollbar-width: none; flex: 0 1 auto;
|
|
103
|
+
}
|
|
104
|
+
.preview-tabs::-webkit-scrollbar { display: none; }
|
|
105
|
+
.preview-bar .preview-tab {
|
|
106
|
+
height: 22px; display: inline-flex; align-items: center; gap: 5px; min-width: 0;
|
|
107
|
+
max-width: 150px; padding: 0 8px; border-radius: 4px 4px 0 0;
|
|
108
|
+
border: 1px solid transparent; border-bottom-color: transparent;
|
|
109
|
+
background: transparent; color: #8b949e; font-size: 11px; line-height: 1;
|
|
110
|
+
white-space: nowrap; cursor: pointer;
|
|
111
|
+
}
|
|
112
|
+
.preview-bar .preview-tab:hover { background: #21262d; color: #c9d1d9; }
|
|
113
|
+
.preview-bar .preview-tab.active {
|
|
114
|
+
background: #0d1117; color: #f0f6fc; border-color: #30363d;
|
|
115
|
+
border-bottom-color: #0d1117;
|
|
116
|
+
}
|
|
117
|
+
.preview-bar .preview-tab.running::before {
|
|
118
|
+
content: ""; width: 6px; height: 6px; border-radius: 999px;
|
|
119
|
+
background: #3fb950; box-shadow: 0 0 8px rgba(63,185,80,0.55);
|
|
120
|
+
flex: 0 0 auto;
|
|
121
|
+
}
|
|
122
|
+
.preview-bar .preview-tab.markdown::before {
|
|
123
|
+
content: "md"; color: #79c0ff; font: 9px/1 ui-monospace, Menlo, monospace;
|
|
124
|
+
border: 1px solid rgba(121,192,255,0.35); border-radius: 3px; padding: 1px 2px;
|
|
125
|
+
flex: 0 0 auto;
|
|
126
|
+
}
|
|
127
|
+
.preview-tab-label { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|
|
100
128
|
.preview-bar .url-bar { flex: 1; background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; padding: 2px 8px; font-size: 11px; border-radius: 4px; font-family: monospace; }
|
|
101
129
|
.preview-bar button { background: none; border: none; color: #8b949e; cursor: pointer; font-size: 14px; padding: 2px 4px; }
|
|
102
130
|
.preview-bar button:hover { color: #c9d1d9; }
|
|
@@ -151,26 +179,201 @@
|
|
|
151
179
|
.footer .stat-value { color: #c9d1d9; }
|
|
152
180
|
.footer .stat-value.good { color: #3fb950; }
|
|
153
181
|
|
|
154
|
-
/*
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
182
|
+
/* Agent chat */
|
|
183
|
+
.panel-agent {
|
|
184
|
+
display: none; flex: 1; min-width: 0; min-height: 0;
|
|
185
|
+
background: #0f141b; color: #d6dee8; flex-direction: column;
|
|
186
|
+
}
|
|
187
|
+
.agent-shell {
|
|
188
|
+
flex: 1; min-height: 0; display: grid;
|
|
189
|
+
grid-template-rows: auto 1fr auto;
|
|
190
|
+
width: 100%;
|
|
191
|
+
background:
|
|
192
|
+
linear-gradient(180deg, rgba(88,166,255,0.04), rgba(63,185,80,0.015) 28%, transparent 48%),
|
|
193
|
+
#0f141b;
|
|
194
|
+
}
|
|
195
|
+
.agent-top {
|
|
196
|
+
display: flex; align-items: center; gap: 8px;
|
|
197
|
+
padding: 7px 12px; background: rgba(22,27,34,0.96);
|
|
198
|
+
border-bottom: 1px solid #30363d; min-height: 40px;
|
|
199
|
+
}
|
|
200
|
+
.agent-heading { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
|
|
201
|
+
.agent-title { font-size: 12px; font-weight: 650; color: #f0f6fc; white-space: nowrap; }
|
|
202
|
+
.agent-subtitle {
|
|
203
|
+
color: #8b949e; font-size: 11px; overflow: hidden; text-overflow: ellipsis;
|
|
204
|
+
white-space: nowrap; max-width: 220px;
|
|
205
|
+
}
|
|
206
|
+
.agent-status {
|
|
207
|
+
margin-left: auto; font-size: 11px; color: #8b949e;
|
|
208
|
+
font-family: ui-monospace, Menlo, monospace; display: inline-flex;
|
|
209
|
+
align-items: center; gap: 6px; min-width: 0; white-space: nowrap;
|
|
210
|
+
}
|
|
211
|
+
.agent-status::before {
|
|
212
|
+
content: ''; width: 6px; height: 6px; border-radius: 50%;
|
|
213
|
+
background: #6e7681; box-shadow: 0 0 0 1px rgba(255,255,255,0.05);
|
|
214
|
+
}
|
|
215
|
+
.agent-status.ready { color: #3fb950; }
|
|
216
|
+
.agent-status.ready::before { background: #3fb950; box-shadow: 0 0 8px rgba(63,185,80,0.55); }
|
|
217
|
+
.agent-status.warn { color: #d29922; }
|
|
218
|
+
.agent-status.warn::before { background: #d29922; box-shadow: 0 0 8px rgba(210,153,34,0.45); }
|
|
219
|
+
.agent-status.streaming { color: #58a6ff; }
|
|
220
|
+
.agent-status.streaming::before { background: #58a6ff; animation: pulse 1.1s ease-in-out infinite; }
|
|
221
|
+
.agent-top button,
|
|
222
|
+
.agent-composer button {
|
|
223
|
+
background: #21262d; border: 1px solid #30363d; color: #c9d1d9;
|
|
224
|
+
padding: 4px 10px; border-radius: 6px; cursor: pointer; font-size: 11px;
|
|
225
|
+
}
|
|
226
|
+
.agent-top button:hover,
|
|
227
|
+
.agent-composer button:hover { background: #30363d; }
|
|
228
|
+
.agent-top button:disabled,
|
|
229
|
+
.agent-composer button:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
230
|
+
.agent-top .agent-secondary { color: #8b949e; }
|
|
231
|
+
.agent-top select {
|
|
232
|
+
background: #0d1117; border: 1px solid #30363d; color: #c9d1d9;
|
|
233
|
+
padding: 4px 8px; border-radius: 6px; font-size: 11px;
|
|
234
|
+
max-width: 260px;
|
|
235
|
+
}
|
|
236
|
+
.agent-messages {
|
|
237
|
+
min-height: 0; overflow: auto; padding: 22px 18px 26px;
|
|
238
|
+
display: flex; flex-direction: column; gap: 16px;
|
|
239
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
240
|
+
}
|
|
241
|
+
.agent-empty {
|
|
242
|
+
margin: auto; max-width: 560px; color: #8b949e; text-align: center;
|
|
243
|
+
font-size: 13px; line-height: 1.6; padding: 28px;
|
|
244
|
+
border: 1px dashed #30363d; border-radius: 8px;
|
|
245
|
+
background: rgba(22,27,34,0.55);
|
|
246
|
+
}
|
|
247
|
+
.agent-msg {
|
|
248
|
+
width: min(900px, 100%); margin: 0 auto;
|
|
249
|
+
display: flex; gap: 10px; align-items: flex-start;
|
|
250
|
+
}
|
|
251
|
+
.agent-msg.user { justify-content: flex-end; }
|
|
252
|
+
.agent-avatar {
|
|
253
|
+
width: 24px; height: 24px; border-radius: 6px; flex: 0 0 auto;
|
|
254
|
+
display: grid; place-items: center; margin-top: 2px;
|
|
255
|
+
border: 1px solid #30363d; background: #161b22;
|
|
256
|
+
color: #8b949e; font: 10px ui-monospace, Menlo, monospace;
|
|
257
|
+
}
|
|
258
|
+
.agent-msg.assistant .agent-avatar { color: #7ee787; border-color: rgba(63,185,80,0.35); background: rgba(63,185,80,0.08); }
|
|
259
|
+
.agent-msg.user .agent-avatar { display: none; }
|
|
260
|
+
.agent-content {
|
|
261
|
+
min-width: 0; max-width: min(760px, 86%); display: flex; flex-direction: column; gap: 8px;
|
|
262
|
+
}
|
|
263
|
+
.agent-msg.user .agent-content { align-items: flex-end; }
|
|
264
|
+
.agent-meta {
|
|
265
|
+
color: #6e7681; font: 10px ui-monospace, Menlo, monospace;
|
|
266
|
+
text-transform: uppercase; letter-spacing: 0.02em;
|
|
267
|
+
}
|
|
268
|
+
.agent-bubble {
|
|
269
|
+
border-radius: 8px; padding: 10px 12px; overflow-wrap: anywhere;
|
|
270
|
+
font-size: 13px; line-height: 1.58; color: #d6dee8;
|
|
271
|
+
}
|
|
272
|
+
.agent-msg.assistant .agent-bubble {
|
|
273
|
+
background: rgba(22,27,34,0.78); border: 1px solid rgba(48,54,61,0.85);
|
|
274
|
+
}
|
|
275
|
+
.agent-msg.user .agent-bubble {
|
|
276
|
+
background: linear-gradient(180deg, rgba(31,111,235,0.24), rgba(31,111,235,0.15));
|
|
277
|
+
border: 1px solid rgba(88,166,255,0.65); color: #f0f6fc;
|
|
278
|
+
max-width: min(680px, 76%);
|
|
279
|
+
}
|
|
280
|
+
.agent-bubble p { margin: 0 0 10px; }
|
|
281
|
+
.agent-bubble p:last-child { margin-bottom: 0; }
|
|
282
|
+
.agent-bubble ul, .agent-bubble ol { margin: 6px 0 10px 18px; }
|
|
283
|
+
.agent-bubble li { margin: 3px 0; }
|
|
284
|
+
.agent-bubble code {
|
|
285
|
+
background: rgba(110,118,129,0.25); border-radius: 4px;
|
|
286
|
+
padding: 0.12em 0.35em; color: #f0f6fc;
|
|
287
|
+
font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 0.92em;
|
|
288
|
+
}
|
|
289
|
+
.agent-bubble pre {
|
|
290
|
+
margin: 8px 0; padding: 10px 12px; border-radius: 8px;
|
|
291
|
+
border: 1px solid #30363d; background: #0d1117; overflow: auto;
|
|
292
|
+
white-space: pre;
|
|
293
|
+
}
|
|
294
|
+
.agent-bubble pre code { background: transparent; padding: 0; }
|
|
295
|
+
.agent-thinking {
|
|
296
|
+
display: inline-flex; align-items: center; gap: 7px;
|
|
297
|
+
color: #8b949e; font-size: 12px; padding: 4px 0;
|
|
298
|
+
}
|
|
299
|
+
.agent-thinking .dots { display: inline-flex; gap: 4px; }
|
|
300
|
+
.agent-thinking .dots span {
|
|
301
|
+
width: 5px; height: 5px; border-radius: 50%; background: #58a6ff;
|
|
302
|
+
animation: agent-dot 1s ease-in-out infinite;
|
|
303
|
+
}
|
|
304
|
+
.agent-thinking .dots span:nth-child(2) { animation-delay: 120ms; }
|
|
305
|
+
.agent-thinking .dots span:nth-child(3) { animation-delay: 240ms; }
|
|
306
|
+
@keyframes agent-dot { 0%, 70%, 100% { opacity: 0.32; transform: translateY(0); } 35% { opacity: 1; transform: translateY(-2px); } }
|
|
307
|
+
.agent-cursor {
|
|
308
|
+
display: inline-block; width: 2px; height: 1em; margin-left: 2px;
|
|
309
|
+
background: #58a6ff; vertical-align: -0.15em; animation: agent-cursor 1s steps(1) infinite;
|
|
310
|
+
}
|
|
311
|
+
@keyframes agent-cursor { 0%, 45% { opacity: 1; } 46%, 100% { opacity: 0; } }
|
|
312
|
+
.agent-reasoning {
|
|
313
|
+
border-left: 2px solid rgba(88,166,255,0.65); padding: 7px 10px;
|
|
314
|
+
background: rgba(88,166,255,0.06); color: #8b949e; border-radius: 0 8px 8px 0;
|
|
315
|
+
font-size: 12px; line-height: 1.5;
|
|
316
|
+
}
|
|
317
|
+
.agent-reasoning summary { cursor: pointer; color: #79c0ff; font-weight: 600; }
|
|
318
|
+
.agent-reasoning pre { margin-top: 7px; white-space: pre-wrap; font: 12px/1.5 ui-monospace, Menlo, monospace; color: #c9d1d9; }
|
|
319
|
+
.agent-tools { display: flex; flex-direction: column; gap: 7px; }
|
|
320
|
+
.agent-tool {
|
|
321
|
+
border: 1px solid #30363d; background: rgba(13,17,23,0.72);
|
|
322
|
+
border-radius: 8px; overflow: hidden;
|
|
323
|
+
}
|
|
324
|
+
.agent-tool summary {
|
|
325
|
+
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
|
326
|
+
padding: 7px 9px; list-style: none; user-select: none;
|
|
327
|
+
}
|
|
328
|
+
.agent-tool summary::-webkit-details-marker { display: none; }
|
|
329
|
+
.agent-tool .tool-dot {
|
|
330
|
+
width: 7px; height: 7px; border-radius: 50%; background: #6e7681; flex: 0 0 auto;
|
|
331
|
+
}
|
|
332
|
+
.agent-tool.running .tool-dot { background: #58a6ff; box-shadow: 0 0 8px rgba(88,166,255,0.7); animation: pulse 1s ease-in-out infinite; }
|
|
333
|
+
.agent-tool.done .tool-dot { background: #3fb950; }
|
|
334
|
+
.agent-tool.error .tool-dot { background: #ff7b72; }
|
|
335
|
+
.agent-tool .tool-name { font: 12px ui-monospace, Menlo, monospace; color: #f0f6fc; }
|
|
336
|
+
.agent-tool .tool-state { margin-left: auto; color: #8b949e; font-size: 11px; }
|
|
337
|
+
.agent-tool.running .tool-state { color: #58a6ff; }
|
|
338
|
+
.agent-tool.done .tool-state { color: #3fb950; }
|
|
339
|
+
.agent-tool.error .tool-state { color: #ff7b72; }
|
|
340
|
+
.agent-tool-body {
|
|
341
|
+
border-top: 1px solid #30363d; padding: 8px 9px; display: grid;
|
|
342
|
+
gap: 8px; background: rgba(22,27,34,0.55);
|
|
343
|
+
}
|
|
344
|
+
.agent-tool-body label {
|
|
345
|
+
display: block; color: #6e7681; font-size: 10px; text-transform: uppercase;
|
|
346
|
+
margin-bottom: 4px; font-family: ui-monospace, Menlo, monospace;
|
|
347
|
+
}
|
|
348
|
+
.agent-tool-body pre {
|
|
349
|
+
margin: 0; max-height: 190px; overflow: auto; white-space: pre-wrap;
|
|
350
|
+
background: #0d1117; border: 1px solid #30363d; border-radius: 6px;
|
|
351
|
+
padding: 8px; color: #c9d1d9; font: 11px/1.5 ui-monospace, Menlo, monospace;
|
|
352
|
+
}
|
|
353
|
+
.agent-composer {
|
|
354
|
+
display: grid; grid-template-columns: 1fr auto; gap: 8px;
|
|
355
|
+
padding: 12px 14px; background: rgba(22,27,34,0.96);
|
|
356
|
+
border-top: 1px solid #30363d;
|
|
357
|
+
}
|
|
358
|
+
.agent-composer textarea {
|
|
359
|
+
resize: none; min-height: 44px; max-height: 170px; background: #0d1117;
|
|
360
|
+
border: 1px solid #30363d; color: #f0f6fc; border-radius: 8px;
|
|
361
|
+
padding: 10px 12px; font: 13px/1.45 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; outline: none;
|
|
362
|
+
}
|
|
363
|
+
.agent-composer textarea:focus {
|
|
364
|
+
border-color: #58a6ff; box-shadow: 0 0 0 2px rgba(88,166,255,0.16);
|
|
365
|
+
}
|
|
366
|
+
.agent-composer button { min-width: 76px; font-weight: 650; }
|
|
367
|
+
.agent-composer button.streaming { border-color: #ff7b72; color: #ff7b72; }
|
|
368
|
+
|
|
369
|
+
/* Editor workspace:
|
|
165
370
|
FILE TREE | editor-stack (editor TOP + terminal BOTTOM) | PREVIEW
|
|
166
371
|
|
|
167
|
-
Editor
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
into .main). In editor mode the stack flips to flex column so
|
|
171
|
-
editor (top) + terminal (bottom) stack vertically. */
|
|
372
|
+
Editor, Agent, and terminal live inside .panel-left-stack. The top
|
|
373
|
+
surface switches between the editor and chat while the terminal and
|
|
374
|
+
preview stay visible. */
|
|
172
375
|
|
|
173
|
-
/* Editor panel — hidden by default; shown by
|
|
376
|
+
/* Editor panel — hidden by default; shown by workspace rules. */
|
|
174
377
|
.panel-editor {
|
|
175
378
|
flex: 1; display: none; flex-direction: column;
|
|
176
379
|
background: #1e1e1e; /* VSCode dark editor background */
|
|
@@ -178,12 +381,11 @@
|
|
|
178
381
|
}
|
|
179
382
|
.panel-editor.active { display: flex; }
|
|
180
383
|
|
|
181
|
-
/* Wrapper for the
|
|
182
|
-
modes (transparent). flex column in editor mode. */
|
|
384
|
+
/* Wrapper for the center workspace column. */
|
|
183
385
|
.panel-left-stack { display: contents; }
|
|
184
386
|
|
|
185
387
|
/* File-tree sidebar. Lives as a FIRST child of
|
|
186
|
-
.main; visible
|
|
388
|
+
.main; visible in the editor workspace. ~250px default width,
|
|
187
389
|
resizable via .tree-resize-handle. */
|
|
188
390
|
.panel-tree {
|
|
189
391
|
display: none; flex-direction: column;
|
|
@@ -283,6 +485,11 @@
|
|
|
283
485
|
.main.editor .panel-terminal {
|
|
284
486
|
display: flex; flex: 0 0 40%; min-height: 80px;
|
|
285
487
|
}
|
|
488
|
+
.main.editor .panel-agent { display: none; }
|
|
489
|
+
.main.editor .panel-left-stack.agent-surface .panel-editor { display: none; }
|
|
490
|
+
.main.editor .panel-left-stack.agent-surface .panel-agent {
|
|
491
|
+
display: flex; flex: 1 1 60%; min-height: 100px;
|
|
492
|
+
}
|
|
286
493
|
.main.editor .vresize-handle { display: block; }
|
|
287
494
|
.main.editor .resize-handle { display: block; }
|
|
288
495
|
.main.editor .panel-preview { display: flex; flex: 1; min-width: 0; }
|
|
@@ -333,13 +540,9 @@
|
|
|
333
540
|
</div>
|
|
334
541
|
</div>
|
|
335
542
|
<div class="toolbar" id="toolbar">
|
|
336
|
-
|
|
337
|
-
<button id="
|
|
338
|
-
<button id="
|
|
339
|
-
<!-- Single canonical editor mode:
|
|
340
|
-
file tree | editor (top) + terminal (bottom) | preview.
|
|
341
|
-
Lazy-loads Monaco + the tree on first click. -->
|
|
342
|
-
<button id="btnEditor" title="Editor (file tree + Monaco + terminal + preview)">Editor</button>
|
|
543
|
+
<!-- Single workspace: file tree | editor/agent + terminal | preview. -->
|
|
544
|
+
<button id="btnEditor" class="active" title="Editor">Editor</button>
|
|
545
|
+
<button id="btnAgent" title="Agent chat">Agent</button>
|
|
343
546
|
<div class="sep"></div>
|
|
344
547
|
<button id="btnProcs" title="Toggle process logs panel">
|
|
345
548
|
<span class="procs-dot"></span>
|
|
@@ -350,9 +553,8 @@
|
|
|
350
553
|
<span class="vite-status" id="viteStatus"></span>
|
|
351
554
|
</div>
|
|
352
555
|
<div class="main" id="mainPanel">
|
|
353
|
-
<!-- File-tree sidebar. Visible
|
|
354
|
-
|
|
355
|
-
The .tree-resize-handle sits between tree and editor stack;
|
|
556
|
+
<!-- File-tree sidebar. Visible in the workspace.
|
|
557
|
+
The .tree-resize-handle sits between tree and the center stack;
|
|
356
558
|
dragging it adjusts .panel-tree's width inline. -->
|
|
357
559
|
<div class="panel-tree" id="treePanel">
|
|
358
560
|
<div class="tree-toolbar">
|
|
@@ -367,11 +569,7 @@
|
|
|
367
569
|
<div class="tree-body" id="treeBody"></div>
|
|
368
570
|
</div>
|
|
369
571
|
<div class="tree-resize-handle" id="treeResizeHandle"></div>
|
|
370
|
-
<!-- The .panel-left-stack wrapper is
|
|
371
|
-
(terminal-only, split, preview-only) — its children act as
|
|
372
|
-
direct flex children of .main. In editor mode, CSS flips it to
|
|
373
|
-
display:flex so editor and terminal stack vertically in the middle
|
|
374
|
-
column. -->
|
|
572
|
+
<!-- The .panel-left-stack wrapper is the middle workspace column. -->
|
|
375
573
|
<div class="panel-left-stack" id="leftStack">
|
|
376
574
|
<div class="panel-editor" id="editorPanel">
|
|
377
575
|
<div class="editor-bar">
|
|
@@ -394,9 +592,29 @@
|
|
|
394
592
|
<span id="editorStatusEnc">UTF-8</span>
|
|
395
593
|
</div>
|
|
396
594
|
</div>
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
595
|
+
<div class="panel-agent" id="agentPanel">
|
|
596
|
+
<div class="agent-shell">
|
|
597
|
+
<div class="agent-top">
|
|
598
|
+
<div class="agent-heading">
|
|
599
|
+
<div class="agent-title">Nimbus Agent</div>
|
|
600
|
+
<div class="agent-subtitle" id="agentSubtitle">Workspace operator</div>
|
|
601
|
+
</div>
|
|
602
|
+
<select id="agentAccount" title="Cloudflare account" style="display:none"></select>
|
|
603
|
+
<button id="agentConnect" style="display:none">Connect Cloudflare</button>
|
|
604
|
+
<button id="agentDisconnect" class="agent-secondary" style="display:none">Disconnect</button>
|
|
605
|
+
<button id="agentClear" class="agent-secondary" title="Clear chat">Clear</button>
|
|
606
|
+
<div class="agent-status" id="agentStatus">Checking...</div>
|
|
607
|
+
</div>
|
|
608
|
+
<div class="agent-messages" id="agentMessages">
|
|
609
|
+
<div class="agent-empty" id="agentEmpty">Cloudflare connection required.</div>
|
|
610
|
+
</div>
|
|
611
|
+
<form class="agent-composer" id="agentForm">
|
|
612
|
+
<textarea id="agentInput" rows="2" placeholder="Message Nimbus"></textarea>
|
|
613
|
+
<button id="agentSend" type="submit">Send</button>
|
|
614
|
+
</form>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
<!-- Vertical resize handle between the top surface and terminal. -->
|
|
400
618
|
<div class="vresize-handle" id="editorTerminalResizeHandle"></div>
|
|
401
619
|
<div class="panel-terminal">
|
|
402
620
|
<div id="terminal-container"></div>
|
|
@@ -409,7 +627,7 @@
|
|
|
409
627
|
<div class="resize-handle" id="resizeHandle"></div>
|
|
410
628
|
<div class="panel-preview" id="previewPanel">
|
|
411
629
|
<div class="preview-bar">
|
|
412
|
-
<
|
|
630
|
+
<div class="preview-tabs" id="previewTabs" aria-label="Preview tabs"></div>
|
|
413
631
|
<input class="url-bar" id="urlBar" value="" readonly>
|
|
414
632
|
<button id="btnReloadPreview" title="Reload">↻</button>
|
|
415
633
|
</div>
|
|
@@ -454,6 +672,11 @@
|
|
|
454
672
|
const STATS_URL = SESSION_PREFIX + '/api/stats';
|
|
455
673
|
const VITE_URL = SESSION_PREFIX + '/preview/';
|
|
456
674
|
const WORKER_URL = SESSION_PREFIX + '/worker/';
|
|
675
|
+
const AGENT_STATUS_URL = SESSION_PREFIX + '/api/agent/status';
|
|
676
|
+
const AGENT_MESSAGES_URL = SESSION_PREFIX + '/api/agent/messages';
|
|
677
|
+
const AGENT_OAUTH_START_URL = SESSION_PREFIX + '/api/agent/oauth/start';
|
|
678
|
+
const AGENT_OAUTH_LOGOUT_URL = SESSION_PREFIX + '/api/agent/oauth/logout';
|
|
679
|
+
const AGENT_ACCOUNT_URL = SESSION_PREFIX + '/api/agent/account';
|
|
457
680
|
// The iframe starts pointing at /preview/ which handles its own
|
|
458
681
|
// "no dev server running" placeholder. When the user spawns a
|
|
459
682
|
// wrangler dev (via `wrangler dev` in the terminal), stats polling
|
|
@@ -520,68 +743,67 @@
|
|
|
520
743
|
const stText = document.getElementById('statusText');
|
|
521
744
|
const previewFrame = document.getElementById('preview-frame');
|
|
522
745
|
const previewPanel = document.getElementById('previewPanel');
|
|
746
|
+
const previewTabsEl = document.getElementById('previewTabs');
|
|
523
747
|
const markdownPreview = document.getElementById('markdown-preview');
|
|
524
748
|
const markdownPreviewBody = document.getElementById('markdown-preview-body');
|
|
525
749
|
const mainPanel = document.getElementById('mainPanel');
|
|
526
750
|
const viteStatus = document.getElementById('viteStatus');
|
|
527
751
|
let ws = null, rd = 1000, statsTimer = null;
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
let
|
|
752
|
+
// The app has one workspace layout. Only the top center surface switches.
|
|
753
|
+
let layout = 'editor';
|
|
754
|
+
let workSurface = 'editor';
|
|
531
755
|
|
|
532
756
|
// ── Layout controls ──
|
|
533
757
|
function setLayout(mode) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
document.getElementById('
|
|
538
|
-
|
|
539
|
-
document.getElementById('btnEditor').classList.toggle('active',
|
|
540
|
-
|
|
758
|
+
workSurface = mode === 'agent' ? 'agent' : 'editor';
|
|
759
|
+
layout = 'editor';
|
|
760
|
+
mainPanel.className = 'main editor';
|
|
761
|
+
const leftStack = document.getElementById('leftStack');
|
|
762
|
+
leftStack.classList.toggle('agent-surface', workSurface === 'agent');
|
|
763
|
+
document.getElementById('btnEditor').classList.toggle('active', workSurface === 'editor');
|
|
764
|
+
document.getElementById('btnAgent').classList.toggle('active', workSurface === 'agent');
|
|
765
|
+
// Editor panel visibility — drives display:flex via .active unless
|
|
766
|
+
// the agent surface is currently selected.
|
|
541
767
|
const editorPanel = document.getElementById('editorPanel');
|
|
542
|
-
const wantEditor = (
|
|
768
|
+
const wantEditor = (workSurface === 'editor');
|
|
543
769
|
editorPanel.classList.toggle('active', wantEditor);
|
|
544
|
-
|
|
545
|
-
//
|
|
770
|
+
FileTree.ensureLoaded().catch(() => {});
|
|
771
|
+
// Lazy-init Monaco the first time the editor surface is selected.
|
|
546
772
|
if (wantEditor) {
|
|
547
|
-
|
|
548
|
-
const treeReady = FileTree.ensureLoaded();
|
|
549
|
-
Promise.allSettled([editorReady, treeReady])
|
|
773
|
+
Editor.ensureLoaded()
|
|
550
774
|
.then(() => Editor.openDefaultWelcome())
|
|
551
775
|
.catch(() => {});
|
|
552
776
|
}
|
|
777
|
+
if (workSurface === 'agent') {
|
|
778
|
+
Agent.ensureLoaded().catch(() => {});
|
|
779
|
+
}
|
|
553
780
|
setTimeout(() => {
|
|
554
|
-
// Re-apply persisted pane dims for this
|
|
555
|
-
// PaneResizer uses inline flex which is mode-specific (split vs
|
|
556
|
-
// editor have different pane structures); onLayoutChange picks
|
|
557
|
-
// the right elements for the active mode.
|
|
781
|
+
// Re-apply persisted pane dims for this workspace.
|
|
558
782
|
try { if (typeof PaneResizer !== 'undefined') PaneResizer.onLayoutChange(); } catch {}
|
|
559
783
|
try { fitAddon.fit(); } catch {}
|
|
560
784
|
// Re-layout Monaco after the panel resizes. automaticLayout:true
|
|
561
|
-
// already handles this but explicit layout() on
|
|
785
|
+
// already handles this but explicit layout() on surface switch
|
|
562
786
|
// avoids a one-frame flash where the editor sees its old size.
|
|
563
787
|
if (window.__nimbusMonacoEditor) {
|
|
564
788
|
try { window.__nimbusMonacoEditor.layout(); } catch {}
|
|
565
789
|
}
|
|
566
790
|
}, 50);
|
|
567
791
|
}
|
|
568
|
-
document.getElementById('btnTerminal').onclick = () => setLayout('terminal-only');
|
|
569
|
-
document.getElementById('btnPreview').onclick = () => setLayout('preview-only');
|
|
570
|
-
document.getElementById('btnSplit').onclick = () => setLayout('split');
|
|
571
792
|
document.getElementById('btnEditor').onclick = () => setLayout('editor');
|
|
793
|
+
document.getElementById('btnAgent').onclick = () => setLayout('agent');
|
|
572
794
|
document.getElementById('btnRefresh').onclick = () => reloadPreview();
|
|
573
795
|
document.getElementById('btnReloadPreview').onclick = () => reloadPreview();
|
|
574
796
|
|
|
575
797
|
// ── Editor module ──
|
|
576
798
|
//
|
|
577
799
|
// Lazy-loads Monaco via cdnjs UMD on first call to ensureLoaded().
|
|
578
|
-
// Cold session (terminal-only flow) never pays the ~5MB download.
|
|
579
800
|
//
|
|
580
801
|
// Owns the open file, dirty state, Ctrl+P palette, Ctrl+S save.
|
|
581
802
|
// All fs operations route through the live WS via fs-read /
|
|
582
803
|
// fs-write / fs-list messages → init.ts handler → SqliteVFS.
|
|
583
804
|
//
|
|
584
|
-
// Perf-invariant: Monaco is
|
|
805
|
+
// Perf-invariant: Monaco is loaded by runtime appending, not by an eager
|
|
806
|
+
// <script src> in the served HTML.
|
|
585
807
|
const Editor = (function() {
|
|
586
808
|
// CDN choice: cdnjs hosts Monaco's UMD AMD-loader (`loader.min.js`)
|
|
587
809
|
// which fetches the remaining vs/* chunks on demand. Total cold
|
|
@@ -1683,14 +1905,667 @@
|
|
|
1683
1905
|
};
|
|
1684
1906
|
})();
|
|
1685
1907
|
|
|
1686
|
-
//
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1908
|
+
// ── Agent chat module ─────────────────────────────────────────────
|
|
1909
|
+
const Agent = (function() {
|
|
1910
|
+
const statusEl = document.getElementById('agentStatus');
|
|
1911
|
+
const subtitleEl = document.getElementById('agentSubtitle');
|
|
1912
|
+
const messagesEl = document.getElementById('agentMessages');
|
|
1913
|
+
const emptyEl = document.getElementById('agentEmpty');
|
|
1914
|
+
const form = document.getElementById('agentForm');
|
|
1915
|
+
const input = document.getElementById('agentInput');
|
|
1916
|
+
const sendBtn = document.getElementById('agentSend');
|
|
1917
|
+
const connectBtn = document.getElementById('agentConnect');
|
|
1918
|
+
const disconnectBtn = document.getElementById('agentDisconnect');
|
|
1919
|
+
const clearBtn = document.getElementById('agentClear');
|
|
1920
|
+
const accountSelect = document.getElementById('agentAccount');
|
|
1921
|
+
|
|
1922
|
+
let loaded = false;
|
|
1923
|
+
let status = null;
|
|
1924
|
+
let messages = [];
|
|
1925
|
+
let busy = false;
|
|
1926
|
+
let currentAbort = null;
|
|
1927
|
+
let oauthPoll = null;
|
|
1928
|
+
let renderSeq = 0;
|
|
1929
|
+
|
|
1930
|
+
function setAgentStatus(text, tone) {
|
|
1931
|
+
statusEl.textContent = text;
|
|
1932
|
+
statusEl.className = 'agent-status' + (tone ? ' ' + tone : '');
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function canStartChat() {
|
|
1936
|
+
return !!status && status.configured && status.connected;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
function applyControls() {
|
|
1940
|
+
const oauth = status?.oauth || {};
|
|
1941
|
+
const owner = status?.ownerToken || {};
|
|
1942
|
+
const connected = !!status?.connected;
|
|
1943
|
+
const canStart = canStartChat();
|
|
1944
|
+
connectBtn.style.display = oauth.configured && !oauth.connected ? 'inline-block' : 'none';
|
|
1945
|
+
disconnectBtn.style.display = oauth.connected ? 'inline-block' : 'none';
|
|
1946
|
+
accountSelect.style.display = oauth.connected && Array.isArray(oauth.accounts) && oauth.accounts.length > 1
|
|
1947
|
+
? 'inline-block'
|
|
1948
|
+
: 'none';
|
|
1949
|
+
input.disabled = !canStart || busy;
|
|
1950
|
+
sendBtn.disabled = !canStart || (!busy && !input.value.trim());
|
|
1951
|
+
sendBtn.textContent = busy ? 'Stop' : 'Send';
|
|
1952
|
+
sendBtn.classList.toggle('streaming', busy);
|
|
1953
|
+
if (!status) {
|
|
1954
|
+
emptyEl.textContent = 'Cloudflare connection required.';
|
|
1955
|
+
subtitleEl.textContent = 'Workspace operator';
|
|
1956
|
+
setAgentStatus('Checking...', '');
|
|
1957
|
+
} else if (!status.configured) {
|
|
1958
|
+
emptyEl.textContent = 'Configure Cloudflare OAuth or an owner API token to enable chat.';
|
|
1959
|
+
subtitleEl.textContent = 'Cloudflare Workers AI';
|
|
1960
|
+
setAgentStatus('AI not configured', 'warn');
|
|
1961
|
+
} else if (connected) {
|
|
1962
|
+
const mode = oauth.connected ? 'Cloudflare connected' : owner.configured ? 'Owner token' : 'Ready';
|
|
1963
|
+
emptyEl.textContent = 'Ask Nimbus to inspect or change this session.';
|
|
1964
|
+
subtitleEl.textContent = 'Shell · files · runtimes · previews';
|
|
1965
|
+
setAgentStatus((busy ? 'Streaming' : mode) + ' · ' + status.model, busy ? 'streaming' : 'ready');
|
|
1966
|
+
} else {
|
|
1967
|
+
emptyEl.textContent = 'Connect Cloudflare to use Workers AI.';
|
|
1968
|
+
subtitleEl.textContent = 'Use your Cloudflare quota';
|
|
1969
|
+
setAgentStatus('Connect Cloudflare', 'warn');
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
function renderAccounts() {
|
|
1974
|
+
const oauth = status?.oauth || {};
|
|
1975
|
+
const accounts = Array.isArray(oauth.accounts) ? oauth.accounts : [];
|
|
1976
|
+
accountSelect.replaceChildren();
|
|
1977
|
+
for (const account of accounts) {
|
|
1978
|
+
const opt = document.createElement('option');
|
|
1979
|
+
opt.value = account.id;
|
|
1980
|
+
opt.textContent = account.name || account.id;
|
|
1981
|
+
opt.selected = account.id === oauth.accountId;
|
|
1982
|
+
accountSelect.appendChild(opt);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
function roleLabel(role, name) {
|
|
1987
|
+
if (role === 'tool') return name || 'tool';
|
|
1988
|
+
return role === 'assistant' ? 'Nimbus' : 'You';
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
function formatTime(ts) {
|
|
1992
|
+
const date = new Date(ts || Date.now());
|
|
1993
|
+
if (Number.isNaN(date.getTime())) return '';
|
|
1994
|
+
return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
function renderMessages(list = messages) {
|
|
1998
|
+
renderSeq++;
|
|
1999
|
+
const seq = renderSeq;
|
|
2000
|
+
const displayList = normalizeTranscriptForRender(list);
|
|
2001
|
+
messagesEl.replaceChildren();
|
|
2002
|
+
if (!displayList || displayList.length === 0) {
|
|
2003
|
+
messagesEl.appendChild(emptyEl);
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
for (const msg of displayList) {
|
|
2007
|
+
messagesEl.appendChild(renderMessage(msg, seq));
|
|
2008
|
+
}
|
|
2009
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
function normalizeTranscriptForRender(list) {
|
|
2013
|
+
if (!Array.isArray(list)) return [];
|
|
2014
|
+
const out = [];
|
|
2015
|
+
let pendingTools = [];
|
|
2016
|
+
const flushPendingTools = () => {
|
|
2017
|
+
for (const tool of pendingTools) {
|
|
2018
|
+
out.push({ id: tool.toolCallId, role: 'assistant', content: '', createdAt: Date.now(), parts: [tool] });
|
|
2019
|
+
}
|
|
2020
|
+
pendingTools = [];
|
|
2021
|
+
};
|
|
2022
|
+
for (const msg of list) {
|
|
2023
|
+
if (msg?.role === 'tool') {
|
|
2024
|
+
pendingTools.push(legacyToolPart(msg));
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
if (msg?.role === 'assistant' && pendingTools.length > 0) {
|
|
2028
|
+
const next = { ...msg, parts: [...pendingTools, ...normalizeTurnParts(msg)] };
|
|
2029
|
+
pendingTools = [];
|
|
2030
|
+
out.push(next);
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
flushPendingTools();
|
|
2034
|
+
out.push(msg);
|
|
2035
|
+
}
|
|
2036
|
+
flushPendingTools();
|
|
2037
|
+
return out;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
function renderMessage(msg, seq) {
|
|
2041
|
+
if (msg.role === 'tool') return renderStoredToolMessage(msg);
|
|
2042
|
+
|
|
2043
|
+
const row = document.createElement('div');
|
|
2044
|
+
row.className = 'agent-msg ' + msg.role;
|
|
2045
|
+
const avatar = document.createElement('div');
|
|
2046
|
+
avatar.className = 'agent-avatar';
|
|
2047
|
+
avatar.textContent = msg.role === 'assistant' ? 'N' : '';
|
|
2048
|
+
const content = document.createElement('div');
|
|
2049
|
+
content.className = 'agent-content';
|
|
2050
|
+
|
|
2051
|
+
const meta = document.createElement('div');
|
|
2052
|
+
meta.className = 'agent-meta';
|
|
2053
|
+
meta.textContent = roleLabel(msg.role, msg.name) + (msg.createdAt ? ' · ' + formatTime(msg.createdAt) : '');
|
|
2054
|
+
|
|
2055
|
+
const isStreaming = !!msg.streaming;
|
|
2056
|
+
|
|
2057
|
+
if (msg.role === 'assistant') content.appendChild(meta);
|
|
2058
|
+
|
|
2059
|
+
if (msg.role === 'assistant') {
|
|
2060
|
+
const parts = normalizeTurnParts(msg);
|
|
2061
|
+
if (msg.error) {
|
|
2062
|
+
const bubble = document.createElement('div');
|
|
2063
|
+
bubble.className = 'agent-bubble';
|
|
2064
|
+
bubble.textContent = msg.error;
|
|
2065
|
+
content.appendChild(bubble);
|
|
2066
|
+
} else if (parts.length > 0) {
|
|
2067
|
+
renderTurnParts(content, parts, { streaming: isStreaming, seq });
|
|
2068
|
+
} else if (isStreaming) {
|
|
2069
|
+
content.appendChild(renderThinking('Thinking'));
|
|
2070
|
+
}
|
|
2071
|
+
} else if (msg.content || msg.error) {
|
|
2072
|
+
const bubble = document.createElement('div');
|
|
2073
|
+
bubble.className = 'agent-bubble';
|
|
2074
|
+
if (msg.error) {
|
|
2075
|
+
bubble.textContent = msg.error;
|
|
2076
|
+
} else {
|
|
2077
|
+
bubble.textContent = String(msg.content || '');
|
|
2078
|
+
}
|
|
2079
|
+
content.appendChild(bubble);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
if (msg.role === 'user') {
|
|
2083
|
+
content.appendChild(meta);
|
|
2084
|
+
row.append(content);
|
|
2085
|
+
} else {
|
|
2086
|
+
row.append(avatar, content);
|
|
2087
|
+
}
|
|
2088
|
+
return row;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
function normalizeTurnParts(msg) {
|
|
2092
|
+
if (Array.isArray(msg.parts)) return msg.parts.filter((part) => part && typeof part === 'object');
|
|
2093
|
+
const parts = [];
|
|
2094
|
+
if (msg.reasoning) parts.push({ type: 'reasoning', text: String(msg.reasoning) });
|
|
2095
|
+
if (Array.isArray(msg.tools)) {
|
|
2096
|
+
for (const tool of msg.tools) parts.push({ type: 'tool', ...tool });
|
|
2097
|
+
}
|
|
2098
|
+
if (msg.content) parts.push({ type: 'text', text: String(msg.content) });
|
|
2099
|
+
return parts;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
function renderTurnParts(content, parts, opts = {}) {
|
|
2103
|
+
const lastTextIndex = findLastTextPartIndex(parts);
|
|
2104
|
+
parts.forEach((part, index) => {
|
|
2105
|
+
if (part.type === 'reasoning') {
|
|
2106
|
+
if (part.text) content.appendChild(renderReasoning(part.text));
|
|
2107
|
+
} else if (part.type === 'tool') {
|
|
2108
|
+
content.appendChild(renderToolCard(part));
|
|
2109
|
+
} else if (part.type === 'text' && part.text) {
|
|
2110
|
+
const bubble = document.createElement('div');
|
|
2111
|
+
bubble.className = 'agent-bubble';
|
|
2112
|
+
renderMarkdownInto(bubble, part.text, {
|
|
2113
|
+
cursor: !!opts.streaming && index === lastTextIndex,
|
|
2114
|
+
seq: opts.seq,
|
|
2115
|
+
});
|
|
2116
|
+
content.appendChild(bubble);
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
if (opts.streaming && (parts.length === 0 || shouldShowPostPartThinking(parts))) {
|
|
2120
|
+
content.appendChild(renderThinking('Thinking'));
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
function findLastTextPartIndex(parts) {
|
|
2125
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
2126
|
+
if (parts[i]?.type === 'text' && parts[i].text) return i;
|
|
2127
|
+
}
|
|
2128
|
+
return -1;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
function shouldShowPostPartThinking(parts) {
|
|
2132
|
+
const last = parts[parts.length - 1];
|
|
2133
|
+
return !!last && last.type === 'tool' && last.status !== 'running';
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
function renderStoredToolMessage(msg) {
|
|
2137
|
+
const row = document.createElement('div');
|
|
2138
|
+
row.className = 'agent-msg assistant';
|
|
2139
|
+
const avatar = document.createElement('div');
|
|
2140
|
+
avatar.className = 'agent-avatar';
|
|
2141
|
+
avatar.textContent = 'T';
|
|
2142
|
+
const content = document.createElement('div');
|
|
2143
|
+
content.className = 'agent-content';
|
|
2144
|
+
content.appendChild(renderToolCard(legacyToolPart(msg)));
|
|
2145
|
+
row.append(avatar, content);
|
|
2146
|
+
return row;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
function legacyToolPart(msg) {
|
|
2150
|
+
const payload = parseToolPayload(msg);
|
|
2151
|
+
const output = payload.output ?? msg.content;
|
|
2152
|
+
return {
|
|
2153
|
+
type: 'tool',
|
|
2154
|
+
toolCallId: msg.id,
|
|
2155
|
+
toolName: payload.tool || msg.name || 'tool',
|
|
2156
|
+
input: payload.input,
|
|
2157
|
+
output,
|
|
2158
|
+
status: isToolOutputFailure(output) ? 'error' : 'done',
|
|
2159
|
+
durationMs: payload.durationMs,
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
function parseToolPayload(msg) {
|
|
2164
|
+
try {
|
|
2165
|
+
const parsed = JSON.parse(msg.content || '{}');
|
|
2166
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
2167
|
+
} catch {}
|
|
2168
|
+
return { tool: msg.name || 'tool', output: msg.content || '' };
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
function renderThinking(label) {
|
|
2172
|
+
const wrap = document.createElement('div');
|
|
2173
|
+
wrap.className = 'agent-thinking';
|
|
2174
|
+
const dots = document.createElement('span');
|
|
2175
|
+
dots.className = 'dots';
|
|
2176
|
+
dots.append(document.createElement('span'), document.createElement('span'), document.createElement('span'));
|
|
2177
|
+
const text = document.createElement('span');
|
|
2178
|
+
text.textContent = label;
|
|
2179
|
+
wrap.append(dots, text);
|
|
2180
|
+
return wrap;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
function renderReasoning(text) {
|
|
2184
|
+
const details = document.createElement('details');
|
|
2185
|
+
details.className = 'agent-reasoning';
|
|
2186
|
+
const summary = document.createElement('summary');
|
|
2187
|
+
summary.textContent = 'Thinking';
|
|
2188
|
+
const pre = document.createElement('pre');
|
|
2189
|
+
pre.textContent = text;
|
|
2190
|
+
details.append(summary, pre);
|
|
2191
|
+
return details;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
function renderToolCard(tool) {
|
|
2195
|
+
const status = tool.status || 'done';
|
|
2196
|
+
const details = document.createElement('details');
|
|
2197
|
+
details.className = 'agent-tool ' + status;
|
|
2198
|
+
if (status === 'running' || status === 'error') details.open = true;
|
|
2199
|
+
|
|
2200
|
+
const summary = document.createElement('summary');
|
|
2201
|
+
const dot = document.createElement('span');
|
|
2202
|
+
dot.className = 'tool-dot';
|
|
2203
|
+
const name = document.createElement('span');
|
|
2204
|
+
name.className = 'tool-name';
|
|
2205
|
+
name.textContent = tool.toolName || 'tool';
|
|
2206
|
+
const state = document.createElement('span');
|
|
2207
|
+
state.className = 'tool-state';
|
|
2208
|
+
state.textContent = toolStateLabel(tool);
|
|
2209
|
+
summary.append(dot, name, state);
|
|
2210
|
+
|
|
2211
|
+
const body = document.createElement('div');
|
|
2212
|
+
body.className = 'agent-tool-body';
|
|
2213
|
+
if (tool.input !== undefined) body.appendChild(renderToolSection('Input', tool.input));
|
|
2214
|
+
if (tool.output !== undefined) body.appendChild(renderToolSection(status === 'error' ? 'Error' : 'Output', tool.output));
|
|
2215
|
+
if (tool.error) body.appendChild(renderToolSection('Error', tool.error));
|
|
2216
|
+
details.append(summary, body);
|
|
2217
|
+
return details;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
function toolStateLabel(tool) {
|
|
2221
|
+
if (tool.status === 'running') return 'running';
|
|
2222
|
+
if (tool.status === 'error') return tool.durationMs ? 'failed · ' + formatDuration(tool.durationMs) : 'failed';
|
|
2223
|
+
return tool.durationMs ? 'done · ' + formatDuration(tool.durationMs) : 'done';
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function formatDuration(ms) {
|
|
2227
|
+
if (!Number.isFinite(ms) || ms < 100) return '<0.1s';
|
|
2228
|
+
return (ms / 1000).toFixed(1) + 's';
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
function renderToolSection(label, value) {
|
|
2232
|
+
const section = document.createElement('div');
|
|
2233
|
+
const title = document.createElement('label');
|
|
2234
|
+
title.textContent = label;
|
|
2235
|
+
const pre = document.createElement('pre');
|
|
2236
|
+
pre.textContent = prettyValue(value);
|
|
2237
|
+
section.append(title, pre);
|
|
2238
|
+
return section;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
function prettyValue(value) {
|
|
2242
|
+
if (typeof value === 'string') return value;
|
|
2243
|
+
try { return JSON.stringify(value, null, 2); } catch { return String(value); }
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function isToolOutputFailure(output) {
|
|
2247
|
+
if (!output || typeof output !== 'object') return false;
|
|
2248
|
+
if (typeof output.error === 'string' && output.error.trim()) return true;
|
|
2249
|
+
if (output.success === false) return true;
|
|
2250
|
+
if (typeof output.exitCode === 'number' && output.exitCode !== 0) return true;
|
|
2251
|
+
if (output.exit && typeof output.exit === 'object' && typeof output.exit.code === 'number') {
|
|
2252
|
+
return output.exit.code !== 0;
|
|
2253
|
+
}
|
|
2254
|
+
return false;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
function renderMarkdownInto(el, text, opts = {}) {
|
|
2258
|
+
const token = String((opts.seq || renderSeq) + ':' + text.length + ':' + (opts.cursor ? '1' : '0'));
|
|
2259
|
+
el.dataset.renderToken = token;
|
|
2260
|
+
el.textContent = text;
|
|
2261
|
+
renderMarkdown(text).then((html) => {
|
|
2262
|
+
if (el.dataset.renderToken !== token) return;
|
|
2263
|
+
el.innerHTML = html;
|
|
2264
|
+
if (opts.cursor) {
|
|
2265
|
+
const cursor = document.createElement('span');
|
|
2266
|
+
cursor.className = 'agent-cursor';
|
|
2267
|
+
el.appendChild(cursor);
|
|
2268
|
+
}
|
|
2269
|
+
}).catch(() => {
|
|
2270
|
+
if (opts.cursor) {
|
|
2271
|
+
const cursor = document.createElement('span');
|
|
2272
|
+
cursor.className = 'agent-cursor';
|
|
2273
|
+
el.appendChild(cursor);
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
async function refreshStatus() {
|
|
2279
|
+
const r = await fetch(AGENT_STATUS_URL, { headers: { Accept: 'application/json' } });
|
|
2280
|
+
const payload = await r.json();
|
|
2281
|
+
if (!r.ok) throw new Error(payload.error || 'Agent status failed');
|
|
2282
|
+
status = payload;
|
|
2283
|
+
renderAccounts();
|
|
2284
|
+
applyControls();
|
|
2285
|
+
return status;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
async function loadMessages() {
|
|
2289
|
+
const r = await fetch(AGENT_MESSAGES_URL, { headers: { Accept: 'application/json' } });
|
|
2290
|
+
const payload = await r.json();
|
|
2291
|
+
if (!r.ok) throw new Error(payload.error || 'Agent messages failed');
|
|
2292
|
+
messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
2293
|
+
renderMessages();
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
async function connectCloudflare() {
|
|
2297
|
+
connectBtn.disabled = true;
|
|
2298
|
+
try {
|
|
2299
|
+
const r = await fetch(AGENT_OAUTH_START_URL, { method: 'POST', headers: { Accept: 'application/json' } });
|
|
2300
|
+
const payload = await r.json();
|
|
2301
|
+
if (!r.ok || !payload.authUrl) {
|
|
2302
|
+
throw new Error(payload.error || 'OAuth start failed');
|
|
2303
|
+
}
|
|
2304
|
+
const popup = window.open(payload.authUrl, 'nimbus-agent-oauth', 'width=720,height=760');
|
|
2305
|
+
if (!popup) location.href = payload.authUrl;
|
|
2306
|
+
setAgentStatus('Waiting for Cloudflare...', 'warn');
|
|
2307
|
+
if (oauthPoll) clearInterval(oauthPoll);
|
|
2308
|
+
oauthPoll = setInterval(() => {
|
|
2309
|
+
if (!popup || popup.closed) {
|
|
2310
|
+
clearInterval(oauthPoll);
|
|
2311
|
+
oauthPoll = null;
|
|
2312
|
+
refreshStatus().catch((e) => setAgentStatus(e?.message || String(e), 'warn'));
|
|
2313
|
+
}
|
|
2314
|
+
}, 1200);
|
|
2315
|
+
} catch (e) {
|
|
2316
|
+
setAgentStatus(e?.message || String(e), 'warn');
|
|
2317
|
+
} finally {
|
|
2318
|
+
connectBtn.disabled = false;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
async function selectAccount() {
|
|
2323
|
+
const accountId = accountSelect.value;
|
|
2324
|
+
if (!accountId) return;
|
|
2325
|
+
await fetch(AGENT_ACCOUNT_URL, {
|
|
2326
|
+
method: 'POST',
|
|
2327
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
2328
|
+
body: JSON.stringify({ accountId }),
|
|
2329
|
+
}).then(async (r) => {
|
|
2330
|
+
const payload = await r.json().catch(() => ({}));
|
|
2331
|
+
if (!r.ok) throw new Error(payload.error || 'Account switch failed');
|
|
2332
|
+
});
|
|
2333
|
+
await refreshStatus();
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
async function disconnectCloudflare() {
|
|
2337
|
+
disconnectBtn.disabled = true;
|
|
2338
|
+
try {
|
|
2339
|
+
const r = await fetch(AGENT_OAUTH_LOGOUT_URL, { method: 'POST', headers: { Accept: 'application/json' } });
|
|
2340
|
+
const payload = await r.json().catch(() => ({}));
|
|
2341
|
+
if (!r.ok) throw new Error(payload.error || 'Disconnect failed');
|
|
2342
|
+
status = null;
|
|
2343
|
+
await refreshStatus();
|
|
2344
|
+
} catch (e) {
|
|
2345
|
+
setAgentStatus(e?.message || String(e), 'warn');
|
|
2346
|
+
} finally {
|
|
2347
|
+
disconnectBtn.disabled = false;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
async function clearMessages() {
|
|
2352
|
+
const r = await fetch(AGENT_MESSAGES_URL, { method: 'DELETE', headers: { Accept: 'application/json' } });
|
|
2353
|
+
const payload = await r.json().catch(() => ({}));
|
|
2354
|
+
if (!r.ok) {
|
|
2355
|
+
setAgentStatus(payload.error || 'Clear failed', 'warn');
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
messages = [];
|
|
2359
|
+
renderMessages();
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
async function sendMessage(text) {
|
|
2363
|
+
const content = String(text || '').trim();
|
|
2364
|
+
if (busy) {
|
|
2365
|
+
stopStreaming();
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
if (!content) return;
|
|
2369
|
+
busy = true;
|
|
2370
|
+
currentAbort = new AbortController();
|
|
2371
|
+
applyControls();
|
|
2372
|
+
input.value = '';
|
|
2373
|
+
const optimistic = {
|
|
2374
|
+
id: 'pending-user',
|
|
2375
|
+
role: 'user',
|
|
2376
|
+
content,
|
|
2377
|
+
createdAt: Date.now(),
|
|
2378
|
+
};
|
|
2379
|
+
const pending = {
|
|
2380
|
+
id: 'pending-assistant',
|
|
2381
|
+
role: 'assistant',
|
|
2382
|
+
content: '',
|
|
2383
|
+
createdAt: Date.now(),
|
|
2384
|
+
streaming: true,
|
|
2385
|
+
parts: [],
|
|
2386
|
+
};
|
|
2387
|
+
let liveList = [...messages, optimistic, pending];
|
|
2388
|
+
renderMessages(liveList);
|
|
2389
|
+
const repaint = () => {
|
|
2390
|
+
liveList = [...messages, pending];
|
|
2391
|
+
renderMessages(liveList);
|
|
2392
|
+
};
|
|
2393
|
+
try {
|
|
2394
|
+
const r = await fetch(AGENT_MESSAGES_URL, {
|
|
2395
|
+
method: 'POST',
|
|
2396
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/x-ndjson' },
|
|
2397
|
+
body: JSON.stringify({ message: content, stream: true }),
|
|
2398
|
+
signal: currentAbort.signal,
|
|
2399
|
+
});
|
|
2400
|
+
if (!r.ok || !r.body) {
|
|
2401
|
+
const payload = await r.json().catch(() => ({}));
|
|
2402
|
+
throw new Error(payload.error || 'Agent request failed');
|
|
2403
|
+
}
|
|
2404
|
+
await readAgentStream(r.body, pending, repaint);
|
|
2405
|
+
} catch (e) {
|
|
2406
|
+
if (e?.name !== 'AbortError') {
|
|
2407
|
+
pending.streaming = false;
|
|
2408
|
+
pending.error = e?.message || String(e);
|
|
2409
|
+
repaint();
|
|
2410
|
+
}
|
|
2411
|
+
} finally {
|
|
2412
|
+
busy = false;
|
|
2413
|
+
currentAbort = null;
|
|
2414
|
+
applyControls();
|
|
2415
|
+
input.focus();
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
function stopStreaming() {
|
|
2420
|
+
if (currentAbort) currentAbort.abort();
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
async function readAgentStream(body, liveAssistant, repaint) {
|
|
2424
|
+
const reader = body.getReader();
|
|
2425
|
+
const decoder = new TextDecoder();
|
|
2426
|
+
let buffer = '';
|
|
2427
|
+
while (true) {
|
|
2428
|
+
const { value, done } = await reader.read();
|
|
2429
|
+
if (done) break;
|
|
2430
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2431
|
+
let lineEnd = buffer.indexOf('\n');
|
|
2432
|
+
while (lineEnd >= 0) {
|
|
2433
|
+
const line = buffer.slice(0, lineEnd).trim();
|
|
2434
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
2435
|
+
if (line) applyStreamEvent(JSON.parse(line), liveAssistant, repaint);
|
|
2436
|
+
lineEnd = buffer.indexOf('\n');
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
const tail = buffer.trim();
|
|
2440
|
+
if (tail) applyStreamEvent(JSON.parse(tail), liveAssistant, repaint);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
function applyStreamEvent(event, liveAssistant, repaint) {
|
|
2444
|
+
if (!event || typeof event !== 'object') return;
|
|
2445
|
+
if (event.type === 'start') {
|
|
2446
|
+
if (Array.isArray(event.messages)) messages = event.messages;
|
|
2447
|
+
} else if (event.type === 'assistant-start') {
|
|
2448
|
+
liveAssistant.id = event.messageId || liveAssistant.id;
|
|
2449
|
+
liveAssistant.createdAt = event.createdAt || liveAssistant.createdAt;
|
|
2450
|
+
} else if (event.type === 'text-delta') {
|
|
2451
|
+
const delta = String(event.delta || '');
|
|
2452
|
+
liveAssistant.content += delta;
|
|
2453
|
+
appendLiveTextPart(liveAssistant, 'text', delta);
|
|
2454
|
+
} else if (event.type === 'reasoning-delta') {
|
|
2455
|
+
appendLiveTextPart(liveAssistant, 'reasoning', String(event.delta || ''));
|
|
2456
|
+
} else if (event.type === 'tool-call') {
|
|
2457
|
+
upsertLiveTool(liveAssistant, {
|
|
2458
|
+
toolCallId: event.toolCallId,
|
|
2459
|
+
toolName: event.toolName,
|
|
2460
|
+
input: event.input,
|
|
2461
|
+
status: 'running',
|
|
2462
|
+
startedAt: Date.now(),
|
|
2463
|
+
});
|
|
2464
|
+
} else if (event.type === 'tool-result' || event.type === 'tool-error') {
|
|
2465
|
+
const tool = upsertLiveTool(liveAssistant, {
|
|
2466
|
+
toolCallId: event.toolCallId,
|
|
2467
|
+
toolName: event.toolName,
|
|
2468
|
+
input: event.input,
|
|
2469
|
+
});
|
|
2470
|
+
tool.status = event.type === 'tool-error' ? 'error' : (event.status || 'done');
|
|
2471
|
+
tool.output = event.type === 'tool-error' ? { error: event.error } : event.output;
|
|
2472
|
+
tool.error = event.type === 'tool-error' ? event.error : '';
|
|
2473
|
+
if (tool.startedAt) tool.durationMs = Date.now() - tool.startedAt;
|
|
2474
|
+
} else if (event.type === 'done') {
|
|
2475
|
+
liveAssistant.streaming = false;
|
|
2476
|
+
if (Array.isArray(event.messages)) {
|
|
2477
|
+
messages = event.messages;
|
|
2478
|
+
renderMessages();
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
if (event.message) {
|
|
2482
|
+
messages = [...messages, event.message];
|
|
2483
|
+
}
|
|
2484
|
+
} else if (event.type === 'error') {
|
|
2485
|
+
liveAssistant.streaming = false;
|
|
2486
|
+
liveAssistant.error = event.error || 'Agent request failed';
|
|
2487
|
+
if (Array.isArray(event.messages)) messages = event.messages;
|
|
2488
|
+
setAgentStatus(liveAssistant.error, 'warn');
|
|
2489
|
+
}
|
|
2490
|
+
repaint();
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
function appendLiveTextPart(liveAssistant, type, delta) {
|
|
2494
|
+
if (!delta) return;
|
|
2495
|
+
if (!Array.isArray(liveAssistant.parts)) liveAssistant.parts = [];
|
|
2496
|
+
const last = liveAssistant.parts[liveAssistant.parts.length - 1];
|
|
2497
|
+
if (last && last.type === type) {
|
|
2498
|
+
last.text = String(last.text || '') + delta;
|
|
2499
|
+
} else {
|
|
2500
|
+
liveAssistant.parts.push({ type, text: delta });
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
function upsertLiveTool(liveAssistant, patch) {
|
|
2505
|
+
if (!Array.isArray(liveAssistant.parts)) liveAssistant.parts = [];
|
|
2506
|
+
let tool = liveAssistant.parts.find((item) => item.type === 'tool' && item.toolCallId === patch.toolCallId);
|
|
2507
|
+
if (!tool) {
|
|
2508
|
+
tool = { type: 'tool', status: 'running' };
|
|
2509
|
+
liveAssistant.parts.push(tool);
|
|
2510
|
+
}
|
|
2511
|
+
Object.assign(tool, patch);
|
|
2512
|
+
return tool;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
function wire() {
|
|
2516
|
+
form.addEventListener('submit', (e) => {
|
|
2517
|
+
e.preventDefault();
|
|
2518
|
+
if (busy) stopStreaming();
|
|
2519
|
+
else sendMessage(input.value);
|
|
2520
|
+
});
|
|
2521
|
+
input.addEventListener('keydown', (e) => {
|
|
2522
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
2523
|
+
e.preventDefault();
|
|
2524
|
+
sendMessage(input.value);
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
input.addEventListener('input', applyControls);
|
|
2528
|
+
connectBtn.addEventListener('click', connectCloudflare);
|
|
2529
|
+
disconnectBtn.addEventListener('click', disconnectCloudflare);
|
|
2530
|
+
clearBtn.addEventListener('click', clearMessages);
|
|
2531
|
+
accountSelect.addEventListener('change', selectAccount);
|
|
2532
|
+
window.addEventListener('message', (event) => {
|
|
2533
|
+
if (event.origin !== location.origin) return;
|
|
2534
|
+
if (event.data && event.data.type === 'nimbus-agent-oauth') {
|
|
2535
|
+
if (oauthPoll) {
|
|
2536
|
+
clearInterval(oauthPoll);
|
|
2537
|
+
oauthPoll = null;
|
|
2538
|
+
}
|
|
2539
|
+
refreshStatus().catch(() => {});
|
|
2540
|
+
}
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
async function ensureLoaded() {
|
|
2545
|
+
if (!loaded) {
|
|
2546
|
+
loaded = true;
|
|
2547
|
+
wire();
|
|
2548
|
+
}
|
|
2549
|
+
const results = await Promise.allSettled([refreshStatus(), loadMessages()]);
|
|
2550
|
+
if (results[0].status === 'rejected') {
|
|
2551
|
+
setAgentStatus(results[0].reason?.message || String(results[0].reason), 'warn');
|
|
2552
|
+
input.disabled = true;
|
|
2553
|
+
sendBtn.disabled = true;
|
|
2554
|
+
}
|
|
2555
|
+
if (results[1].status === 'rejected') renderMessages([]);
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
const api = { ensureLoaded, refreshStatus, loadMessages };
|
|
2559
|
+
window.NimbusAgent = api;
|
|
2560
|
+
return api;
|
|
2561
|
+
})();
|
|
2562
|
+
|
|
2563
|
+
// Tabbed preview surface. Markdown preview and live app previews share
|
|
2564
|
+
// the right pane without fighting over a single iframe.
|
|
2565
|
+
let previewBackendKind = 'none'; // 'vite' | 'worker' | 'none'
|
|
2566
|
+
let activePreviewTabId = null;
|
|
2567
|
+
let markdownRenderSeq = 0;
|
|
2568
|
+
const previewTabs = new Map();
|
|
1694
2569
|
let markdownRendererPromise = null;
|
|
1695
2570
|
let markdownRenderer = null;
|
|
1696
2571
|
|
|
@@ -1698,32 +2573,199 @@
|
|
|
1698
2573
|
return kind === 'worker' ? WORKER_URL : VITE_URL;
|
|
1699
2574
|
}
|
|
1700
2575
|
|
|
2576
|
+
function portPreviewUrl(port) {
|
|
2577
|
+
return SESSION_PREFIX + '/port/' + Number(port) + '/';
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
function absolutePreviewUrl(url) {
|
|
2581
|
+
return new URL(url, location.origin).href;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
function shortPathLabel(path) {
|
|
2585
|
+
const parts = String(path || '').split('/').filter(Boolean);
|
|
2586
|
+
return parts[parts.length - 1] || 'Markdown';
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
function appTabFor(kind) {
|
|
2590
|
+
const appKind = kind === 'worker' ? 'worker' : 'vite';
|
|
2591
|
+
return {
|
|
2592
|
+
id: 'app:' + appKind,
|
|
2593
|
+
type: 'app',
|
|
2594
|
+
kind: appKind,
|
|
2595
|
+
label: appKind === 'worker' ? 'Worker' : 'Preview',
|
|
2596
|
+
title: appKind === 'worker' ? 'Cloudflare Worker preview' : 'Vite preview',
|
|
2597
|
+
url: previewUrlFor(appKind),
|
|
2598
|
+
running: previewBackendKind === appKind,
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
function renderPreviewTabs() {
|
|
2603
|
+
if (!previewTabsEl) return;
|
|
2604
|
+
previewTabsEl.innerHTML = '';
|
|
2605
|
+
for (const tab of previewTabs.values()) {
|
|
2606
|
+
const button = document.createElement('button');
|
|
2607
|
+
button.type = 'button';
|
|
2608
|
+
button.className = [
|
|
2609
|
+
'preview-tab',
|
|
2610
|
+
tab.type === 'markdown' ? 'markdown' : '',
|
|
2611
|
+
tab.running ? 'running' : '',
|
|
2612
|
+
tab.id === activePreviewTabId ? 'active' : '',
|
|
2613
|
+
].filter(Boolean).join(' ');
|
|
2614
|
+
button.title = tab.title || tab.url || tab.path || tab.label;
|
|
2615
|
+
button.onclick = () => activatePreviewTab(tab.id);
|
|
2616
|
+
|
|
2617
|
+
const label = document.createElement('span');
|
|
2618
|
+
label.className = 'preview-tab-label';
|
|
2619
|
+
label.textContent = tab.label;
|
|
2620
|
+
button.appendChild(label);
|
|
2621
|
+
|
|
2622
|
+
previewTabsEl.appendChild(button);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
function ensurePreviewTab(tab, options = {}) {
|
|
2627
|
+
const existing = previewTabs.get(tab.id);
|
|
2628
|
+
const merged = {
|
|
2629
|
+
...(existing || {}),
|
|
2630
|
+
...tab,
|
|
2631
|
+
createdAt: existing?.createdAt || Date.now(),
|
|
2632
|
+
updatedAt: Date.now(),
|
|
2633
|
+
};
|
|
2634
|
+
previewTabs.set(tab.id, merged);
|
|
2635
|
+
if (!existing && options.focusNew) {
|
|
2636
|
+
activatePreviewTab(tab.id);
|
|
2637
|
+
} else {
|
|
2638
|
+
renderPreviewTabs();
|
|
2639
|
+
}
|
|
2640
|
+
return merged;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
function firstAppTabId() {
|
|
2644
|
+
const port = [...previewTabs.values()].filter((tab) => tab.type === 'port').pop();
|
|
2645
|
+
if (port) return port.id;
|
|
2646
|
+
const preferred = previewBackendKind === 'worker' ? 'app:worker' : 'app:vite';
|
|
2647
|
+
if (previewTabs.has(preferred)) return preferred;
|
|
2648
|
+
if (previewTabs.has('app:vite')) return 'app:vite';
|
|
2649
|
+
if (previewTabs.has('app:worker')) return 'app:worker';
|
|
2650
|
+
return ensurePreviewTab(appTabFor('vite')).id;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
function activatePreviewTab(id) {
|
|
2654
|
+
const tab = previewTabs.get(id);
|
|
2655
|
+
if (!tab) return;
|
|
2656
|
+
activePreviewTabId = id;
|
|
2657
|
+
renderPreviewTabs();
|
|
2658
|
+
if (tab.type === 'markdown') {
|
|
2659
|
+
previewPanel.classList.add('markdown-mode');
|
|
2660
|
+
if (previewFrame) previewFrame.style.display = 'none';
|
|
2661
|
+
if (markdownPreview) markdownPreview.classList.add('active');
|
|
2662
|
+
setPreviewUrlBar('Markdown Preview · ' + tab.path);
|
|
2663
|
+
paintMarkdownPreview(tab.markdown || '').catch(() => {});
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
previewPanel.classList.remove('markdown-mode');
|
|
2668
|
+
if (markdownPreview) markdownPreview.classList.remove('active');
|
|
2669
|
+
if (previewFrame) {
|
|
2670
|
+
previewFrame.style.display = 'block';
|
|
2671
|
+
const next = absolutePreviewUrl(tab.url);
|
|
2672
|
+
if (previewFrame.src !== next) previewFrame.src = tab.url;
|
|
2673
|
+
}
|
|
2674
|
+
setPreviewUrlBar(tab.url);
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
function normalizePortStats(portStats) {
|
|
2678
|
+
const raw = Array.isArray(portStats?.ports)
|
|
2679
|
+
? portStats.ports
|
|
2680
|
+
: Array.isArray(portStats)
|
|
2681
|
+
? portStats
|
|
2682
|
+
: [];
|
|
2683
|
+
const seen = new Set();
|
|
2684
|
+
const ports = [];
|
|
2685
|
+
for (const entry of raw) {
|
|
2686
|
+
const port = Number(entry?.port);
|
|
2687
|
+
if (!Number.isInteger(port) || port <= 0 || seen.has(port)) continue;
|
|
2688
|
+
seen.add(port);
|
|
2689
|
+
ports.push({ port, pid: Number(entry?.pid) || 0 });
|
|
2690
|
+
}
|
|
2691
|
+
return ports;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
function syncPortPreviewTabs(portStats, canonicalPort, canonicalTabId) {
|
|
2695
|
+
const ports = normalizePortStats(portStats);
|
|
2696
|
+
const hiddenPort = Number.isInteger(canonicalPort) && canonicalPort > 0 ? canonicalPort : null;
|
|
2697
|
+
const visiblePorts = hiddenPort == null
|
|
2698
|
+
? ports
|
|
2699
|
+
: ports.filter((p) => p.port !== hiddenPort);
|
|
2700
|
+
const live = new Set(visiblePorts.map((p) => 'port:' + p.port));
|
|
2701
|
+
let activeWasRemoved = false;
|
|
2702
|
+
let activeWasCanonicalAlias = false;
|
|
2703
|
+
|
|
2704
|
+
for (const [id, tab] of previewTabs) {
|
|
2705
|
+
if (tab.type === 'port' && !live.has(id)) {
|
|
2706
|
+
previewTabs.delete(id);
|
|
2707
|
+
if (activePreviewTabId === id) {
|
|
2708
|
+
activeWasRemoved = true;
|
|
2709
|
+
activeWasCanonicalAlias = hiddenPort != null && tab.port === hiddenPort;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
for (const entry of visiblePorts) {
|
|
2715
|
+
const id = 'port:' + entry.port;
|
|
2716
|
+
const existed = previewTabs.has(id);
|
|
2717
|
+
ensurePreviewTab({
|
|
2718
|
+
id,
|
|
2719
|
+
type: 'port',
|
|
2720
|
+
port: entry.port,
|
|
2721
|
+
pid: entry.pid,
|
|
2722
|
+
label: ':' + entry.port,
|
|
2723
|
+
title: 'Port ' + entry.port,
|
|
2724
|
+
url: portPreviewUrl(entry.port),
|
|
2725
|
+
running: true,
|
|
2726
|
+
}, { focusNew: !existed });
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
if (activeWasRemoved || !activePreviewTabId || !previewTabs.has(activePreviewTabId)) {
|
|
2730
|
+
activatePreviewTab(activeWasCanonicalAlias && canonicalTabId ? canonicalTabId : firstAppTabId());
|
|
2731
|
+
} else {
|
|
2732
|
+
renderPreviewTabs();
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
|
|
1701
2736
|
function setPreviewUrlBar(value) {
|
|
1702
2737
|
const urlBar = document.getElementById('urlBar');
|
|
1703
2738
|
if (urlBar) urlBar.value = value;
|
|
1704
2739
|
}
|
|
1705
2740
|
|
|
1706
2741
|
function showAppPreview() {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
if (previewFrame) previewFrame.style.display = 'block';
|
|
1710
|
-
previewPanel.classList.remove('markdown-mode');
|
|
1711
|
-
const kind = (activePreview === null || activePreview === 'none') ? 'vite' : activePreview;
|
|
1712
|
-
const url = previewUrlFor(kind);
|
|
1713
|
-
if (previewFrame && (previewFrame.src === 'about:blank' || !previewFrame.src.includes(url))) {
|
|
1714
|
-
previewFrame.src = url;
|
|
1715
|
-
}
|
|
1716
|
-
setPreviewUrlBar(url);
|
|
2742
|
+
ensurePreviewTab(appTabFor(previewBackendKind));
|
|
2743
|
+
activatePreviewTab(firstAppTabId());
|
|
1717
2744
|
}
|
|
1718
2745
|
|
|
1719
2746
|
async function showMarkdownPreview(path, markdown) {
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2747
|
+
const id = 'markdown:' + path;
|
|
2748
|
+
ensurePreviewTab({
|
|
2749
|
+
id,
|
|
2750
|
+
type: 'markdown',
|
|
2751
|
+
label: shortPathLabel(path),
|
|
2752
|
+
title: path,
|
|
2753
|
+
path,
|
|
2754
|
+
markdown: markdown || '',
|
|
2755
|
+
running: false,
|
|
2756
|
+
});
|
|
2757
|
+
activatePreviewTab(id);
|
|
2758
|
+
if (activePreviewTabId !== id) return;
|
|
2759
|
+
await paintMarkdownPreview(markdown || '');
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
async function paintMarkdownPreview(markdown) {
|
|
2763
|
+
const seq = ++markdownRenderSeq;
|
|
1725
2764
|
const html = await renderMarkdown(markdown || '');
|
|
1726
|
-
|
|
2765
|
+
const active = activePreviewTabId ? previewTabs.get(activePreviewTabId) : null;
|
|
2766
|
+
if (seq === markdownRenderSeq && active?.type === 'markdown' && markdownPreviewBody) {
|
|
2767
|
+
markdownPreviewBody.innerHTML = html;
|
|
2768
|
+
}
|
|
1727
2769
|
}
|
|
1728
2770
|
|
|
1729
2771
|
async function renderMarkdown(markdown) {
|
|
@@ -1763,48 +2805,47 @@
|
|
|
1763
2805
|
return div.innerHTML;
|
|
1764
2806
|
}
|
|
1765
2807
|
|
|
1766
|
-
function
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
if (kind !== 'none' && layout === 'terminal-only') setLayout('split');
|
|
2808
|
+
function previewBackendPort(kind, stats) {
|
|
2809
|
+
const raw = kind === 'vite' ? stats?.vite?.port : null;
|
|
2810
|
+
const n = Number(raw);
|
|
2811
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
function applyPreviewTarget(kind, stats) {
|
|
2815
|
+
const previousKind = previewBackendKind;
|
|
2816
|
+
previewBackendKind = kind === 'worker' || kind === 'vite' ? kind : 'none';
|
|
2817
|
+
const appKind = previewBackendKind === 'worker' ? 'worker' : 'vite';
|
|
2818
|
+
const appTab = ensurePreviewTab(appTabFor(appKind));
|
|
2819
|
+
const shouldFocusApp = previousKind !== previewBackendKind && previewBackendKind !== 'none';
|
|
2820
|
+
if (shouldFocusApp) activatePreviewTab(appTab.id);
|
|
2821
|
+
if (!activePreviewTabId) activatePreviewTab(appTab.id);
|
|
2822
|
+
syncPortPreviewTabs(stats?.ports, previewBackendPort(previewBackendKind, stats), appTab.id);
|
|
1782
2823
|
}
|
|
1783
2824
|
|
|
1784
2825
|
function reloadPreview() {
|
|
1785
|
-
|
|
2826
|
+
const active = activePreviewTabId ? previewTabs.get(activePreviewTabId) : null;
|
|
2827
|
+
if (active?.type === 'markdown') {
|
|
1786
2828
|
Editor.refreshMarkdownPreview().catch(() => {});
|
|
1787
2829
|
return;
|
|
1788
2830
|
}
|
|
1789
2831
|
// Refreshing without a running backend should re-poll the
|
|
1790
2832
|
// placeholder so users see dev-server status update promptly.
|
|
1791
|
-
if (previewFrame.src === 'about:blank'
|
|
1792
|
-
|
|
1793
|
-
previewFrame.src = previewUrlFor(
|
|
1794
|
-
(activePreview === 'none' || activePreview === null) ? 'vite' : activePreview
|
|
1795
|
-
);
|
|
2833
|
+
if (!active || previewFrame.src === 'about:blank') {
|
|
2834
|
+
activatePreviewTab(firstAppTabId());
|
|
1796
2835
|
} else {
|
|
1797
2836
|
try { previewFrame.contentWindow?.location.reload(); }
|
|
1798
2837
|
catch { previewFrame.src = previewFrame.src; }
|
|
1799
2838
|
}
|
|
1800
2839
|
}
|
|
1801
2840
|
|
|
2841
|
+
applyPreviewTarget('none', null);
|
|
2842
|
+
|
|
1802
2843
|
// ── Pane resize manager ──
|
|
1803
2844
|
//
|
|
1804
|
-
// Owns the
|
|
1805
|
-
// H1 tree ↔
|
|
1806
|
-
// H2
|
|
1807
|
-
// V1
|
|
2845
|
+
// Owns the three workspace resize handles:
|
|
2846
|
+
// H1 tree ↔ center stack (col) — TreeResizeHandle in FileTree IIFE
|
|
2847
|
+
// H2 center stack ↔ preview (col) — #resizeHandle
|
|
2848
|
+
// V1 top surface ↔ terminal (row) — #editorTerminalResizeHandle
|
|
1808
2849
|
//
|
|
1809
2850
|
// Each handle persists its dimension to localStorage so a reload
|
|
1810
2851
|
// restores the user's layout. Keyed per-session.
|
|
@@ -1853,41 +2894,25 @@
|
|
|
1853
2894
|
applyMonacoLayout();
|
|
1854
2895
|
}
|
|
1855
2896
|
|
|
1856
|
-
// Map middlePct
|
|
1857
|
-
// compete for horizontal space in each mode:
|
|
1858
|
-
// - split mode: .panel-terminal vs .panel-preview
|
|
1859
|
-
// - editor mode: .panel-left-stack vs .panel-preview
|
|
1860
|
-
// In editor mode .panel-left-stack is flex:1 (CSS rule); we
|
|
1861
|
-
// override with explicit basis. Same for .panel-terminal in split.
|
|
2897
|
+
// Map middlePct to the center workspace and preview columns.
|
|
1862
2898
|
function applyMiddlePreviewFlex() {
|
|
1863
2899
|
const stack = document.getElementById('leftStack');
|
|
1864
2900
|
const term = mainPanel.querySelector('.panel-terminal');
|
|
1865
2901
|
const prev = mainPanel.querySelector('.panel-preview');
|
|
1866
2902
|
const middlePct = Math.max(20, Math.min(80, dims.middlePct));
|
|
1867
|
-
if (
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
if (prev) prev.style.flex = (100 - middlePct) + ' 0 0%';
|
|
1871
|
-
if (term) term.style.flex = ''; // unset; vertical handler owns it
|
|
1872
|
-
} else if (layout === 'split') {
|
|
1873
|
-
if (term) term.style.flex = middlePct + ' 0 0%';
|
|
1874
|
-
if (prev) prev.style.flex = (100 - middlePct) + ' 0 0%';
|
|
1875
|
-
if (stack) stack.style.flex = '';
|
|
1876
|
-
} else {
|
|
1877
|
-
// Other modes (terminal-only / preview-only) don't use either
|
|
1878
|
-
// handle visibly; clear inline so CSS defaults take effect.
|
|
1879
|
-
if (term) term.style.flex = '';
|
|
1880
|
-
if (prev) prev.style.flex = '';
|
|
1881
|
-
if (stack) stack.style.flex = '';
|
|
1882
|
-
}
|
|
2903
|
+
if (stack) stack.style.flex = middlePct + ' 0 0%';
|
|
2904
|
+
if (prev) prev.style.flex = (100 - middlePct) + ' 0 0%';
|
|
2905
|
+
if (term) term.style.flex = '';
|
|
1883
2906
|
}
|
|
1884
2907
|
|
|
1885
2908
|
function applyEditorTerminalFlex() {
|
|
1886
2909
|
if (layout !== 'editor') return;
|
|
1887
|
-
const
|
|
2910
|
+
const topSurface = workSurface === 'agent'
|
|
2911
|
+
? document.getElementById('agentPanel')
|
|
2912
|
+
: document.getElementById('editorPanel');
|
|
1888
2913
|
const term = mainPanel.querySelector('.panel-terminal');
|
|
1889
2914
|
const editorPct = Math.max(10, Math.min(90, dims.editorPct));
|
|
1890
|
-
if (
|
|
2915
|
+
if (topSurface) topSurface.style.flex = editorPct + ' 0 0%';
|
|
1891
2916
|
if (term) term.style.flex = (100 - editorPct) + ' 0 0%';
|
|
1892
2917
|
}
|
|
1893
2918
|
|
|
@@ -1975,9 +3000,7 @@
|
|
|
1975
3000
|
if (tree) tree.style.width = dims.treeWidth + 'px';
|
|
1976
3001
|
}
|
|
1977
3002
|
|
|
1978
|
-
// Re-apply dims any time setLayout() switches
|
|
1979
|
-
// flex values are mode-specific (split vs editor have different
|
|
1980
|
-
// pane structures).
|
|
3003
|
+
// Re-apply dims any time setLayout() switches surfaces.
|
|
1981
3004
|
function onLayoutChange() {
|
|
1982
3005
|
applyMiddlePreviewFlex();
|
|
1983
3006
|
applyEditorTerminalFlex();
|
|
@@ -2014,7 +3037,7 @@
|
|
|
2014
3037
|
let kind = 'none';
|
|
2015
3038
|
if (s.wrangler?.running) kind = 'worker';
|
|
2016
3039
|
else if (s.vite?.running) kind = 'vite';
|
|
2017
|
-
applyPreviewTarget(kind);
|
|
3040
|
+
applyPreviewTarget(kind, s);
|
|
2018
3041
|
|
|
2019
3042
|
if (s.wrangler?.running) {
|
|
2020
3043
|
viteStatus.textContent = 'worker running (' + (s.wrangler.name || '?') + ')';
|
|
@@ -2503,7 +3526,10 @@
|
|
|
2503
3526
|
// Restore persisted pane dims before activating layout so the first paint
|
|
2504
3527
|
// shows the user's prior arrangement.
|
|
2505
3528
|
PaneResizer.restoreDims();
|
|
2506
|
-
|
|
3529
|
+
const initialMode = new URLSearchParams(location.search).get('agent') === '1'
|
|
3530
|
+
? 'agent'
|
|
3531
|
+
: 'editor';
|
|
3532
|
+
setLayout(initialMode);
|
|
2507
3533
|
connect();
|
|
2508
3534
|
</script>
|
|
2509
3535
|
</body>
|