@poncho-ai/cli 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +36 -0
- package/dist/{chunk-XIFWXRUB.js → chunk-A32BXZKP.js} +1577 -196
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-64QEOUXL.js → run-interactive-ink-SLWDVTDX.js} +1 -1
- package/package.json +4 -3
- package/src/api-docs.ts +674 -0
- package/src/index.ts +413 -40
- package/src/init-feature-context.ts +5 -0
- package/src/init-onboarding.ts +23 -2
- package/src/web-ui.ts +559 -155
|
@@ -22,6 +22,11 @@ import {
|
|
|
22
22
|
resolveStateConfig
|
|
23
23
|
} from "@poncho-ai/harness";
|
|
24
24
|
import { getTextContent } from "@poncho-ai/sdk";
|
|
25
|
+
import {
|
|
26
|
+
AgentBridge,
|
|
27
|
+
ResendAdapter,
|
|
28
|
+
SlackAdapter
|
|
29
|
+
} from "@poncho-ai/messaging";
|
|
25
30
|
import Busboy from "busboy";
|
|
26
31
|
import { Command } from "commander";
|
|
27
32
|
import dotenv from "dotenv";
|
|
@@ -254,7 +259,8 @@ var renderWebUiHtml = (options) => {
|
|
|
254
259
|
<head>
|
|
255
260
|
<meta charset="utf-8">
|
|
256
261
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
|
257
|
-
<meta name="theme-color" content="#000000">
|
|
262
|
+
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)">
|
|
263
|
+
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
|
258
264
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
259
265
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
260
266
|
<meta name="apple-mobile-web-app-title" content="${agentName}">
|
|
@@ -264,12 +270,168 @@ var renderWebUiHtml = (options) => {
|
|
|
264
270
|
<title>${agentName}</title>
|
|
265
271
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inconsolata:400,700">
|
|
266
272
|
<style>
|
|
273
|
+
:root {
|
|
274
|
+
color-scheme: light dark;
|
|
275
|
+
|
|
276
|
+
--bg: #000;
|
|
277
|
+
--bg-alt: #0a0a0a;
|
|
278
|
+
--bg-elevated: #111;
|
|
279
|
+
|
|
280
|
+
--fg: #ededed;
|
|
281
|
+
--fg-strong: #fff;
|
|
282
|
+
--fg-2: #888;
|
|
283
|
+
--fg-3: #999;
|
|
284
|
+
--fg-4: #777;
|
|
285
|
+
--fg-5: #666;
|
|
286
|
+
--fg-6: #555;
|
|
287
|
+
--fg-7: #444;
|
|
288
|
+
--fg-8: #333;
|
|
289
|
+
|
|
290
|
+
--fg-tool: #8a8a8a;
|
|
291
|
+
--fg-tool-code: #bcbcbc;
|
|
292
|
+
--fg-tool-item: #d6d6d6;
|
|
293
|
+
--fg-approval-label: #b0b0b0;
|
|
294
|
+
--fg-approval-input: #cfcfcf;
|
|
295
|
+
--fg-approval-btn: #f0f0f0;
|
|
296
|
+
|
|
297
|
+
--accent: #ededed;
|
|
298
|
+
--accent-fg: #000;
|
|
299
|
+
--accent-hover: #fff;
|
|
300
|
+
|
|
301
|
+
--stop-bg: #4a4a4a;
|
|
302
|
+
--stop-fg: #fff;
|
|
303
|
+
--stop-hover: #565656;
|
|
304
|
+
|
|
305
|
+
--border-1: rgba(255,255,255,0.06);
|
|
306
|
+
--border-2: rgba(255,255,255,0.08);
|
|
307
|
+
--border-3: rgba(255,255,255,0.1);
|
|
308
|
+
--border-4: rgba(255,255,255,0.12);
|
|
309
|
+
--border-5: rgba(255,255,255,0.18);
|
|
310
|
+
--border-focus: rgba(255,255,255,0.2);
|
|
311
|
+
--border-hover: rgba(255,255,255,0.25);
|
|
312
|
+
--border-drag: rgba(255,255,255,0.4);
|
|
313
|
+
|
|
314
|
+
--surface-1: rgba(255,255,255,0.02);
|
|
315
|
+
--surface-2: rgba(255,255,255,0.03);
|
|
316
|
+
--surface-3: rgba(255,255,255,0.04);
|
|
317
|
+
--surface-4: rgba(255,255,255,0.06);
|
|
318
|
+
--surface-5: rgba(255,255,255,0.08);
|
|
319
|
+
--surface-6: rgba(255,255,255,0.1);
|
|
320
|
+
--surface-7: rgba(255,255,255,0.12);
|
|
321
|
+
--surface-8: rgba(255,255,255,0.14);
|
|
322
|
+
|
|
323
|
+
--chip-bg: rgba(0,0,0,0.6);
|
|
324
|
+
--chip-bg-hover: rgba(0,0,0,0.75);
|
|
325
|
+
--backdrop: rgba(0,0,0,0.6);
|
|
326
|
+
--lightbox-bg: rgba(0,0,0,0.85);
|
|
327
|
+
--inset-1: rgba(0,0,0,0.16);
|
|
328
|
+
--inset-2: rgba(0,0,0,0.25);
|
|
329
|
+
|
|
330
|
+
--file-badge-bg: rgba(0,0,0,0.2);
|
|
331
|
+
--file-badge-fg: rgba(255,255,255,0.8);
|
|
332
|
+
|
|
333
|
+
--error: #ff4444;
|
|
334
|
+
--error-soft: #ff6b6b;
|
|
335
|
+
--error-alt: #ff6666;
|
|
336
|
+
--error-bg: rgba(255,68,68,0.08);
|
|
337
|
+
--error-border: rgba(255,68,68,0.25);
|
|
338
|
+
|
|
339
|
+
--tool-done: #6a9955;
|
|
340
|
+
--tool-error: #f48771;
|
|
341
|
+
|
|
342
|
+
--approve: #78e7a6;
|
|
343
|
+
--approve-border: rgba(58,208,122,0.45);
|
|
344
|
+
--deny: #f59b9b;
|
|
345
|
+
--deny-border: rgba(224,95,95,0.45);
|
|
346
|
+
|
|
347
|
+
--scrollbar: rgba(255,255,255,0.1);
|
|
348
|
+
--scrollbar-hover: rgba(255,255,255,0.16);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@media (prefers-color-scheme: light) {
|
|
352
|
+
:root {
|
|
353
|
+
--bg: #ffffff;
|
|
354
|
+
--bg-alt: #f5f5f5;
|
|
355
|
+
--bg-elevated: #e8e8e8;
|
|
356
|
+
|
|
357
|
+
--fg: #1a1a1a;
|
|
358
|
+
--fg-strong: #000;
|
|
359
|
+
--fg-2: #666;
|
|
360
|
+
--fg-3: #555;
|
|
361
|
+
--fg-4: #777;
|
|
362
|
+
--fg-5: #888;
|
|
363
|
+
--fg-6: #888;
|
|
364
|
+
--fg-7: #aaa;
|
|
365
|
+
--fg-8: #bbb;
|
|
366
|
+
|
|
367
|
+
--fg-tool: #666;
|
|
368
|
+
--fg-tool-code: #444;
|
|
369
|
+
--fg-tool-item: #333;
|
|
370
|
+
--fg-approval-label: #666;
|
|
371
|
+
--fg-approval-input: #444;
|
|
372
|
+
--fg-approval-btn: #1a1a1a;
|
|
373
|
+
|
|
374
|
+
--accent: #1a1a1a;
|
|
375
|
+
--accent-fg: #fff;
|
|
376
|
+
--accent-hover: #000;
|
|
377
|
+
|
|
378
|
+
--stop-bg: #d4d4d4;
|
|
379
|
+
--stop-fg: #333;
|
|
380
|
+
--stop-hover: #c4c4c4;
|
|
381
|
+
|
|
382
|
+
--border-1: rgba(0,0,0,0.06);
|
|
383
|
+
--border-2: rgba(0,0,0,0.08);
|
|
384
|
+
--border-3: rgba(0,0,0,0.1);
|
|
385
|
+
--border-4: rgba(0,0,0,0.1);
|
|
386
|
+
--border-5: rgba(0,0,0,0.15);
|
|
387
|
+
--border-focus: rgba(0,0,0,0.2);
|
|
388
|
+
--border-hover: rgba(0,0,0,0.2);
|
|
389
|
+
--border-drag: rgba(0,0,0,0.3);
|
|
390
|
+
|
|
391
|
+
--surface-1: rgba(0,0,0,0.02);
|
|
392
|
+
--surface-2: rgba(0,0,0,0.03);
|
|
393
|
+
--surface-3: rgba(0,0,0,0.03);
|
|
394
|
+
--surface-4: rgba(0,0,0,0.04);
|
|
395
|
+
--surface-5: rgba(0,0,0,0.05);
|
|
396
|
+
--surface-6: rgba(0,0,0,0.07);
|
|
397
|
+
--surface-7: rgba(0,0,0,0.08);
|
|
398
|
+
--surface-8: rgba(0,0,0,0.1);
|
|
399
|
+
|
|
400
|
+
--chip-bg: rgba(255,255,255,0.8);
|
|
401
|
+
--chip-bg-hover: rgba(255,255,255,0.9);
|
|
402
|
+
--backdrop: rgba(0,0,0,0.3);
|
|
403
|
+
--lightbox-bg: rgba(0,0,0,0.75);
|
|
404
|
+
--inset-1: rgba(0,0,0,0.04);
|
|
405
|
+
--inset-2: rgba(0,0,0,0.06);
|
|
406
|
+
|
|
407
|
+
--file-badge-bg: rgba(0,0,0,0.05);
|
|
408
|
+
--file-badge-fg: rgba(0,0,0,0.7);
|
|
409
|
+
|
|
410
|
+
--error: #dc2626;
|
|
411
|
+
--error-soft: #ef4444;
|
|
412
|
+
--error-alt: #ef4444;
|
|
413
|
+
--error-bg: rgba(220,38,38,0.06);
|
|
414
|
+
--error-border: rgba(220,38,38,0.2);
|
|
415
|
+
|
|
416
|
+
--tool-done: #16a34a;
|
|
417
|
+
--tool-error: #dc2626;
|
|
418
|
+
|
|
419
|
+
--approve: #16a34a;
|
|
420
|
+
--approve-border: rgba(22,163,74,0.35);
|
|
421
|
+
--deny: #dc2626;
|
|
422
|
+
--deny-border: rgba(220,38,38,0.3);
|
|
423
|
+
|
|
424
|
+
--scrollbar: rgba(0,0,0,0.12);
|
|
425
|
+
--scrollbar-hover: rgba(0,0,0,0.2);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
267
429
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
268
430
|
html, body { height: 100vh; overflow: hidden; overscroll-behavior: none; touch-action: pan-y; }
|
|
269
431
|
body {
|
|
270
432
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;
|
|
271
|
-
background:
|
|
272
|
-
color:
|
|
433
|
+
background: var(--bg);
|
|
434
|
+
color: var(--fg);
|
|
273
435
|
font-size: 14px;
|
|
274
436
|
line-height: 1.5;
|
|
275
437
|
-webkit-font-smoothing: antialiased;
|
|
@@ -277,7 +439,7 @@ var renderWebUiHtml = (options) => {
|
|
|
277
439
|
}
|
|
278
440
|
button, input, textarea { font: inherit; color: inherit; }
|
|
279
441
|
.hidden { display: none !important; }
|
|
280
|
-
a { color:
|
|
442
|
+
a { color: var(--fg); }
|
|
281
443
|
|
|
282
444
|
/* Auth */
|
|
283
445
|
.auth {
|
|
@@ -285,39 +447,39 @@ var renderWebUiHtml = (options) => {
|
|
|
285
447
|
display: grid;
|
|
286
448
|
place-items: center;
|
|
287
449
|
padding: 20px;
|
|
288
|
-
background:
|
|
450
|
+
background: var(--bg);
|
|
289
451
|
}
|
|
290
452
|
.auth-card {
|
|
291
453
|
width: min(400px, 90vw);
|
|
292
454
|
}
|
|
293
455
|
.auth-shell {
|
|
294
|
-
background:
|
|
295
|
-
border: 1px solid
|
|
456
|
+
background: var(--bg-alt);
|
|
457
|
+
border: 1px solid var(--border-3);
|
|
296
458
|
border-radius: 9999px;
|
|
297
459
|
display: flex;
|
|
298
460
|
align-items: center;
|
|
299
461
|
padding: 4px 6px 4px 18px;
|
|
300
462
|
transition: border-color 0.15s;
|
|
301
463
|
}
|
|
302
|
-
.auth-shell:focus-within { border-color:
|
|
464
|
+
.auth-shell:focus-within { border-color: var(--border-focus); }
|
|
303
465
|
.auth-input {
|
|
304
466
|
flex: 1;
|
|
305
467
|
background: transparent;
|
|
306
468
|
border: 0;
|
|
307
469
|
outline: none;
|
|
308
|
-
color:
|
|
470
|
+
color: var(--fg);
|
|
309
471
|
padding: 10px 0 8px;
|
|
310
472
|
font-size: 14px;
|
|
311
473
|
margin-top: -2px;
|
|
312
474
|
}
|
|
313
|
-
.auth-input::placeholder { color:
|
|
475
|
+
.auth-input::placeholder { color: var(--fg-7); }
|
|
314
476
|
.auth-submit {
|
|
315
477
|
width: 32px;
|
|
316
478
|
height: 32px;
|
|
317
|
-
background:
|
|
479
|
+
background: var(--accent);
|
|
318
480
|
border: 0;
|
|
319
481
|
border-radius: 50%;
|
|
320
|
-
color:
|
|
482
|
+
color: var(--accent-fg);
|
|
321
483
|
cursor: pointer;
|
|
322
484
|
display: grid;
|
|
323
485
|
place-items: center;
|
|
@@ -325,19 +487,19 @@ var renderWebUiHtml = (options) => {
|
|
|
325
487
|
margin-bottom: 2px;
|
|
326
488
|
transition: background 0.15s;
|
|
327
489
|
}
|
|
328
|
-
.auth-submit:hover { background:
|
|
329
|
-
.error { color:
|
|
490
|
+
.auth-submit:hover { background: var(--accent-hover); }
|
|
491
|
+
.error { color: var(--error); font-size: 13px; min-height: 16px; }
|
|
330
492
|
.message-error {
|
|
331
|
-
background:
|
|
332
|
-
border: 1px solid
|
|
493
|
+
background: var(--error-bg);
|
|
494
|
+
border: 1px solid var(--error-border);
|
|
333
495
|
border-radius: 10px;
|
|
334
|
-
color:
|
|
496
|
+
color: var(--error-soft);
|
|
335
497
|
padding: 12px 16px;
|
|
336
498
|
font-size: 13px;
|
|
337
499
|
line-height: 1.5;
|
|
338
500
|
max-width: 600px;
|
|
339
501
|
}
|
|
340
|
-
.message-error strong { color:
|
|
502
|
+
.message-error strong { color: var(--error); }
|
|
341
503
|
|
|
342
504
|
/* Layout - use fixed positioning with explicit dimensions */
|
|
343
505
|
.shell {
|
|
@@ -369,8 +531,8 @@ var renderWebUiHtml = (options) => {
|
|
|
369
531
|
}
|
|
370
532
|
.sidebar {
|
|
371
533
|
width: 260px;
|
|
372
|
-
background:
|
|
373
|
-
border-right: 1px solid
|
|
534
|
+
background: var(--bg);
|
|
535
|
+
border-right: 1px solid var(--border-1);
|
|
374
536
|
display: flex;
|
|
375
537
|
flex-direction: column;
|
|
376
538
|
padding: 12px 8px;
|
|
@@ -378,7 +540,7 @@ var renderWebUiHtml = (options) => {
|
|
|
378
540
|
.new-chat-btn {
|
|
379
541
|
background: transparent;
|
|
380
542
|
border: 0;
|
|
381
|
-
color:
|
|
543
|
+
color: var(--fg-2);
|
|
382
544
|
border-radius: 12px;
|
|
383
545
|
height: 36px;
|
|
384
546
|
padding: 0 10px;
|
|
@@ -389,7 +551,7 @@ var renderWebUiHtml = (options) => {
|
|
|
389
551
|
cursor: pointer;
|
|
390
552
|
transition: background 0.15s, color 0.15s;
|
|
391
553
|
}
|
|
392
|
-
.new-chat-btn:hover { color:
|
|
554
|
+
.new-chat-btn:hover { color: var(--fg); }
|
|
393
555
|
.new-chat-btn svg { width: 16px; height: 16px; }
|
|
394
556
|
.conversation-list {
|
|
395
557
|
flex: 1;
|
|
@@ -409,16 +571,16 @@ var renderWebUiHtml = (options) => {
|
|
|
409
571
|
cursor: pointer;
|
|
410
572
|
font-size: 13px;
|
|
411
573
|
line-height: 36px;
|
|
412
|
-
color:
|
|
574
|
+
color: var(--fg-6);
|
|
413
575
|
white-space: nowrap;
|
|
414
576
|
overflow: hidden;
|
|
415
577
|
text-overflow: ellipsis;
|
|
416
578
|
position: relative;
|
|
417
579
|
transition: color 0.15s;
|
|
418
580
|
}
|
|
419
|
-
.conversation-item:hover { color:
|
|
581
|
+
.conversation-item:hover { color: var(--fg-3); }
|
|
420
582
|
.conversation-item.active {
|
|
421
|
-
color:
|
|
583
|
+
color: var(--fg);
|
|
422
584
|
}
|
|
423
585
|
.conversation-item .delete-btn {
|
|
424
586
|
position: absolute;
|
|
@@ -426,9 +588,9 @@ var renderWebUiHtml = (options) => {
|
|
|
426
588
|
top: 0;
|
|
427
589
|
bottom: 0;
|
|
428
590
|
opacity: 0;
|
|
429
|
-
background:
|
|
591
|
+
background: var(--bg);
|
|
430
592
|
border: 0;
|
|
431
|
-
color:
|
|
593
|
+
color: var(--fg-7);
|
|
432
594
|
padding: 0 8px;
|
|
433
595
|
border-radius: 0 4px 4px 0;
|
|
434
596
|
cursor: pointer;
|
|
@@ -439,7 +601,7 @@ var renderWebUiHtml = (options) => {
|
|
|
439
601
|
transition: opacity 0.15s, color 0.15s;
|
|
440
602
|
}
|
|
441
603
|
.conversation-item:hover .delete-btn { opacity: 1; }
|
|
442
|
-
.conversation-item.active .delete-btn { background:
|
|
604
|
+
.conversation-item.active .delete-btn { background: var(--bg); }
|
|
443
605
|
.conversation-item .delete-btn::before {
|
|
444
606
|
content: "";
|
|
445
607
|
position: absolute;
|
|
@@ -447,23 +609,23 @@ var renderWebUiHtml = (options) => {
|
|
|
447
609
|
top: 0;
|
|
448
610
|
bottom: 0;
|
|
449
611
|
width: 24px;
|
|
450
|
-
background: linear-gradient(to right, transparent,
|
|
612
|
+
background: linear-gradient(to right, transparent, var(--bg));
|
|
451
613
|
pointer-events: none;
|
|
452
614
|
}
|
|
453
615
|
.conversation-item.active .delete-btn::before {
|
|
454
|
-
background: linear-gradient(to right, transparent,
|
|
616
|
+
background: linear-gradient(to right, transparent, var(--bg));
|
|
455
617
|
}
|
|
456
|
-
.conversation-item .delete-btn:hover { color:
|
|
618
|
+
.conversation-item .delete-btn:hover { color: var(--fg-2); }
|
|
457
619
|
.conversation-item .delete-btn.confirming {
|
|
458
620
|
opacity: 1;
|
|
459
621
|
width: auto;
|
|
460
622
|
padding: 0 8px;
|
|
461
623
|
font-size: 11px;
|
|
462
|
-
color:
|
|
624
|
+
color: var(--error);
|
|
463
625
|
border-radius: 3px;
|
|
464
626
|
}
|
|
465
627
|
.conversation-item .delete-btn.confirming:hover {
|
|
466
|
-
color:
|
|
628
|
+
color: var(--error-alt);
|
|
467
629
|
}
|
|
468
630
|
.sidebar-footer {
|
|
469
631
|
margin-top: auto;
|
|
@@ -472,7 +634,7 @@ var renderWebUiHtml = (options) => {
|
|
|
472
634
|
.logout-btn {
|
|
473
635
|
background: transparent;
|
|
474
636
|
border: 0;
|
|
475
|
-
color:
|
|
637
|
+
color: var(--fg-6);
|
|
476
638
|
width: 100%;
|
|
477
639
|
padding: 8px 10px;
|
|
478
640
|
text-align: left;
|
|
@@ -481,10 +643,10 @@ var renderWebUiHtml = (options) => {
|
|
|
481
643
|
font-size: 13px;
|
|
482
644
|
transition: color 0.15s, background 0.15s;
|
|
483
645
|
}
|
|
484
|
-
.logout-btn:hover { color:
|
|
646
|
+
.logout-btn:hover { color: var(--fg-2); }
|
|
485
647
|
|
|
486
648
|
/* Main */
|
|
487
|
-
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; max-width: 100%; background:
|
|
649
|
+
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; max-width: 100%; background: var(--bg); overflow: hidden; }
|
|
488
650
|
.topbar {
|
|
489
651
|
height: calc(52px + env(safe-area-inset-top, 0px));
|
|
490
652
|
padding-top: env(safe-area-inset-top, 0px);
|
|
@@ -493,8 +655,8 @@ var renderWebUiHtml = (options) => {
|
|
|
493
655
|
justify-content: center;
|
|
494
656
|
font-size: 13px;
|
|
495
657
|
font-weight: 500;
|
|
496
|
-
color:
|
|
497
|
-
border-bottom: 1px solid
|
|
658
|
+
color: var(--fg-2);
|
|
659
|
+
border-bottom: 1px solid var(--border-1);
|
|
498
660
|
position: relative;
|
|
499
661
|
flex-shrink: 0;
|
|
500
662
|
}
|
|
@@ -513,7 +675,7 @@ var renderWebUiHtml = (options) => {
|
|
|
513
675
|
bottom: 4px; /* Position from bottom of topbar content area */
|
|
514
676
|
background: transparent;
|
|
515
677
|
border: 0;
|
|
516
|
-
color:
|
|
678
|
+
color: var(--fg-5);
|
|
517
679
|
width: 44px;
|
|
518
680
|
height: 44px;
|
|
519
681
|
border-radius: 6px;
|
|
@@ -523,7 +685,7 @@ var renderWebUiHtml = (options) => {
|
|
|
523
685
|
z-index: 10;
|
|
524
686
|
-webkit-tap-highlight-color: transparent;
|
|
525
687
|
}
|
|
526
|
-
.sidebar-toggle:hover { color:
|
|
688
|
+
.sidebar-toggle:hover { color: var(--fg); }
|
|
527
689
|
.topbar-new-chat {
|
|
528
690
|
display: none;
|
|
529
691
|
position: absolute;
|
|
@@ -531,7 +693,7 @@ var renderWebUiHtml = (options) => {
|
|
|
531
693
|
bottom: 4px;
|
|
532
694
|
background: transparent;
|
|
533
695
|
border: 0;
|
|
534
|
-
color:
|
|
696
|
+
color: var(--fg-5);
|
|
535
697
|
width: 44px;
|
|
536
698
|
height: 44px;
|
|
537
699
|
border-radius: 6px;
|
|
@@ -540,7 +702,7 @@ var renderWebUiHtml = (options) => {
|
|
|
540
702
|
z-index: 10;
|
|
541
703
|
-webkit-tap-highlight-color: transparent;
|
|
542
704
|
}
|
|
543
|
-
.topbar-new-chat:hover { color:
|
|
705
|
+
.topbar-new-chat:hover { color: var(--fg); }
|
|
544
706
|
.topbar-new-chat svg { width: 16px; height: 16px; }
|
|
545
707
|
|
|
546
708
|
/* Messages */
|
|
@@ -552,8 +714,8 @@ var renderWebUiHtml = (options) => {
|
|
|
552
714
|
.assistant-avatar {
|
|
553
715
|
width: 24px;
|
|
554
716
|
height: 24px;
|
|
555
|
-
background:
|
|
556
|
-
color:
|
|
717
|
+
background: var(--accent);
|
|
718
|
+
color: var(--accent-fg);
|
|
557
719
|
border-radius: 6px;
|
|
558
720
|
display: grid;
|
|
559
721
|
place-items: center;
|
|
@@ -564,7 +726,7 @@ var renderWebUiHtml = (options) => {
|
|
|
564
726
|
}
|
|
565
727
|
.assistant-content {
|
|
566
728
|
line-height: 1.65;
|
|
567
|
-
color:
|
|
729
|
+
color: var(--fg);
|
|
568
730
|
font-size: 14px;
|
|
569
731
|
min-width: 0;
|
|
570
732
|
max-width: 100%;
|
|
@@ -576,32 +738,32 @@ var renderWebUiHtml = (options) => {
|
|
|
576
738
|
.assistant-content p:last-child { margin-bottom: 0; }
|
|
577
739
|
.assistant-content ul, .assistant-content ol { margin: 8px 0; padding-left: 20px; }
|
|
578
740
|
.assistant-content li { margin: 4px 0; }
|
|
579
|
-
.assistant-content strong { font-weight: 600; color:
|
|
741
|
+
.assistant-content strong { font-weight: 600; color: var(--fg-strong); }
|
|
580
742
|
.assistant-content h2 {
|
|
581
743
|
font-size: 16px;
|
|
582
744
|
font-weight: 600;
|
|
583
745
|
letter-spacing: -0.02em;
|
|
584
746
|
margin: 20px 0 8px;
|
|
585
|
-
color:
|
|
747
|
+
color: var(--fg-strong);
|
|
586
748
|
}
|
|
587
749
|
.assistant-content h3 {
|
|
588
750
|
font-size: 14px;
|
|
589
751
|
font-weight: 600;
|
|
590
752
|
letter-spacing: -0.01em;
|
|
591
753
|
margin: 16px 0 6px;
|
|
592
|
-
color:
|
|
754
|
+
color: var(--fg-strong);
|
|
593
755
|
}
|
|
594
756
|
.assistant-content code {
|
|
595
|
-
background:
|
|
596
|
-
border: 1px solid
|
|
757
|
+
background: var(--surface-4);
|
|
758
|
+
border: 1px solid var(--border-1);
|
|
597
759
|
padding: 2px 5px;
|
|
598
760
|
border-radius: 4px;
|
|
599
761
|
font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
|
|
600
762
|
font-size: 0.88em;
|
|
601
763
|
}
|
|
602
764
|
.assistant-content pre {
|
|
603
|
-
background:
|
|
604
|
-
border: 1px solid
|
|
765
|
+
background: var(--bg-alt);
|
|
766
|
+
border: 1px solid var(--border-1);
|
|
605
767
|
padding: 14px 16px;
|
|
606
768
|
border-radius: 8px;
|
|
607
769
|
overflow-x: auto;
|
|
@@ -618,33 +780,33 @@ var renderWebUiHtml = (options) => {
|
|
|
618
780
|
margin: 8px 0;
|
|
619
781
|
font-size: 12px;
|
|
620
782
|
line-height: 1.45;
|
|
621
|
-
color:
|
|
783
|
+
color: var(--fg-tool);
|
|
622
784
|
}
|
|
623
785
|
.tool-activity-inline code {
|
|
624
786
|
font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
|
|
625
|
-
background:
|
|
626
|
-
border: 1px solid
|
|
787
|
+
background: var(--surface-3);
|
|
788
|
+
border: 1px solid var(--border-2);
|
|
627
789
|
padding: 4px 8px;
|
|
628
790
|
border-radius: 6px;
|
|
629
|
-
color:
|
|
791
|
+
color: var(--fg-tool-code);
|
|
630
792
|
font-size: 11px;
|
|
631
793
|
}
|
|
632
794
|
.tool-status {
|
|
633
|
-
color:
|
|
795
|
+
color: var(--fg-tool);
|
|
634
796
|
font-style: italic;
|
|
635
797
|
}
|
|
636
798
|
.tool-done {
|
|
637
|
-
color:
|
|
799
|
+
color: var(--tool-done);
|
|
638
800
|
}
|
|
639
801
|
.tool-error {
|
|
640
|
-
color:
|
|
802
|
+
color: var(--tool-error);
|
|
641
803
|
}
|
|
642
804
|
.assistant-content table {
|
|
643
805
|
border-collapse: collapse;
|
|
644
806
|
width: 100%;
|
|
645
807
|
margin: 14px 0;
|
|
646
808
|
font-size: 13px;
|
|
647
|
-
border: 1px solid
|
|
809
|
+
border: 1px solid var(--border-2);
|
|
648
810
|
border-radius: 8px;
|
|
649
811
|
overflow: hidden;
|
|
650
812
|
display: block;
|
|
@@ -653,17 +815,17 @@ var renderWebUiHtml = (options) => {
|
|
|
653
815
|
white-space: nowrap;
|
|
654
816
|
}
|
|
655
817
|
.assistant-content th {
|
|
656
|
-
background:
|
|
818
|
+
background: var(--surface-4);
|
|
657
819
|
padding: 10px 12px;
|
|
658
820
|
text-align: left;
|
|
659
821
|
font-weight: 600;
|
|
660
|
-
border-bottom: 1px solid
|
|
661
|
-
color:
|
|
822
|
+
border-bottom: 1px solid var(--border-4);
|
|
823
|
+
color: var(--fg-strong);
|
|
662
824
|
min-width: 100px;
|
|
663
825
|
}
|
|
664
826
|
.assistant-content td {
|
|
665
827
|
padding: 10px 12px;
|
|
666
|
-
border-bottom: 1px solid
|
|
828
|
+
border-bottom: 1px solid var(--border-1);
|
|
667
829
|
width: 100%;
|
|
668
830
|
min-width: 100px;
|
|
669
831
|
}
|
|
@@ -671,22 +833,22 @@ var renderWebUiHtml = (options) => {
|
|
|
671
833
|
border-bottom: none;
|
|
672
834
|
}
|
|
673
835
|
.assistant-content tbody tr:hover {
|
|
674
|
-
background:
|
|
836
|
+
background: var(--surface-1);
|
|
675
837
|
}
|
|
676
838
|
.assistant-content hr {
|
|
677
839
|
border: 0;
|
|
678
|
-
border-top: 1px solid
|
|
840
|
+
border-top: 1px solid var(--border-3);
|
|
679
841
|
margin: 20px 0;
|
|
680
842
|
}
|
|
681
843
|
.tool-activity {
|
|
682
844
|
margin-top: 12px;
|
|
683
845
|
margin-bottom: 12px;
|
|
684
|
-
border: 1px solid
|
|
685
|
-
background:
|
|
846
|
+
border: 1px solid var(--border-2);
|
|
847
|
+
background: var(--surface-2);
|
|
686
848
|
border-radius: 10px;
|
|
687
849
|
font-size: 12px;
|
|
688
850
|
line-height: 1.45;
|
|
689
|
-
color:
|
|
851
|
+
color: var(--fg-tool-code);
|
|
690
852
|
width: 300px;
|
|
691
853
|
}
|
|
692
854
|
.assistant-content > .tool-activity:first-child {
|
|
@@ -711,12 +873,12 @@ var renderWebUiHtml = (options) => {
|
|
|
711
873
|
font-size: 11px;
|
|
712
874
|
text-transform: uppercase;
|
|
713
875
|
letter-spacing: 0.06em;
|
|
714
|
-
color:
|
|
876
|
+
color: var(--fg-tool);
|
|
715
877
|
font-weight: 600;
|
|
716
878
|
}
|
|
717
879
|
.tool-activity-caret {
|
|
718
880
|
margin-left: auto;
|
|
719
|
-
color:
|
|
881
|
+
color: var(--fg-tool);
|
|
720
882
|
display: inline-flex;
|
|
721
883
|
align-items: center;
|
|
722
884
|
justify-content: center;
|
|
@@ -738,28 +900,28 @@ var renderWebUiHtml = (options) => {
|
|
|
738
900
|
}
|
|
739
901
|
.tool-activity-item {
|
|
740
902
|
font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
|
|
741
|
-
background:
|
|
903
|
+
background: var(--surface-3);
|
|
742
904
|
border-radius: 6px;
|
|
743
905
|
padding: 4px 7px;
|
|
744
|
-
color:
|
|
906
|
+
color: var(--fg-tool-item);
|
|
745
907
|
}
|
|
746
908
|
.approval-requests {
|
|
747
|
-
border-top: 1px solid
|
|
909
|
+
border-top: 1px solid var(--border-2);
|
|
748
910
|
padding: 10px 12px 12px;
|
|
749
911
|
display: grid;
|
|
750
912
|
gap: 8px;
|
|
751
|
-
background:
|
|
913
|
+
background: var(--inset-1);
|
|
752
914
|
}
|
|
753
915
|
.approval-requests-label {
|
|
754
916
|
font-size: 11px;
|
|
755
917
|
text-transform: uppercase;
|
|
756
918
|
letter-spacing: 0.06em;
|
|
757
|
-
color:
|
|
919
|
+
color: var(--fg-approval-label);
|
|
758
920
|
font-weight: 600;
|
|
759
921
|
}
|
|
760
922
|
.approval-request-item {
|
|
761
|
-
border: 1px solid
|
|
762
|
-
background:
|
|
923
|
+
border: 1px solid var(--border-3);
|
|
924
|
+
background: var(--surface-2);
|
|
763
925
|
border-radius: 8px;
|
|
764
926
|
padding: 8px;
|
|
765
927
|
display: grid;
|
|
@@ -767,15 +929,15 @@ var renderWebUiHtml = (options) => {
|
|
|
767
929
|
}
|
|
768
930
|
.approval-request-tool {
|
|
769
931
|
font-size: 12px;
|
|
770
|
-
color:
|
|
932
|
+
color: var(--fg-strong);
|
|
771
933
|
font-weight: 600;
|
|
772
934
|
overflow-wrap: anywhere;
|
|
773
935
|
}
|
|
774
936
|
.approval-request-input {
|
|
775
937
|
font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
|
|
776
938
|
font-size: 11px;
|
|
777
|
-
color:
|
|
778
|
-
background:
|
|
939
|
+
color: var(--fg-approval-input);
|
|
940
|
+
background: var(--inset-2);
|
|
779
941
|
border-radius: 6px;
|
|
780
942
|
padding: 6px;
|
|
781
943
|
overflow-wrap: anywhere;
|
|
@@ -788,32 +950,32 @@ var renderWebUiHtml = (options) => {
|
|
|
788
950
|
}
|
|
789
951
|
.approval-action-btn {
|
|
790
952
|
border-radius: 6px;
|
|
791
|
-
border: 1px solid
|
|
792
|
-
background:
|
|
793
|
-
color:
|
|
953
|
+
border: 1px solid var(--border-5);
|
|
954
|
+
background: var(--surface-4);
|
|
955
|
+
color: var(--fg-approval-btn);
|
|
794
956
|
font-size: 11px;
|
|
795
957
|
font-weight: 600;
|
|
796
958
|
padding: 4px 8px;
|
|
797
959
|
cursor: pointer;
|
|
798
960
|
}
|
|
799
961
|
.approval-action-btn:hover {
|
|
800
|
-
background:
|
|
962
|
+
background: var(--surface-7);
|
|
801
963
|
}
|
|
802
964
|
.approval-action-btn.approve {
|
|
803
|
-
border-color:
|
|
804
|
-
color:
|
|
965
|
+
border-color: var(--approve-border);
|
|
966
|
+
color: var(--approve);
|
|
805
967
|
}
|
|
806
968
|
.approval-action-btn.deny {
|
|
807
|
-
border-color:
|
|
808
|
-
color:
|
|
969
|
+
border-color: var(--deny-border);
|
|
970
|
+
color: var(--deny);
|
|
809
971
|
}
|
|
810
972
|
.approval-action-btn[disabled] {
|
|
811
973
|
opacity: 0.55;
|
|
812
974
|
cursor: not-allowed;
|
|
813
975
|
}
|
|
814
976
|
.user-bubble {
|
|
815
|
-
background:
|
|
816
|
-
border: 1px solid
|
|
977
|
+
background: var(--bg-elevated);
|
|
978
|
+
border: 1px solid var(--border-2);
|
|
817
979
|
padding: 10px 16px;
|
|
818
980
|
border-radius: 18px;
|
|
819
981
|
max-width: 70%;
|
|
@@ -829,7 +991,7 @@ var renderWebUiHtml = (options) => {
|
|
|
829
991
|
justify-content: center;
|
|
830
992
|
height: 100%;
|
|
831
993
|
gap: 16px;
|
|
832
|
-
color:
|
|
994
|
+
color: var(--fg-6);
|
|
833
995
|
}
|
|
834
996
|
.empty-state .assistant-avatar {
|
|
835
997
|
width: 36px;
|
|
@@ -839,7 +1001,7 @@ var renderWebUiHtml = (options) => {
|
|
|
839
1001
|
}
|
|
840
1002
|
.empty-state-text {
|
|
841
1003
|
font-size: 14px;
|
|
842
|
-
color:
|
|
1004
|
+
color: var(--fg-6);
|
|
843
1005
|
}
|
|
844
1006
|
.thinking-indicator {
|
|
845
1007
|
display: inline-block;
|
|
@@ -847,7 +1009,7 @@ var renderWebUiHtml = (options) => {
|
|
|
847
1009
|
font-size: 20px;
|
|
848
1010
|
line-height: 1;
|
|
849
1011
|
vertical-align: middle;
|
|
850
|
-
color:
|
|
1012
|
+
color: var(--fg);
|
|
851
1013
|
opacity: 0.5;
|
|
852
1014
|
}
|
|
853
1015
|
.thinking-status {
|
|
@@ -855,13 +1017,13 @@ var renderWebUiHtml = (options) => {
|
|
|
855
1017
|
align-items: center;
|
|
856
1018
|
gap: 8px;
|
|
857
1019
|
margin-top: 2px;
|
|
858
|
-
color:
|
|
1020
|
+
color: var(--fg-tool);
|
|
859
1021
|
font-size: 14px;
|
|
860
1022
|
line-height: 1.65;
|
|
861
1023
|
font-weight: 400;
|
|
862
1024
|
}
|
|
863
1025
|
.thinking-status-label {
|
|
864
|
-
color:
|
|
1026
|
+
color: var(--fg-tool);
|
|
865
1027
|
font-size: 14px;
|
|
866
1028
|
line-height: 1.65;
|
|
867
1029
|
font-weight: 400;
|
|
@@ -892,26 +1054,26 @@ var renderWebUiHtml = (options) => {
|
|
|
892
1054
|
right: 0;
|
|
893
1055
|
bottom: 100%;
|
|
894
1056
|
height: 48px;
|
|
895
|
-
background: linear-gradient(to top,
|
|
1057
|
+
background: linear-gradient(to top, var(--bg) 0%, transparent 100%);
|
|
896
1058
|
pointer-events: none;
|
|
897
1059
|
}
|
|
898
1060
|
.composer-inner { max-width: 680px; margin: 0 auto; }
|
|
899
1061
|
.composer-shell {
|
|
900
|
-
background:
|
|
901
|
-
border: 1px solid
|
|
1062
|
+
background: var(--bg-alt);
|
|
1063
|
+
border: 1px solid var(--border-3);
|
|
902
1064
|
border-radius: 24px;
|
|
903
1065
|
display: flex;
|
|
904
1066
|
align-items: end;
|
|
905
1067
|
padding: 4px 6px 4px 6px;
|
|
906
1068
|
transition: border-color 0.15s;
|
|
907
1069
|
}
|
|
908
|
-
.composer-shell:focus-within { border-color:
|
|
1070
|
+
.composer-shell:focus-within { border-color: var(--border-focus); }
|
|
909
1071
|
.composer-input {
|
|
910
1072
|
flex: 1;
|
|
911
1073
|
background: transparent;
|
|
912
1074
|
border: 0;
|
|
913
1075
|
outline: none;
|
|
914
|
-
color:
|
|
1076
|
+
color: var(--fg);
|
|
915
1077
|
min-height: 40px;
|
|
916
1078
|
max-height: 200px;
|
|
917
1079
|
resize: none;
|
|
@@ -920,14 +1082,14 @@ var renderWebUiHtml = (options) => {
|
|
|
920
1082
|
line-height: 1.5;
|
|
921
1083
|
margin-top: -4px;
|
|
922
1084
|
}
|
|
923
|
-
.composer-input::placeholder { color:
|
|
1085
|
+
.composer-input::placeholder { color: var(--fg-7); }
|
|
924
1086
|
.send-btn {
|
|
925
1087
|
width: 32px;
|
|
926
1088
|
height: 32px;
|
|
927
|
-
background:
|
|
1089
|
+
background: var(--accent);
|
|
928
1090
|
border: 0;
|
|
929
1091
|
border-radius: 50%;
|
|
930
|
-
color:
|
|
1092
|
+
color: var(--accent-fg);
|
|
931
1093
|
cursor: pointer;
|
|
932
1094
|
display: grid;
|
|
933
1095
|
place-items: center;
|
|
@@ -935,21 +1097,79 @@ var renderWebUiHtml = (options) => {
|
|
|
935
1097
|
margin-bottom: 2px;
|
|
936
1098
|
transition: background 0.15s, opacity 0.15s;
|
|
937
1099
|
}
|
|
938
|
-
.send-btn:hover { background:
|
|
1100
|
+
.send-btn:hover { background: var(--accent-hover); }
|
|
939
1101
|
.send-btn.stop-mode {
|
|
940
|
-
background:
|
|
941
|
-
color:
|
|
1102
|
+
background: var(--stop-bg);
|
|
1103
|
+
color: var(--stop-fg);
|
|
942
1104
|
}
|
|
943
|
-
.send-btn.stop-mode:hover { background:
|
|
1105
|
+
.send-btn.stop-mode:hover { background: var(--stop-hover); }
|
|
944
1106
|
.send-btn:disabled { opacity: 0.2; cursor: default; }
|
|
945
|
-
.send-btn:disabled:hover { background:
|
|
1107
|
+
.send-btn:disabled:hover { background: var(--accent); }
|
|
1108
|
+
.send-btn-wrapper {
|
|
1109
|
+
position: relative;
|
|
1110
|
+
width: 36px;
|
|
1111
|
+
height: 36px;
|
|
1112
|
+
display: grid;
|
|
1113
|
+
place-items: center;
|
|
1114
|
+
flex-shrink: 0;
|
|
1115
|
+
margin-bottom: 0;
|
|
1116
|
+
}
|
|
1117
|
+
.send-btn-wrapper .send-btn {
|
|
1118
|
+
margin-bottom: 0;
|
|
1119
|
+
}
|
|
1120
|
+
.context-ring {
|
|
1121
|
+
position: absolute;
|
|
1122
|
+
inset: 0;
|
|
1123
|
+
width: 36px;
|
|
1124
|
+
height: 36px;
|
|
1125
|
+
pointer-events: none;
|
|
1126
|
+
transform: rotate(-90deg);
|
|
1127
|
+
}
|
|
1128
|
+
.context-ring-fill {
|
|
1129
|
+
fill: none;
|
|
1130
|
+
stroke: var(--bg-alt);
|
|
1131
|
+
stroke-width: 3;
|
|
1132
|
+
stroke-linecap: butt;
|
|
1133
|
+
transition: stroke-dashoffset 0.4s ease, stroke 0.3s ease;
|
|
1134
|
+
}
|
|
1135
|
+
.send-btn-wrapper.stop-mode .context-ring-fill {
|
|
1136
|
+
stroke: var(--fg-3);
|
|
1137
|
+
}
|
|
1138
|
+
.context-ring-fill.warning {
|
|
1139
|
+
stroke: #e5a33d;
|
|
1140
|
+
}
|
|
1141
|
+
.context-ring-fill.critical {
|
|
1142
|
+
stroke: #e55d4a;
|
|
1143
|
+
}
|
|
1144
|
+
.context-tooltip {
|
|
1145
|
+
position: absolute;
|
|
1146
|
+
bottom: calc(100% + 8px);
|
|
1147
|
+
right: 0;
|
|
1148
|
+
background: var(--bg-elevated);
|
|
1149
|
+
border: 1px solid var(--border-3);
|
|
1150
|
+
border-radius: 8px;
|
|
1151
|
+
padding: 6px 10px;
|
|
1152
|
+
font-size: 12px;
|
|
1153
|
+
color: var(--fg-2);
|
|
1154
|
+
white-space: nowrap;
|
|
1155
|
+
pointer-events: none;
|
|
1156
|
+
opacity: 0;
|
|
1157
|
+
transform: translateY(4px);
|
|
1158
|
+
transition: opacity 0.15s, transform 0.15s;
|
|
1159
|
+
z-index: 10;
|
|
1160
|
+
}
|
|
1161
|
+
.send-btn-wrapper:hover .context-tooltip,
|
|
1162
|
+
.send-btn-wrapper:focus-within .context-tooltip {
|
|
1163
|
+
opacity: 1;
|
|
1164
|
+
transform: translateY(0);
|
|
1165
|
+
}
|
|
946
1166
|
.attach-btn {
|
|
947
1167
|
width: 32px;
|
|
948
1168
|
height: 32px;
|
|
949
|
-
background:
|
|
1169
|
+
background: var(--surface-5);
|
|
950
1170
|
border: 0;
|
|
951
1171
|
border-radius: 50%;
|
|
952
|
-
color:
|
|
1172
|
+
color: var(--fg-3);
|
|
953
1173
|
cursor: pointer;
|
|
954
1174
|
display: grid;
|
|
955
1175
|
place-items: center;
|
|
@@ -958,7 +1178,7 @@ var renderWebUiHtml = (options) => {
|
|
|
958
1178
|
margin-right: 8px;
|
|
959
1179
|
transition: color 0.15s, background 0.15s;
|
|
960
1180
|
}
|
|
961
|
-
.attach-btn:hover { color:
|
|
1181
|
+
.attach-btn:hover { color: var(--fg); background: var(--surface-8); }
|
|
962
1182
|
.attachment-preview {
|
|
963
1183
|
display: flex;
|
|
964
1184
|
gap: 8px;
|
|
@@ -969,12 +1189,12 @@ var renderWebUiHtml = (options) => {
|
|
|
969
1189
|
display: inline-flex;
|
|
970
1190
|
align-items: center;
|
|
971
1191
|
gap: 6px;
|
|
972
|
-
background:
|
|
973
|
-
border: 1px solid
|
|
1192
|
+
background: var(--chip-bg);
|
|
1193
|
+
border: 1px solid var(--border-4);
|
|
974
1194
|
border-radius: 9999px;
|
|
975
1195
|
padding: 4px 10px 4px 6px;
|
|
976
1196
|
font-size: 11px;
|
|
977
|
-
color:
|
|
1197
|
+
color: var(--fg-4);
|
|
978
1198
|
max-width: 200px;
|
|
979
1199
|
cursor: pointer;
|
|
980
1200
|
backdrop-filter: blur(6px);
|
|
@@ -982,9 +1202,9 @@ var renderWebUiHtml = (options) => {
|
|
|
982
1202
|
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
983
1203
|
}
|
|
984
1204
|
.attachment-chip:hover {
|
|
985
|
-
color:
|
|
986
|
-
border-color:
|
|
987
|
-
background:
|
|
1205
|
+
color: var(--fg);
|
|
1206
|
+
border-color: var(--border-hover);
|
|
1207
|
+
background: var(--chip-bg-hover);
|
|
988
1208
|
}
|
|
989
1209
|
.attachment-chip img {
|
|
990
1210
|
width: 20px;
|
|
@@ -998,7 +1218,7 @@ var renderWebUiHtml = (options) => {
|
|
|
998
1218
|
width: 20px;
|
|
999
1219
|
height: 20px;
|
|
1000
1220
|
border-radius: 50%;
|
|
1001
|
-
background:
|
|
1221
|
+
background: var(--surface-6);
|
|
1002
1222
|
display: grid;
|
|
1003
1223
|
place-items: center;
|
|
1004
1224
|
font-size: 11px;
|
|
@@ -1006,13 +1226,13 @@ var renderWebUiHtml = (options) => {
|
|
|
1006
1226
|
}
|
|
1007
1227
|
.attachment-chip .remove-attachment {
|
|
1008
1228
|
cursor: pointer;
|
|
1009
|
-
color:
|
|
1229
|
+
color: var(--fg-6);
|
|
1010
1230
|
font-size: 14px;
|
|
1011
1231
|
margin-left: 2px;
|
|
1012
1232
|
line-height: 1;
|
|
1013
1233
|
transition: color 0.15s;
|
|
1014
1234
|
}
|
|
1015
|
-
.attachment-chip .remove-attachment:hover { color:
|
|
1235
|
+
.attachment-chip .remove-attachment:hover { color: var(--fg-strong); }
|
|
1016
1236
|
.attachment-chip .filename { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100px; }
|
|
1017
1237
|
.user-bubble .user-file-attachments {
|
|
1018
1238
|
display: flex;
|
|
@@ -1042,7 +1262,7 @@ var renderWebUiHtml = (options) => {
|
|
|
1042
1262
|
transition: background 0.25s ease, backdrop-filter 0.25s ease;
|
|
1043
1263
|
}
|
|
1044
1264
|
.lightbox.active {
|
|
1045
|
-
background:
|
|
1265
|
+
background: var(--lightbox-bg);
|
|
1046
1266
|
backdrop-filter: blur(8px);
|
|
1047
1267
|
}
|
|
1048
1268
|
.lightbox img {
|
|
@@ -1062,16 +1282,16 @@ var renderWebUiHtml = (options) => {
|
|
|
1062
1282
|
display: inline-flex;
|
|
1063
1283
|
align-items: center;
|
|
1064
1284
|
gap: 4px;
|
|
1065
|
-
background:
|
|
1285
|
+
background: var(--file-badge-bg);
|
|
1066
1286
|
border-radius: 6px;
|
|
1067
1287
|
padding: 4px 8px;
|
|
1068
1288
|
font-size: 12px;
|
|
1069
|
-
color:
|
|
1289
|
+
color: var(--file-badge-fg);
|
|
1070
1290
|
}
|
|
1071
1291
|
.drag-overlay {
|
|
1072
1292
|
position: fixed;
|
|
1073
1293
|
inset: 0;
|
|
1074
|
-
background:
|
|
1294
|
+
background: var(--backdrop);
|
|
1075
1295
|
z-index: 9999;
|
|
1076
1296
|
display: none;
|
|
1077
1297
|
align-items: center;
|
|
@@ -1080,15 +1300,15 @@ var renderWebUiHtml = (options) => {
|
|
|
1080
1300
|
}
|
|
1081
1301
|
.drag-overlay.active { display: flex; }
|
|
1082
1302
|
.drag-overlay-inner {
|
|
1083
|
-
border: 2px dashed
|
|
1303
|
+
border: 2px dashed var(--border-drag);
|
|
1084
1304
|
border-radius: 16px;
|
|
1085
1305
|
padding: 40px 60px;
|
|
1086
|
-
color:
|
|
1306
|
+
color: var(--fg-strong);
|
|
1087
1307
|
font-size: 16px;
|
|
1088
1308
|
}
|
|
1089
1309
|
.disclaimer {
|
|
1090
1310
|
text-align: center;
|
|
1091
|
-
color:
|
|
1311
|
+
color: var(--fg-8);
|
|
1092
1312
|
font-size: 12px;
|
|
1093
1313
|
margin-top: 10px;
|
|
1094
1314
|
}
|
|
@@ -1103,10 +1323,10 @@ var renderWebUiHtml = (options) => {
|
|
|
1103
1323
|
align-items: center;
|
|
1104
1324
|
gap: 6px;
|
|
1105
1325
|
font-size: 11px;
|
|
1106
|
-
color:
|
|
1326
|
+
color: var(--fg-4);
|
|
1107
1327
|
text-decoration: none;
|
|
1108
|
-
background:
|
|
1109
|
-
border: 1px solid
|
|
1328
|
+
background: var(--chip-bg);
|
|
1329
|
+
border: 1px solid var(--border-4);
|
|
1110
1330
|
border-radius: 9999px;
|
|
1111
1331
|
padding: 4px 10px 4px 6px;
|
|
1112
1332
|
backdrop-filter: blur(6px);
|
|
@@ -1114,9 +1334,9 @@ var renderWebUiHtml = (options) => {
|
|
|
1114
1334
|
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
1115
1335
|
}
|
|
1116
1336
|
.poncho-badge:hover {
|
|
1117
|
-
color:
|
|
1118
|
-
border-color:
|
|
1119
|
-
background:
|
|
1337
|
+
color: var(--fg);
|
|
1338
|
+
border-color: var(--border-hover);
|
|
1339
|
+
background: var(--chip-bg-hover);
|
|
1120
1340
|
}
|
|
1121
1341
|
.poncho-badge-avatar {
|
|
1122
1342
|
width: 16px;
|
|
@@ -1130,8 +1350,8 @@ var renderWebUiHtml = (options) => {
|
|
|
1130
1350
|
/* Scrollbar */
|
|
1131
1351
|
::-webkit-scrollbar { width: 6px; }
|
|
1132
1352
|
::-webkit-scrollbar-track { background: transparent; }
|
|
1133
|
-
::-webkit-scrollbar-thumb { background:
|
|
1134
|
-
::-webkit-scrollbar-thumb:hover { background:
|
|
1353
|
+
::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; }
|
|
1354
|
+
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); }
|
|
1135
1355
|
|
|
1136
1356
|
/* Mobile */
|
|
1137
1357
|
@media (max-width: 768px) {
|
|
@@ -1161,7 +1381,7 @@ var renderWebUiHtml = (options) => {
|
|
|
1161
1381
|
.sidebar-backdrop {
|
|
1162
1382
|
position: fixed;
|
|
1163
1383
|
inset: 0;
|
|
1164
|
-
background:
|
|
1384
|
+
background: var(--backdrop);
|
|
1165
1385
|
z-index: 50;
|
|
1166
1386
|
backdrop-filter: blur(2px);
|
|
1167
1387
|
-webkit-backdrop-filter: blur(2px);
|
|
@@ -1236,9 +1456,15 @@ var renderWebUiHtml = (options) => {
|
|
|
1236
1456
|
</button>
|
|
1237
1457
|
<input id="file-input" type="file" multiple accept="image/*,video/*,application/pdf,.txt,.csv,.json,.html" style="display:none" />
|
|
1238
1458
|
<textarea id="prompt" class="composer-input" placeholder="Send a message..." rows="1"></textarea>
|
|
1239
|
-
<
|
|
1240
|
-
<svg
|
|
1241
|
-
|
|
1459
|
+
<div class="send-btn-wrapper" id="send-btn-wrapper">
|
|
1460
|
+
<svg class="context-ring" viewBox="0 0 36 36">
|
|
1461
|
+
<circle class="context-ring-fill" id="context-ring-fill" cx="18" cy="18" r="14.5" />
|
|
1462
|
+
</svg>
|
|
1463
|
+
<button id="send" class="send-btn" type="submit">
|
|
1464
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 12V4M4 7l4-4 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
1465
|
+
</button>
|
|
1466
|
+
<div class="context-tooltip" id="context-tooltip"></div>
|
|
1467
|
+
</div>
|
|
1242
1468
|
</div>
|
|
1243
1469
|
</div>
|
|
1244
1470
|
</form>
|
|
@@ -1270,6 +1496,8 @@ var renderWebUiHtml = (options) => {
|
|
|
1270
1496
|
confirmDeleteId: null,
|
|
1271
1497
|
approvalRequestsInFlight: {},
|
|
1272
1498
|
pendingFiles: [],
|
|
1499
|
+
contextTokens: 0,
|
|
1500
|
+
contextWindow: 0,
|
|
1273
1501
|
};
|
|
1274
1502
|
|
|
1275
1503
|
const agentInitial = document.body.dataset.agentInitial || "A";
|
|
@@ -1297,12 +1525,41 @@ var renderWebUiHtml = (options) => {
|
|
|
1297
1525
|
attachmentPreview: $("attachment-preview"),
|
|
1298
1526
|
dragOverlay: $("drag-overlay"),
|
|
1299
1527
|
lightbox: $("lightbox"),
|
|
1528
|
+
contextRingFill: $("context-ring-fill"),
|
|
1529
|
+
contextTooltip: $("context-tooltip"),
|
|
1530
|
+
sendBtnWrapper: $("send-btn-wrapper"),
|
|
1300
1531
|
};
|
|
1301
1532
|
const sendIconMarkup =
|
|
1302
1533
|
'<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 12V4M4 7l4-4 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
1303
1534
|
const stopIconMarkup =
|
|
1304
1535
|
'<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="4" y="4" width="8" height="8" rx="2" fill="currentColor"/></svg>';
|
|
1305
1536
|
|
|
1537
|
+
const CONTEXT_RING_CIRCUMFERENCE = 2 * Math.PI * 14.5;
|
|
1538
|
+
const formatTokenCount = (n) => {
|
|
1539
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\\.0$/, "") + "M";
|
|
1540
|
+
if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\\.0$/, "") + "k";
|
|
1541
|
+
return String(n);
|
|
1542
|
+
};
|
|
1543
|
+
const updateContextRing = () => {
|
|
1544
|
+
const ring = elements.contextRingFill;
|
|
1545
|
+
const tooltip = elements.contextTooltip;
|
|
1546
|
+
if (!ring || !tooltip) return;
|
|
1547
|
+
if (state.contextWindow <= 0) {
|
|
1548
|
+
ring.style.strokeDasharray = String(CONTEXT_RING_CIRCUMFERENCE);
|
|
1549
|
+
ring.style.strokeDashoffset = String(CONTEXT_RING_CIRCUMFERENCE);
|
|
1550
|
+
tooltip.textContent = "";
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
const ratio = Math.min(state.contextTokens / state.contextWindow, 1);
|
|
1554
|
+
const offset = CONTEXT_RING_CIRCUMFERENCE * (1 - ratio);
|
|
1555
|
+
ring.style.strokeDasharray = String(CONTEXT_RING_CIRCUMFERENCE);
|
|
1556
|
+
ring.style.strokeDashoffset = String(offset);
|
|
1557
|
+
ring.classList.toggle("warning", ratio >= 0.7 && ratio < 0.9);
|
|
1558
|
+
ring.classList.toggle("critical", ratio >= 0.9);
|
|
1559
|
+
const pct = (ratio * 100).toFixed(1).replace(/\\.0$/, "");
|
|
1560
|
+
tooltip.textContent = formatTokenCount(state.contextTokens) + " / " + formatTokenCount(state.contextWindow) + " tokens (" + pct + "%)";
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1306
1563
|
const pushConversationUrl = (conversationId) => {
|
|
1307
1564
|
const target = conversationId ? "/c/" + encodeURIComponent(conversationId) : "/";
|
|
1308
1565
|
if (window.location.pathname !== target) {
|
|
@@ -1622,6 +1879,9 @@ var renderWebUiHtml = (options) => {
|
|
|
1622
1879
|
if (state.activeConversationId === c.conversationId) {
|
|
1623
1880
|
state.activeConversationId = null;
|
|
1624
1881
|
state.activeMessages = [];
|
|
1882
|
+
state.contextTokens = 0;
|
|
1883
|
+
state.contextWindow = 0;
|
|
1884
|
+
updateContextRing();
|
|
1625
1885
|
pushConversationUrl(null);
|
|
1626
1886
|
elements.chatTitle.textContent = "";
|
|
1627
1887
|
renderMessages([]);
|
|
@@ -1718,8 +1978,14 @@ var renderWebUiHtml = (options) => {
|
|
|
1718
1978
|
} else if (shouldRenderEmptyStreamingIndicator) {
|
|
1719
1979
|
content.appendChild(createThinkingIndicator(getThinkingStatusLabel(m)));
|
|
1720
1980
|
} else {
|
|
1721
|
-
//
|
|
1722
|
-
|
|
1981
|
+
// Merge stored sections (persisted) with live sections (from
|
|
1982
|
+
// an active stream). For normal messages only one source
|
|
1983
|
+
// exists; for liveOnly reconnects both contribute.
|
|
1984
|
+
const storedSections = (m.metadata && m.metadata.sections) || [];
|
|
1985
|
+
const liveSections = m._sections || [];
|
|
1986
|
+
const sections = liveSections.length > 0 && storedSections.length > 0
|
|
1987
|
+
? storedSections.concat(liveSections)
|
|
1988
|
+
: liveSections.length > 0 ? liveSections : (storedSections.length > 0 ? storedSections : null);
|
|
1723
1989
|
const pendingApprovals = Array.isArray(m._pendingApprovals) ? m._pendingApprovals : [];
|
|
1724
1990
|
|
|
1725
1991
|
if (sections && sections.length > 0) {
|
|
@@ -1860,11 +2126,24 @@ var renderWebUiHtml = (options) => {
|
|
|
1860
2126
|
payload.conversation.messages || [],
|
|
1861
2127
|
payload.conversation.pendingApprovals || payload.pendingApprovals || [],
|
|
1862
2128
|
);
|
|
2129
|
+
state.contextTokens = 0;
|
|
2130
|
+
state.contextWindow = 0;
|
|
2131
|
+
updateContextRing();
|
|
1863
2132
|
renderMessages(state.activeMessages, false, { forceScrollBottom: true });
|
|
1864
2133
|
elements.prompt.focus();
|
|
2134
|
+
if (payload.hasActiveRun && !state.isStreaming) {
|
|
2135
|
+
setStreaming(true);
|
|
2136
|
+
streamConversationEvents(conversationId, { liveOnly: true }).finally(() => {
|
|
2137
|
+
if (state.activeConversationId === conversationId) {
|
|
2138
|
+
setStreaming(false);
|
|
2139
|
+
renderMessages(state.activeMessages, false);
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
1865
2143
|
};
|
|
1866
2144
|
|
|
1867
|
-
const streamConversationEvents = (conversationId) => {
|
|
2145
|
+
const streamConversationEvents = (conversationId, options) => {
|
|
2146
|
+
const liveOnly = options && options.liveOnly;
|
|
1868
2147
|
return new Promise((resolve) => {
|
|
1869
2148
|
const localMessages = state.activeMessages || [];
|
|
1870
2149
|
const renderIfActiveConversation = (streaming) => {
|
|
@@ -1883,20 +2162,36 @@ var renderWebUiHtml = (options) => {
|
|
|
1883
2162
|
_currentText: "",
|
|
1884
2163
|
_currentTools: [],
|
|
1885
2164
|
_pendingApprovals: [],
|
|
2165
|
+
_activeActivities: [],
|
|
1886
2166
|
metadata: { toolActivity: [] },
|
|
1887
2167
|
};
|
|
1888
2168
|
localMessages.push(assistantMessage);
|
|
1889
2169
|
state.activeMessages = localMessages;
|
|
1890
2170
|
}
|
|
1891
|
-
if (
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2171
|
+
if (liveOnly) {
|
|
2172
|
+
// Live-only mode: keep metadata.sections intact (the stored
|
|
2173
|
+
// base content) and start _sections empty so it only collects
|
|
2174
|
+
// NEW sections from live events. The renderer merges both.
|
|
2175
|
+
assistantMessage._sections = [];
|
|
2176
|
+
assistantMessage._currentText = "";
|
|
2177
|
+
assistantMessage._currentTools = [];
|
|
2178
|
+
if (!assistantMessage._activeActivities) assistantMessage._activeActivities = [];
|
|
2179
|
+
if (!assistantMessage._pendingApprovals) assistantMessage._pendingApprovals = [];
|
|
2180
|
+
if (!assistantMessage.metadata) assistantMessage.metadata = {};
|
|
2181
|
+
if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
|
|
2182
|
+
} else {
|
|
2183
|
+
// Full replay mode: reset transient state so replayed events
|
|
2184
|
+
// rebuild from scratch (the buffer has the full event history).
|
|
2185
|
+
assistantMessage.content = "";
|
|
2186
|
+
assistantMessage._sections = [];
|
|
2187
|
+
assistantMessage._currentText = "";
|
|
2188
|
+
assistantMessage._currentTools = [];
|
|
2189
|
+
assistantMessage._activeActivities = [];
|
|
2190
|
+
assistantMessage._pendingApprovals = [];
|
|
2191
|
+
assistantMessage.metadata = { toolActivity: [] };
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
const url = "/api/conversations/" + encodeURIComponent(conversationId) + "/events" + (liveOnly ? "?live_only=true" : "");
|
|
1900
2195
|
fetch(url, { credentials: "include" }).then((response) => {
|
|
1901
2196
|
if (!response.ok || !response.body) {
|
|
1902
2197
|
resolve(undefined);
|
|
@@ -1917,6 +2212,11 @@ var renderWebUiHtml = (options) => {
|
|
|
1917
2212
|
if (eventName === "stream:end") {
|
|
1918
2213
|
return;
|
|
1919
2214
|
}
|
|
2215
|
+
if (eventName === "run:started") {
|
|
2216
|
+
if (typeof payload.contextWindow === "number" && payload.contextWindow > 0) {
|
|
2217
|
+
state.contextWindow = payload.contextWindow;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
1920
2220
|
if (eventName === "model:chunk") {
|
|
1921
2221
|
const chunk = String(payload.content || "");
|
|
1922
2222
|
if (assistantMessage._currentTools.length > 0 && chunk.length > 0) {
|
|
@@ -1930,6 +2230,12 @@ var renderWebUiHtml = (options) => {
|
|
|
1930
2230
|
assistantMessage._currentText += chunk;
|
|
1931
2231
|
renderIfActiveConversation(true);
|
|
1932
2232
|
}
|
|
2233
|
+
if (eventName === "model:response") {
|
|
2234
|
+
if (typeof payload.usage?.input === "number") {
|
|
2235
|
+
state.contextTokens = payload.usage.input;
|
|
2236
|
+
updateContextRing();
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
1933
2239
|
if (eventName === "tool:started") {
|
|
1934
2240
|
const toolName = payload.tool || "tool";
|
|
1935
2241
|
const startedActivity = addActiveActivityFromToolStart(
|
|
@@ -2002,6 +2308,75 @@ var renderWebUiHtml = (options) => {
|
|
|
2002
2308
|
assistantMessage.metadata.toolActivity.push(toolText);
|
|
2003
2309
|
renderIfActiveConversation(true);
|
|
2004
2310
|
}
|
|
2311
|
+
if (eventName === "tool:approval:required") {
|
|
2312
|
+
const toolName = payload.tool || "tool";
|
|
2313
|
+
const activeActivity = removeActiveActivityForTool(
|
|
2314
|
+
assistantMessage,
|
|
2315
|
+
toolName,
|
|
2316
|
+
);
|
|
2317
|
+
const detailFromPayload = describeToolStart(payload);
|
|
2318
|
+
const detail =
|
|
2319
|
+
(activeActivity && typeof activeActivity.detail === "string"
|
|
2320
|
+
? activeActivity.detail.trim()
|
|
2321
|
+
: "") ||
|
|
2322
|
+
(detailFromPayload && typeof detailFromPayload.detail === "string"
|
|
2323
|
+
? detailFromPayload.detail.trim()
|
|
2324
|
+
: "");
|
|
2325
|
+
const toolText =
|
|
2326
|
+
"- approval required \\x60" +
|
|
2327
|
+
toolName +
|
|
2328
|
+
"\\x60" +
|
|
2329
|
+
(detail ? " (" + detail + ")" : "");
|
|
2330
|
+
assistantMessage._currentTools.push(toolText);
|
|
2331
|
+
assistantMessage.metadata.toolActivity.push(toolText);
|
|
2332
|
+
const approvalId =
|
|
2333
|
+
typeof payload.approvalId === "string" ? payload.approvalId : "";
|
|
2334
|
+
if (approvalId) {
|
|
2335
|
+
const preview = safeJsonPreview(payload.input ?? {});
|
|
2336
|
+
const inputPreview = preview.length > 600 ? preview.slice(0, 600) + "..." : preview;
|
|
2337
|
+
if (!Array.isArray(assistantMessage._pendingApprovals)) {
|
|
2338
|
+
assistantMessage._pendingApprovals = [];
|
|
2339
|
+
}
|
|
2340
|
+
const exists = assistantMessage._pendingApprovals.some(
|
|
2341
|
+
(req) => req.approvalId === approvalId,
|
|
2342
|
+
);
|
|
2343
|
+
if (!exists) {
|
|
2344
|
+
assistantMessage._pendingApprovals.push({
|
|
2345
|
+
approvalId,
|
|
2346
|
+
tool: toolName,
|
|
2347
|
+
inputPreview,
|
|
2348
|
+
state: "pending",
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
renderIfActiveConversation(true);
|
|
2353
|
+
}
|
|
2354
|
+
if (eventName === "tool:approval:granted") {
|
|
2355
|
+
const toolText = "- approval granted";
|
|
2356
|
+
assistantMessage._currentTools.push(toolText);
|
|
2357
|
+
assistantMessage.metadata.toolActivity.push(toolText);
|
|
2358
|
+
const approvalId =
|
|
2359
|
+
typeof payload.approvalId === "string" ? payload.approvalId : "";
|
|
2360
|
+
if (approvalId && Array.isArray(assistantMessage._pendingApprovals)) {
|
|
2361
|
+
assistantMessage._pendingApprovals = assistantMessage._pendingApprovals.filter(
|
|
2362
|
+
(req) => req.approvalId !== approvalId,
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
renderIfActiveConversation(true);
|
|
2366
|
+
}
|
|
2367
|
+
if (eventName === "tool:approval:denied") {
|
|
2368
|
+
const toolText = "- approval denied";
|
|
2369
|
+
assistantMessage._currentTools.push(toolText);
|
|
2370
|
+
assistantMessage.metadata.toolActivity.push(toolText);
|
|
2371
|
+
const approvalId =
|
|
2372
|
+
typeof payload.approvalId === "string" ? payload.approvalId : "";
|
|
2373
|
+
if (approvalId && Array.isArray(assistantMessage._pendingApprovals)) {
|
|
2374
|
+
assistantMessage._pendingApprovals = assistantMessage._pendingApprovals.filter(
|
|
2375
|
+
(req) => req.approvalId !== approvalId,
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
renderIfActiveConversation(true);
|
|
2379
|
+
}
|
|
2005
2380
|
if (eventName === "run:completed") {
|
|
2006
2381
|
assistantMessage._activeActivities = [];
|
|
2007
2382
|
if (
|
|
@@ -2048,9 +2423,22 @@ var renderWebUiHtml = (options) => {
|
|
|
2048
2423
|
}
|
|
2049
2424
|
if (eventName === "run:error") {
|
|
2050
2425
|
assistantMessage._activeActivities = [];
|
|
2426
|
+
if (assistantMessage._currentTools.length > 0) {
|
|
2427
|
+
assistantMessage._sections.push({
|
|
2428
|
+
type: "tools",
|
|
2429
|
+
content: assistantMessage._currentTools,
|
|
2430
|
+
});
|
|
2431
|
+
assistantMessage._currentTools = [];
|
|
2432
|
+
}
|
|
2433
|
+
if (assistantMessage._currentText.length > 0) {
|
|
2434
|
+
assistantMessage._sections.push({
|
|
2435
|
+
type: "text",
|
|
2436
|
+
content: assistantMessage._currentText,
|
|
2437
|
+
});
|
|
2438
|
+
assistantMessage._currentText = "";
|
|
2439
|
+
}
|
|
2051
2440
|
const errMsg =
|
|
2052
2441
|
payload.error?.message || "Something went wrong";
|
|
2053
|
-
assistantMessage.content = "";
|
|
2054
2442
|
assistantMessage._error = errMsg;
|
|
2055
2443
|
renderIfActiveConversation(false);
|
|
2056
2444
|
}
|
|
@@ -2123,6 +2511,9 @@ var renderWebUiHtml = (options) => {
|
|
|
2123
2511
|
elements.send.disabled = value ? !canStop : false;
|
|
2124
2512
|
elements.send.innerHTML = value ? stopIconMarkup : sendIconMarkup;
|
|
2125
2513
|
elements.send.classList.toggle("stop-mode", value);
|
|
2514
|
+
if (elements.sendBtnWrapper) {
|
|
2515
|
+
elements.sendBtnWrapper.classList.toggle("stop-mode", value);
|
|
2516
|
+
}
|
|
2126
2517
|
elements.send.setAttribute("aria-label", value ? "Stop response" : "Send message");
|
|
2127
2518
|
elements.send.setAttribute(
|
|
2128
2519
|
"title",
|
|
@@ -2495,8 +2886,17 @@ var renderWebUiHtml = (options) => {
|
|
|
2495
2886
|
}
|
|
2496
2887
|
if (eventName === "run:started") {
|
|
2497
2888
|
state.activeStreamRunId = typeof payload.runId === "string" ? payload.runId : null;
|
|
2889
|
+
if (typeof payload.contextWindow === "number" && payload.contextWindow > 0) {
|
|
2890
|
+
state.contextWindow = payload.contextWindow;
|
|
2891
|
+
}
|
|
2498
2892
|
setStreaming(state.isStreaming);
|
|
2499
2893
|
}
|
|
2894
|
+
if (eventName === "model:response") {
|
|
2895
|
+
if (typeof payload.usage?.input === "number") {
|
|
2896
|
+
state.contextTokens = payload.usage.input;
|
|
2897
|
+
updateContextRing();
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2500
2900
|
if (eventName === "tool:started") {
|
|
2501
2901
|
const toolName = payload.tool || "tool";
|
|
2502
2902
|
const startedActivity = addActiveActivityFromToolStart(
|
|
@@ -2659,9 +3059,8 @@ var renderWebUiHtml = (options) => {
|
|
|
2659
3059
|
renderIfActiveConversation(false);
|
|
2660
3060
|
}
|
|
2661
3061
|
if (eventName === "run:error") {
|
|
2662
|
-
|
|
3062
|
+
finalizeAssistantMessage();
|
|
2663
3063
|
const errMsg = payload.error?.message || "Something went wrong";
|
|
2664
|
-
assistantMessage.content = "";
|
|
2665
3064
|
assistantMessage._error = errMsg;
|
|
2666
3065
|
renderIfActiveConversation(false);
|
|
2667
3066
|
}
|
|
@@ -2764,6 +3163,9 @@ var renderWebUiHtml = (options) => {
|
|
|
2764
3163
|
state.activeConversationId = null;
|
|
2765
3164
|
state.activeMessages = [];
|
|
2766
3165
|
state.confirmDeleteId = null;
|
|
3166
|
+
state.contextTokens = 0;
|
|
3167
|
+
state.contextWindow = 0;
|
|
3168
|
+
updateContextRing();
|
|
2767
3169
|
pushConversationUrl(null);
|
|
2768
3170
|
elements.chatTitle.textContent = "";
|
|
2769
3171
|
renderMessages([]);
|
|
@@ -2801,6 +3203,9 @@ var renderWebUiHtml = (options) => {
|
|
|
2801
3203
|
state.confirmDeleteId = null;
|
|
2802
3204
|
state.conversations = [];
|
|
2803
3205
|
state.csrfToken = "";
|
|
3206
|
+
state.contextTokens = 0;
|
|
3207
|
+
state.contextWindow = 0;
|
|
3208
|
+
updateContextRing();
|
|
2804
3209
|
await requireAuth();
|
|
2805
3210
|
});
|
|
2806
3211
|
|
|
@@ -3005,6 +3410,9 @@ var renderWebUiHtml = (options) => {
|
|
|
3005
3410
|
} else {
|
|
3006
3411
|
state.activeConversationId = null;
|
|
3007
3412
|
state.activeMessages = [];
|
|
3413
|
+
state.contextTokens = 0;
|
|
3414
|
+
state.contextWindow = 0;
|
|
3415
|
+
updateContextRing();
|
|
3008
3416
|
elements.chatTitle.textContent = "";
|
|
3009
3417
|
renderMessages([]);
|
|
3010
3418
|
renderConversationList();
|
|
@@ -3046,6 +3454,7 @@ var renderWebUiHtml = (options) => {
|
|
|
3046
3454
|
await createConversation();
|
|
3047
3455
|
}
|
|
3048
3456
|
autoResizePrompt();
|
|
3457
|
+
updateContextRing();
|
|
3049
3458
|
elements.prompt.focus();
|
|
3050
3459
|
})();
|
|
3051
3460
|
|
|
@@ -3262,6 +3671,631 @@ var renderWebUiHtml = (options) => {
|
|
|
3262
3671
|
</html>`;
|
|
3263
3672
|
};
|
|
3264
3673
|
|
|
3674
|
+
// src/api-docs.ts
|
|
3675
|
+
var buildOpenApiSpec = (options) => ({
|
|
3676
|
+
openapi: "3.1.0",
|
|
3677
|
+
info: {
|
|
3678
|
+
title: `${options.agentName} API`,
|
|
3679
|
+
description: "HTTP API for interacting with a Poncho agent. Supports conversation management, streaming message responses via Server-Sent Events (SSE), tool approval workflows, file uploads, and cron job triggers.",
|
|
3680
|
+
version: "1.0.0"
|
|
3681
|
+
},
|
|
3682
|
+
servers: [{ url: "/", description: "Current host" }],
|
|
3683
|
+
components: {
|
|
3684
|
+
securitySchemes: {
|
|
3685
|
+
bearerAuth: {
|
|
3686
|
+
type: "http",
|
|
3687
|
+
scheme: "bearer",
|
|
3688
|
+
description: "Pass the PONCHO_AUTH_TOKEN value as a Bearer token. Required only when `auth.required: true` in poncho.config.js."
|
|
3689
|
+
}
|
|
3690
|
+
},
|
|
3691
|
+
schemas: {
|
|
3692
|
+
ConversationSummary: {
|
|
3693
|
+
type: "object",
|
|
3694
|
+
properties: {
|
|
3695
|
+
conversationId: { type: "string" },
|
|
3696
|
+
title: { type: "string" },
|
|
3697
|
+
runtimeRunId: { type: "string" },
|
|
3698
|
+
ownerId: { type: "string" },
|
|
3699
|
+
tenantId: { type: ["string", "null"] },
|
|
3700
|
+
createdAt: { type: "number", description: "Unix epoch ms" },
|
|
3701
|
+
updatedAt: { type: "number", description: "Unix epoch ms" },
|
|
3702
|
+
messageCount: { type: "integer" }
|
|
3703
|
+
}
|
|
3704
|
+
},
|
|
3705
|
+
Message: {
|
|
3706
|
+
type: "object",
|
|
3707
|
+
properties: {
|
|
3708
|
+
role: { type: "string", enum: ["user", "assistant"] },
|
|
3709
|
+
content: {
|
|
3710
|
+
oneOf: [
|
|
3711
|
+
{ type: "string" },
|
|
3712
|
+
{
|
|
3713
|
+
type: "array",
|
|
3714
|
+
items: {
|
|
3715
|
+
oneOf: [
|
|
3716
|
+
{
|
|
3717
|
+
type: "object",
|
|
3718
|
+
properties: {
|
|
3719
|
+
type: { type: "string", const: "text" },
|
|
3720
|
+
text: { type: "string" }
|
|
3721
|
+
}
|
|
3722
|
+
},
|
|
3723
|
+
{
|
|
3724
|
+
type: "object",
|
|
3725
|
+
properties: {
|
|
3726
|
+
type: { type: "string", const: "file" },
|
|
3727
|
+
data: { type: "string" },
|
|
3728
|
+
mediaType: { type: "string" },
|
|
3729
|
+
filename: { type: "string" }
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
]
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
]
|
|
3736
|
+
},
|
|
3737
|
+
metadata: {
|
|
3738
|
+
type: "object",
|
|
3739
|
+
properties: {
|
|
3740
|
+
id: { type: "string" },
|
|
3741
|
+
timestamp: { type: "number" },
|
|
3742
|
+
tokenCount: { type: "number" },
|
|
3743
|
+
step: { type: "number" },
|
|
3744
|
+
toolActivity: { type: "array", items: { type: "string" } }
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
},
|
|
3749
|
+
Conversation: {
|
|
3750
|
+
type: "object",
|
|
3751
|
+
properties: {
|
|
3752
|
+
conversationId: { type: "string" },
|
|
3753
|
+
title: { type: "string" },
|
|
3754
|
+
ownerId: { type: "string" },
|
|
3755
|
+
tenantId: { type: ["string", "null"] },
|
|
3756
|
+
createdAt: { type: "number" },
|
|
3757
|
+
updatedAt: { type: "number" },
|
|
3758
|
+
messages: { type: "array", items: { $ref: "#/components/schemas/Message" } },
|
|
3759
|
+
pendingApprovals: {
|
|
3760
|
+
type: "array",
|
|
3761
|
+
items: { $ref: "#/components/schemas/PendingApproval" }
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
},
|
|
3765
|
+
PendingApproval: {
|
|
3766
|
+
type: "object",
|
|
3767
|
+
properties: {
|
|
3768
|
+
approvalId: { type: "string" },
|
|
3769
|
+
runId: { type: "string" },
|
|
3770
|
+
tool: { type: "string" },
|
|
3771
|
+
input: {}
|
|
3772
|
+
}
|
|
3773
|
+
},
|
|
3774
|
+
TokenUsage: {
|
|
3775
|
+
type: "object",
|
|
3776
|
+
properties: {
|
|
3777
|
+
input: { type: "integer" },
|
|
3778
|
+
output: { type: "integer" },
|
|
3779
|
+
cached: { type: "integer" }
|
|
3780
|
+
}
|
|
3781
|
+
},
|
|
3782
|
+
RunResult: {
|
|
3783
|
+
type: "object",
|
|
3784
|
+
properties: {
|
|
3785
|
+
status: { type: "string", enum: ["completed", "error", "cancelled"] },
|
|
3786
|
+
response: { type: "string" },
|
|
3787
|
+
steps: { type: "integer" },
|
|
3788
|
+
tokens: { $ref: "#/components/schemas/TokenUsage" },
|
|
3789
|
+
duration: { type: "number", description: "Duration in ms" },
|
|
3790
|
+
continuation: { type: "boolean" },
|
|
3791
|
+
maxSteps: { type: "integer" }
|
|
3792
|
+
}
|
|
3793
|
+
},
|
|
3794
|
+
FileAttachment: {
|
|
3795
|
+
type: "object",
|
|
3796
|
+
properties: {
|
|
3797
|
+
data: { type: "string", description: "base64-encoded file data" },
|
|
3798
|
+
mediaType: { type: "string" },
|
|
3799
|
+
filename: { type: "string" }
|
|
3800
|
+
},
|
|
3801
|
+
required: ["data", "mediaType"]
|
|
3802
|
+
},
|
|
3803
|
+
Error: {
|
|
3804
|
+
type: "object",
|
|
3805
|
+
properties: {
|
|
3806
|
+
code: { type: "string" },
|
|
3807
|
+
message: { type: "string" }
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
},
|
|
3812
|
+
security: [{ bearerAuth: [] }],
|
|
3813
|
+
paths: {
|
|
3814
|
+
"/health": {
|
|
3815
|
+
get: {
|
|
3816
|
+
tags: ["Health"],
|
|
3817
|
+
summary: "Health check",
|
|
3818
|
+
security: [],
|
|
3819
|
+
responses: {
|
|
3820
|
+
"200": {
|
|
3821
|
+
description: "Server is healthy",
|
|
3822
|
+
content: {
|
|
3823
|
+
"application/json": {
|
|
3824
|
+
schema: {
|
|
3825
|
+
type: "object",
|
|
3826
|
+
properties: { status: { type: "string", const: "ok" } }
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
},
|
|
3834
|
+
"/api/auth/session": {
|
|
3835
|
+
get: {
|
|
3836
|
+
tags: ["Auth"],
|
|
3837
|
+
summary: "Check session status",
|
|
3838
|
+
description: "Returns whether the caller is authenticated and provides a CSRF token for subsequent mutating requests.",
|
|
3839
|
+
security: [],
|
|
3840
|
+
responses: {
|
|
3841
|
+
"200": {
|
|
3842
|
+
description: "Session status",
|
|
3843
|
+
content: {
|
|
3844
|
+
"application/json": {
|
|
3845
|
+
schema: {
|
|
3846
|
+
type: "object",
|
|
3847
|
+
properties: {
|
|
3848
|
+
authenticated: { type: "boolean" },
|
|
3849
|
+
sessionId: { type: "string" },
|
|
3850
|
+
ownerId: { type: "string" },
|
|
3851
|
+
csrfToken: { type: "string" }
|
|
3852
|
+
},
|
|
3853
|
+
required: ["authenticated"]
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
},
|
|
3861
|
+
"/api/auth/login": {
|
|
3862
|
+
post: {
|
|
3863
|
+
tags: ["Auth"],
|
|
3864
|
+
summary: "Authenticate with passphrase",
|
|
3865
|
+
description: "Creates a session cookie. Only needed for browser-based auth; API clients should use Bearer tokens instead.",
|
|
3866
|
+
security: [],
|
|
3867
|
+
requestBody: {
|
|
3868
|
+
required: true,
|
|
3869
|
+
content: {
|
|
3870
|
+
"application/json": {
|
|
3871
|
+
schema: {
|
|
3872
|
+
type: "object",
|
|
3873
|
+
properties: { passphrase: { type: "string" } },
|
|
3874
|
+
required: ["passphrase"]
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
},
|
|
3879
|
+
responses: {
|
|
3880
|
+
"200": {
|
|
3881
|
+
description: "Login successful",
|
|
3882
|
+
content: {
|
|
3883
|
+
"application/json": {
|
|
3884
|
+
schema: {
|
|
3885
|
+
type: "object",
|
|
3886
|
+
properties: {
|
|
3887
|
+
ok: { type: "boolean" },
|
|
3888
|
+
sessionId: { type: "string" },
|
|
3889
|
+
csrfToken: { type: "string" }
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
},
|
|
3895
|
+
"401": {
|
|
3896
|
+
description: "Invalid passphrase",
|
|
3897
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
3898
|
+
},
|
|
3899
|
+
"429": {
|
|
3900
|
+
description: "Too many login attempts",
|
|
3901
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
},
|
|
3906
|
+
"/api/auth/logout": {
|
|
3907
|
+
post: {
|
|
3908
|
+
tags: ["Auth"],
|
|
3909
|
+
summary: "End session",
|
|
3910
|
+
responses: {
|
|
3911
|
+
"200": {
|
|
3912
|
+
description: "Logged out",
|
|
3913
|
+
content: {
|
|
3914
|
+
"application/json": {
|
|
3915
|
+
schema: { type: "object", properties: { ok: { type: "boolean" } } }
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
},
|
|
3922
|
+
"/api/conversations": {
|
|
3923
|
+
get: {
|
|
3924
|
+
tags: ["Conversations"],
|
|
3925
|
+
summary: "List conversations",
|
|
3926
|
+
responses: {
|
|
3927
|
+
"200": {
|
|
3928
|
+
description: "Conversation list",
|
|
3929
|
+
content: {
|
|
3930
|
+
"application/json": {
|
|
3931
|
+
schema: {
|
|
3932
|
+
type: "object",
|
|
3933
|
+
properties: {
|
|
3934
|
+
conversations: {
|
|
3935
|
+
type: "array",
|
|
3936
|
+
items: { $ref: "#/components/schemas/ConversationSummary" }
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
},
|
|
3945
|
+
post: {
|
|
3946
|
+
tags: ["Conversations"],
|
|
3947
|
+
summary: "Create a conversation",
|
|
3948
|
+
requestBody: {
|
|
3949
|
+
content: {
|
|
3950
|
+
"application/json": {
|
|
3951
|
+
schema: {
|
|
3952
|
+
type: "object",
|
|
3953
|
+
properties: { title: { type: "string" } }
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
},
|
|
3958
|
+
responses: {
|
|
3959
|
+
"201": {
|
|
3960
|
+
description: "Conversation created",
|
|
3961
|
+
content: {
|
|
3962
|
+
"application/json": {
|
|
3963
|
+
schema: {
|
|
3964
|
+
type: "object",
|
|
3965
|
+
properties: { conversation: { $ref: "#/components/schemas/Conversation" } }
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
},
|
|
3973
|
+
"/api/conversations/{conversationId}": {
|
|
3974
|
+
get: {
|
|
3975
|
+
tags: ["Conversations"],
|
|
3976
|
+
summary: "Get conversation",
|
|
3977
|
+
description: "Returns the full conversation including messages and any pending tool approval requests.",
|
|
3978
|
+
parameters: [
|
|
3979
|
+
{ name: "conversationId", in: "path", required: true, schema: { type: "string" } }
|
|
3980
|
+
],
|
|
3981
|
+
responses: {
|
|
3982
|
+
"200": {
|
|
3983
|
+
description: "Conversation with messages",
|
|
3984
|
+
content: {
|
|
3985
|
+
"application/json": {
|
|
3986
|
+
schema: {
|
|
3987
|
+
type: "object",
|
|
3988
|
+
properties: { conversation: { $ref: "#/components/schemas/Conversation" } }
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
},
|
|
3993
|
+
"404": {
|
|
3994
|
+
description: "Conversation not found",
|
|
3995
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
},
|
|
3999
|
+
patch: {
|
|
4000
|
+
tags: ["Conversations"],
|
|
4001
|
+
summary: "Rename conversation",
|
|
4002
|
+
parameters: [
|
|
4003
|
+
{ name: "conversationId", in: "path", required: true, schema: { type: "string" } }
|
|
4004
|
+
],
|
|
4005
|
+
requestBody: {
|
|
4006
|
+
required: true,
|
|
4007
|
+
content: {
|
|
4008
|
+
"application/json": {
|
|
4009
|
+
schema: {
|
|
4010
|
+
type: "object",
|
|
4011
|
+
properties: { title: { type: "string" } },
|
|
4012
|
+
required: ["title"]
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
},
|
|
4017
|
+
responses: {
|
|
4018
|
+
"200": {
|
|
4019
|
+
description: "Conversation renamed",
|
|
4020
|
+
content: {
|
|
4021
|
+
"application/json": {
|
|
4022
|
+
schema: {
|
|
4023
|
+
type: "object",
|
|
4024
|
+
properties: { conversation: { $ref: "#/components/schemas/Conversation" } }
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
},
|
|
4031
|
+
delete: {
|
|
4032
|
+
tags: ["Conversations"],
|
|
4033
|
+
summary: "Delete conversation",
|
|
4034
|
+
parameters: [
|
|
4035
|
+
{ name: "conversationId", in: "path", required: true, schema: { type: "string" } }
|
|
4036
|
+
],
|
|
4037
|
+
responses: {
|
|
4038
|
+
"200": {
|
|
4039
|
+
description: "Conversation deleted",
|
|
4040
|
+
content: {
|
|
4041
|
+
"application/json": {
|
|
4042
|
+
schema: { type: "object", properties: { ok: { type: "boolean" } } }
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
},
|
|
4049
|
+
"/api/conversations/{conversationId}/messages": {
|
|
4050
|
+
post: {
|
|
4051
|
+
tags: ["Messages"],
|
|
4052
|
+
summary: "Send a message (streaming)",
|
|
4053
|
+
description: "Sends a user message and streams the agent's response via Server-Sent Events.\n\n### SSE protocol\n\nThe response is a stream of SSE frames. Each frame has the format:\n\n```\nevent: <type>\ndata: <json>\n\n```\n\n**Event types:**\n\n| Event | Payload | Description |\n| --- | --- | --- |\n| `run:started` | `{ runId, agentId }` | Agent run has begun |\n| `model:chunk` | `{ content }` | Incremental text token from the model |\n| `model:response` | `{ usage: { input, output, cached } }` | Model call finished |\n| `step:started` | `{ step }` | Agent step started |\n| `step:completed` | `{ step, duration }` | Agent step finished |\n| `tool:started` | `{ tool, input }` | Tool invocation started |\n| `tool:completed` | `{ tool, output, duration }` | Tool finished successfully |\n| `tool:error` | `{ tool, error, recoverable }` | Tool returned an error |\n| `tool:approval:required` | `{ tool, input, approvalId }` | Tool needs human approval |\n| `tool:approval:granted` | `{ approvalId }` | Approval was granted |\n| `tool:approval:denied` | `{ approvalId, reason? }` | Approval was denied |\n| `run:completed` | `{ runId, result: RunResult }` | Agent finished |\n| `run:error` | `{ runId, error: { code, message } }` | Agent failed |\n| `run:cancelled` | `{ runId }` | Run was cancelled via stop endpoint |\n\nTo build the assistant's response, concatenate all `model:chunk` content values.\n\n### Reconnection\n\nIf the SSE connection drops mid-stream, reconnect via `GET /api/conversations/{conversationId}/events` to replay buffered events.",
|
|
4054
|
+
parameters: [
|
|
4055
|
+
{ name: "conversationId", in: "path", required: true, schema: { type: "string" } }
|
|
4056
|
+
],
|
|
4057
|
+
requestBody: {
|
|
4058
|
+
required: true,
|
|
4059
|
+
content: {
|
|
4060
|
+
"application/json": {
|
|
4061
|
+
schema: {
|
|
4062
|
+
type: "object",
|
|
4063
|
+
properties: {
|
|
4064
|
+
message: { type: "string", description: "User message text" },
|
|
4065
|
+
parameters: {
|
|
4066
|
+
type: "object",
|
|
4067
|
+
additionalProperties: true,
|
|
4068
|
+
description: "Key-value parameters passed to the agent run"
|
|
4069
|
+
},
|
|
4070
|
+
files: {
|
|
4071
|
+
type: "array",
|
|
4072
|
+
items: { $ref: "#/components/schemas/FileAttachment" },
|
|
4073
|
+
description: "Attached files (base64-encoded)"
|
|
4074
|
+
}
|
|
4075
|
+
},
|
|
4076
|
+
required: ["message"]
|
|
4077
|
+
}
|
|
4078
|
+
},
|
|
4079
|
+
"multipart/form-data": {
|
|
4080
|
+
schema: {
|
|
4081
|
+
type: "object",
|
|
4082
|
+
properties: {
|
|
4083
|
+
message: { type: "string" },
|
|
4084
|
+
parameters: { type: "string", description: "JSON-encoded parameters object" },
|
|
4085
|
+
files: { type: "array", items: { type: "string", format: "binary" } }
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
},
|
|
4091
|
+
responses: {
|
|
4092
|
+
"200": {
|
|
4093
|
+
description: "SSE stream of agent events",
|
|
4094
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4095
|
+
},
|
|
4096
|
+
"404": {
|
|
4097
|
+
description: "Conversation not found",
|
|
4098
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
},
|
|
4103
|
+
"/api/conversations/{conversationId}/events": {
|
|
4104
|
+
get: {
|
|
4105
|
+
tags: ["Messages"],
|
|
4106
|
+
summary: "Attach to live event stream",
|
|
4107
|
+
description: "Connects to the SSE event stream for an in-progress run. Replays all buffered events from the current run, then streams live events. If no run is active, sends a `stream:end` event and closes.",
|
|
4108
|
+
parameters: [
|
|
4109
|
+
{ name: "conversationId", in: "path", required: true, schema: { type: "string" } }
|
|
4110
|
+
],
|
|
4111
|
+
responses: {
|
|
4112
|
+
"200": {
|
|
4113
|
+
description: "SSE stream (same event format as POST /messages)",
|
|
4114
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4115
|
+
},
|
|
4116
|
+
"404": {
|
|
4117
|
+
description: "Conversation not found",
|
|
4118
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
},
|
|
4123
|
+
"/api/conversations/{conversationId}/stop": {
|
|
4124
|
+
post: {
|
|
4125
|
+
tags: ["Messages"],
|
|
4126
|
+
summary: "Stop an in-flight run",
|
|
4127
|
+
parameters: [
|
|
4128
|
+
{ name: "conversationId", in: "path", required: true, schema: { type: "string" } }
|
|
4129
|
+
],
|
|
4130
|
+
requestBody: {
|
|
4131
|
+
required: true,
|
|
4132
|
+
content: {
|
|
4133
|
+
"application/json": {
|
|
4134
|
+
schema: {
|
|
4135
|
+
type: "object",
|
|
4136
|
+
properties: {
|
|
4137
|
+
runId: { type: "string", description: "The run ID to cancel (from run:started event)" }
|
|
4138
|
+
},
|
|
4139
|
+
required: ["runId"]
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
},
|
|
4144
|
+
responses: {
|
|
4145
|
+
"200": {
|
|
4146
|
+
description: "Stop result",
|
|
4147
|
+
content: {
|
|
4148
|
+
"application/json": {
|
|
4149
|
+
schema: {
|
|
4150
|
+
type: "object",
|
|
4151
|
+
properties: {
|
|
4152
|
+
ok: { type: "boolean" },
|
|
4153
|
+
stopped: { type: "boolean" },
|
|
4154
|
+
runId: { type: "string" }
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
},
|
|
4163
|
+
"/api/approvals/{approvalId}": {
|
|
4164
|
+
post: {
|
|
4165
|
+
tags: ["Approvals"],
|
|
4166
|
+
summary: "Resolve a tool approval request",
|
|
4167
|
+
description: "When an agent run encounters a gated tool, it emits a `tool:approval:required` SSE event and pauses. Use this endpoint to approve or deny the tool invocation.",
|
|
4168
|
+
parameters: [
|
|
4169
|
+
{ name: "approvalId", in: "path", required: true, schema: { type: "string" } }
|
|
4170
|
+
],
|
|
4171
|
+
requestBody: {
|
|
4172
|
+
required: true,
|
|
4173
|
+
content: {
|
|
4174
|
+
"application/json": {
|
|
4175
|
+
schema: {
|
|
4176
|
+
type: "object",
|
|
4177
|
+
properties: { approved: { type: "boolean" } },
|
|
4178
|
+
required: ["approved"]
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
},
|
|
4183
|
+
responses: {
|
|
4184
|
+
"200": {
|
|
4185
|
+
description: "Approval resolved",
|
|
4186
|
+
content: {
|
|
4187
|
+
"application/json": {
|
|
4188
|
+
schema: {
|
|
4189
|
+
type: "object",
|
|
4190
|
+
properties: {
|
|
4191
|
+
ok: { type: "boolean" },
|
|
4192
|
+
approvalId: { type: "string" },
|
|
4193
|
+
approved: { type: "boolean" }
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
},
|
|
4199
|
+
"404": {
|
|
4200
|
+
description: "Approval not found or expired",
|
|
4201
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
},
|
|
4206
|
+
"/api/uploads/{key}": {
|
|
4207
|
+
get: {
|
|
4208
|
+
tags: ["Assets"],
|
|
4209
|
+
summary: "Retrieve an uploaded file",
|
|
4210
|
+
description: "Serves a file previously uploaded during a conversation. The key is returned in file content part references.",
|
|
4211
|
+
parameters: [
|
|
4212
|
+
{
|
|
4213
|
+
name: "key",
|
|
4214
|
+
in: "path",
|
|
4215
|
+
required: true,
|
|
4216
|
+
schema: { type: "string" },
|
|
4217
|
+
description: "Upload key (e.g. filename or storage path)"
|
|
4218
|
+
}
|
|
4219
|
+
],
|
|
4220
|
+
responses: {
|
|
4221
|
+
"200": {
|
|
4222
|
+
description: "File content with appropriate Content-Type",
|
|
4223
|
+
content: { "application/octet-stream": { schema: { type: "string", format: "binary" } } }
|
|
4224
|
+
},
|
|
4225
|
+
"404": {
|
|
4226
|
+
description: "Upload not found",
|
|
4227
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
},
|
|
4232
|
+
"/api/cron/{jobName}": {
|
|
4233
|
+
get: {
|
|
4234
|
+
tags: ["Cron"],
|
|
4235
|
+
summary: "Trigger a cron job",
|
|
4236
|
+
description: "Triggers a named cron job defined in AGENT.md frontmatter. Supports continuation via the `continue` query parameter.",
|
|
4237
|
+
parameters: [
|
|
4238
|
+
{ name: "jobName", in: "path", required: true, schema: { type: "string" } },
|
|
4239
|
+
{
|
|
4240
|
+
name: "continue",
|
|
4241
|
+
in: "query",
|
|
4242
|
+
schema: { type: "string" },
|
|
4243
|
+
description: "Conversation ID to continue a previous cron run"
|
|
4244
|
+
}
|
|
4245
|
+
],
|
|
4246
|
+
responses: {
|
|
4247
|
+
"200": {
|
|
4248
|
+
description: "Cron job result",
|
|
4249
|
+
content: {
|
|
4250
|
+
"application/json": {
|
|
4251
|
+
schema: {
|
|
4252
|
+
type: "object",
|
|
4253
|
+
properties: {
|
|
4254
|
+
conversationId: { type: "string" },
|
|
4255
|
+
response: { type: "string" },
|
|
4256
|
+
steps: { type: "integer" },
|
|
4257
|
+
status: { type: "string" },
|
|
4258
|
+
continuation: { type: "string", description: "URL to continue this run" }
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
},
|
|
4264
|
+
"404": {
|
|
4265
|
+
description: "Cron job not found",
|
|
4266
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
},
|
|
4272
|
+
tags: [
|
|
4273
|
+
{ name: "Health", description: "Server health check" },
|
|
4274
|
+
{ name: "Auth", description: "Session and authentication management" },
|
|
4275
|
+
{ name: "Conversations", description: "Create, list, read, rename, and delete conversations" },
|
|
4276
|
+
{
|
|
4277
|
+
name: "Messages",
|
|
4278
|
+
description: "Send messages and stream agent responses via SSE"
|
|
4279
|
+
},
|
|
4280
|
+
{ name: "Approvals", description: "Resolve gated tool approval requests" },
|
|
4281
|
+
{ name: "Assets", description: "Retrieve uploaded files" },
|
|
4282
|
+
{ name: "Cron", description: "Trigger cron jobs defined in AGENT.md" }
|
|
4283
|
+
]
|
|
4284
|
+
});
|
|
4285
|
+
var renderApiDocsHtml = (specUrl) => `<!DOCTYPE html>
|
|
4286
|
+
<html lang="en">
|
|
4287
|
+
<head>
|
|
4288
|
+
<meta charset="utf-8" />
|
|
4289
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
4290
|
+
<title>API Documentation</title>
|
|
4291
|
+
<style>body { margin: 0; }</style>
|
|
4292
|
+
</head>
|
|
4293
|
+
<body>
|
|
4294
|
+
<script id="api-reference" data-url="${specUrl}"></script>
|
|
4295
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
4296
|
+
</body>
|
|
4297
|
+
</html>`;
|
|
4298
|
+
|
|
3265
4299
|
// src/index.ts
|
|
3266
4300
|
import { createInterface } from "readline/promises";
|
|
3267
4301
|
|
|
@@ -3509,12 +4543,30 @@ var buildConfigFromOnboardingAnswers = (answers) => {
|
|
|
3509
4543
|
enabled: telemetryEnabled
|
|
3510
4544
|
};
|
|
3511
4545
|
maybeSet(telemetry, "otlp", answers["telemetry.otlp"]);
|
|
3512
|
-
|
|
4546
|
+
const messagingPlatform = String(answers["messaging.platform"] ?? "none");
|
|
4547
|
+
const config = {
|
|
3513
4548
|
mcp: [],
|
|
3514
4549
|
auth,
|
|
3515
4550
|
storage,
|
|
3516
4551
|
telemetry
|
|
3517
4552
|
};
|
|
4553
|
+
if (messagingPlatform !== "none") {
|
|
4554
|
+
const channelConfig = {
|
|
4555
|
+
platform: messagingPlatform
|
|
4556
|
+
};
|
|
4557
|
+
if (messagingPlatform === "resend") {
|
|
4558
|
+
const mode = String(answers["messaging.resend.mode"] ?? "auto-reply");
|
|
4559
|
+
if (mode === "tool") {
|
|
4560
|
+
channelConfig.mode = "tool";
|
|
4561
|
+
}
|
|
4562
|
+
const recipientsRaw = String(answers["messaging.resend.allowedRecipients"] ?? "");
|
|
4563
|
+
if (recipientsRaw.trim().length > 0) {
|
|
4564
|
+
channelConfig.allowedRecipients = recipientsRaw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
config.messaging = [channelConfig];
|
|
4568
|
+
}
|
|
4569
|
+
return config;
|
|
3518
4570
|
};
|
|
3519
4571
|
var collectEnvVars = (answers) => {
|
|
3520
4572
|
const envVars = /* @__PURE__ */ new Set();
|
|
@@ -3658,11 +4710,13 @@ var summarizeConfig = (config) => {
|
|
|
3658
4710
|
const memoryEnabled = config?.storage?.memory?.enabled ?? config?.memory?.enabled ?? false;
|
|
3659
4711
|
const authRequired = config?.auth?.required ?? false;
|
|
3660
4712
|
const telemetryEnabled = config?.telemetry?.enabled ?? true;
|
|
4713
|
+
const messagingPlatforms = (config?.messaging ?? []).map((m) => m.platform);
|
|
3661
4714
|
return [
|
|
3662
4715
|
`storage: ${provider}`,
|
|
3663
4716
|
`memory tools: ${memoryEnabled ? "enabled" : "disabled"}`,
|
|
3664
4717
|
`auth: ${authRequired ? "required" : "not required"}`,
|
|
3665
|
-
`telemetry: ${telemetryEnabled ? "enabled" : "disabled"}
|
|
4718
|
+
`telemetry: ${telemetryEnabled ? "enabled" : "disabled"}`,
|
|
4719
|
+
...messagingPlatforms.length > 0 ? [`messaging: ${messagingPlatforms.join(", ")}`] : []
|
|
3666
4720
|
];
|
|
3667
4721
|
};
|
|
3668
4722
|
var getOnboardingMarkerPath = async (workingDir) => {
|
|
@@ -3738,6 +4792,7 @@ var consumeFirstRunIntro = async (workingDir, input2) => {
|
|
|
3738
4792
|
"- **Turn on telemetry**: Track usage with OpenTelemetry/OTLP",
|
|
3739
4793
|
"- **Add MCP servers**: Connect external tool servers",
|
|
3740
4794
|
"- **Schedule cron jobs**: Set up recurring tasks in AGENT.md frontmatter",
|
|
4795
|
+
"- **Connect to Slack**: Set up messaging so users can @mention this agent in Slack",
|
|
3741
4796
|
"",
|
|
3742
4797
|
"Just let me know what you'd like to work on!\n"
|
|
3743
4798
|
].join("\n");
|
|
@@ -3981,7 +5036,7 @@ cp .env.example .env
|
|
|
3981
5036
|
poncho dev
|
|
3982
5037
|
\`\`\`
|
|
3983
5038
|
|
|
3984
|
-
Open \`http://localhost:3000\` for the web UI.
|
|
5039
|
+
Open \`http://localhost:3000\` for the web UI, or \`http://localhost:3000/api/docs\` for interactive API documentation.
|
|
3985
5040
|
|
|
3986
5041
|
On your first interactive session, the agent introduces its configurable capabilities.
|
|
3987
5042
|
While a response is streaming, you can stop it:
|
|
@@ -4103,7 +5158,7 @@ Core files:
|
|
|
4103
5158
|
|
|
4104
5159
|
- \`AGENT.md\`: behavior, model selection, runtime guidance
|
|
4105
5160
|
- \`poncho.config.js\`: runtime config (storage, auth, telemetry, MCP, tools)
|
|
4106
|
-
- \`.env\`: secrets and environment variables
|
|
5161
|
+
- \`.env\`: secrets and environment variables (loaded before the harness starts, so \`process.env\` is available in skill scripts)
|
|
4107
5162
|
|
|
4108
5163
|
Example \`poncho.config.js\`:
|
|
4109
5164
|
|
|
@@ -4129,18 +5184,20 @@ export default {
|
|
|
4129
5184
|
auth: { type: "bearer", tokenEnv: "GITHUB_TOKEN" },
|
|
4130
5185
|
},
|
|
4131
5186
|
],
|
|
5187
|
+
// Tool access: true (available), false (disabled), 'approval' (requires human approval)
|
|
4132
5188
|
tools: {
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
read_file: true,
|
|
4136
|
-
write_file: true, // still gated by environment/policy
|
|
4137
|
-
},
|
|
5189
|
+
write_file: true, // gated by environment for writes
|
|
5190
|
+
send_email: 'approval', // requires human approval
|
|
4138
5191
|
byEnvironment: {
|
|
4139
5192
|
production: {
|
|
4140
|
-
|
|
5193
|
+
write_file: false,
|
|
5194
|
+
},
|
|
5195
|
+
development: {
|
|
5196
|
+
send_email: true, // skip approval in dev
|
|
4141
5197
|
},
|
|
4142
5198
|
},
|
|
4143
5199
|
},
|
|
5200
|
+
// webUi: false, // Disable built-in UI for API-only deployments
|
|
4144
5201
|
};
|
|
4145
5202
|
\`\`\`
|
|
4146
5203
|
|
|
@@ -4181,6 +5238,44 @@ cron:
|
|
|
4181
5238
|
- Docker/Fly.io: scheduler runs automatically.
|
|
4182
5239
|
- Trigger manually: \`curl http://localhost:3000/api/cron/daily-report\`
|
|
4183
5240
|
|
|
5241
|
+
## Messaging (Slack)
|
|
5242
|
+
|
|
5243
|
+
Connect your agent to Slack so it responds to @mentions:
|
|
5244
|
+
|
|
5245
|
+
1. Create a Slack App at [api.slack.com/apps](https://api.slack.com/apps)
|
|
5246
|
+
2. Add Bot Token Scopes: \`app_mentions:read\`, \`chat:write\`, \`reactions:write\`
|
|
5247
|
+
3. Enable Event Subscriptions, set Request URL to \`https://<your-url>/api/messaging/slack\`, subscribe to \`app_mention\`
|
|
5248
|
+
4. Install to workspace, copy Bot Token and Signing Secret
|
|
5249
|
+
5. Set env vars:
|
|
5250
|
+
\`\`\`
|
|
5251
|
+
SLACK_BOT_TOKEN=xoxb-...
|
|
5252
|
+
SLACK_SIGNING_SECRET=...
|
|
5253
|
+
\`\`\`
|
|
5254
|
+
6. Add to \`poncho.config.js\`:
|
|
5255
|
+
\`\`\`javascript
|
|
5256
|
+
messaging: [{ platform: 'slack' }]
|
|
5257
|
+
\`\`\`
|
|
5258
|
+
|
|
5259
|
+
## Messaging (Email via Resend)
|
|
5260
|
+
|
|
5261
|
+
Connect your agent to email so users can interact by sending emails:
|
|
5262
|
+
|
|
5263
|
+
1. Set up a domain and enable Inbound at [resend.com](https://resend.com)
|
|
5264
|
+
2. Create a webhook for \`email.received\` pointing to \`https://<your-url>/api/messaging/resend\`
|
|
5265
|
+
3. Install the Resend SDK: \`npm install resend\`
|
|
5266
|
+
4. Set env vars:
|
|
5267
|
+
\`\`\`
|
|
5268
|
+
RESEND_API_KEY=re_...
|
|
5269
|
+
RESEND_WEBHOOK_SECRET=whsec_...
|
|
5270
|
+
RESEND_FROM=Agent <agent@yourdomain.com>
|
|
5271
|
+
\`\`\`
|
|
5272
|
+
5. Add to \`poncho.config.js\`:
|
|
5273
|
+
\`\`\`javascript
|
|
5274
|
+
messaging: [{ platform: 'resend' }]
|
|
5275
|
+
\`\`\`
|
|
5276
|
+
|
|
5277
|
+
For full control over outbound emails, use **tool mode** (\`mode: 'tool'\`) \u2014 the agent gets a \`send_email\` tool instead of auto-replying. See the repo README for details.
|
|
5278
|
+
|
|
4184
5279
|
## Deployment
|
|
4185
5280
|
|
|
4186
5281
|
\`\`\`bash
|
|
@@ -4883,11 +5978,270 @@ var createRequestHandler = async (options) => {
|
|
|
4883
5978
|
workingDir,
|
|
4884
5979
|
agentId: identity.id
|
|
4885
5980
|
});
|
|
5981
|
+
const messagingRoutes = /* @__PURE__ */ new Map();
|
|
5982
|
+
const messagingRouteRegistrar = (method, path, routeHandler) => {
|
|
5983
|
+
let byMethod = messagingRoutes.get(path);
|
|
5984
|
+
if (!byMethod) {
|
|
5985
|
+
byMethod = /* @__PURE__ */ new Map();
|
|
5986
|
+
messagingRoutes.set(path, byMethod);
|
|
5987
|
+
}
|
|
5988
|
+
byMethod.set(method, routeHandler);
|
|
5989
|
+
};
|
|
5990
|
+
const messagingRunner = {
|
|
5991
|
+
async getOrCreateConversation(conversationId, meta) {
|
|
5992
|
+
const existing = await conversationStore.get(conversationId);
|
|
5993
|
+
if (existing) {
|
|
5994
|
+
return { messages: existing.messages };
|
|
5995
|
+
}
|
|
5996
|
+
const now = Date.now();
|
|
5997
|
+
const conversation = {
|
|
5998
|
+
conversationId,
|
|
5999
|
+
title: meta.title ?? `${meta.platform} thread`,
|
|
6000
|
+
messages: [],
|
|
6001
|
+
ownerId: meta.ownerId,
|
|
6002
|
+
tenantId: null,
|
|
6003
|
+
createdAt: now,
|
|
6004
|
+
updatedAt: now
|
|
6005
|
+
};
|
|
6006
|
+
await conversationStore.update(conversation);
|
|
6007
|
+
return { messages: [] };
|
|
6008
|
+
},
|
|
6009
|
+
async run(conversationId, input2) {
|
|
6010
|
+
console.log("[messaging-runner] starting run for", conversationId, "task:", input2.task.slice(0, 80));
|
|
6011
|
+
const historyMessages = [...input2.messages];
|
|
6012
|
+
const userContent = input2.task;
|
|
6013
|
+
const updateConversation = async (patch) => {
|
|
6014
|
+
const fresh = await conversationStore.get(conversationId);
|
|
6015
|
+
if (!fresh) return;
|
|
6016
|
+
patch(fresh);
|
|
6017
|
+
fresh.updatedAt = Date.now();
|
|
6018
|
+
await conversationStore.update(fresh);
|
|
6019
|
+
};
|
|
6020
|
+
await updateConversation((c) => {
|
|
6021
|
+
c.messages = [...historyMessages, { role: "user", content: userContent }];
|
|
6022
|
+
});
|
|
6023
|
+
let latestRunId = "";
|
|
6024
|
+
let assistantResponse = "";
|
|
6025
|
+
const toolTimeline = [];
|
|
6026
|
+
const sections = [];
|
|
6027
|
+
let currentTools = [];
|
|
6028
|
+
let currentText = "";
|
|
6029
|
+
const buildMessages = () => {
|
|
6030
|
+
const draftSections = [
|
|
6031
|
+
...sections.map((s) => ({
|
|
6032
|
+
type: s.type,
|
|
6033
|
+
content: Array.isArray(s.content) ? [...s.content] : s.content
|
|
6034
|
+
}))
|
|
6035
|
+
];
|
|
6036
|
+
if (currentTools.length > 0) {
|
|
6037
|
+
draftSections.push({ type: "tools", content: [...currentTools] });
|
|
6038
|
+
}
|
|
6039
|
+
if (currentText.length > 0) {
|
|
6040
|
+
draftSections.push({ type: "text", content: currentText });
|
|
6041
|
+
}
|
|
6042
|
+
const hasDraftContent = assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
|
|
6043
|
+
if (!hasDraftContent) {
|
|
6044
|
+
return [...historyMessages, { role: "user", content: userContent }];
|
|
6045
|
+
}
|
|
6046
|
+
return [
|
|
6047
|
+
...historyMessages,
|
|
6048
|
+
{ role: "user", content: userContent },
|
|
6049
|
+
{
|
|
6050
|
+
role: "assistant",
|
|
6051
|
+
content: assistantResponse,
|
|
6052
|
+
metadata: toolTimeline.length > 0 || draftSections.length > 0 ? {
|
|
6053
|
+
toolActivity: [...toolTimeline],
|
|
6054
|
+
sections: draftSections.length > 0 ? draftSections : void 0
|
|
6055
|
+
} : void 0
|
|
6056
|
+
}
|
|
6057
|
+
];
|
|
6058
|
+
};
|
|
6059
|
+
const persistDraftAssistantTurn = async () => {
|
|
6060
|
+
if (assistantResponse.length === 0 && toolTimeline.length === 0) return;
|
|
6061
|
+
await updateConversation((c) => {
|
|
6062
|
+
c.messages = buildMessages();
|
|
6063
|
+
});
|
|
6064
|
+
};
|
|
6065
|
+
const runInput = {
|
|
6066
|
+
task: input2.task,
|
|
6067
|
+
conversationId,
|
|
6068
|
+
messages: input2.messages,
|
|
6069
|
+
files: input2.files,
|
|
6070
|
+
parameters: input2.metadata ? {
|
|
6071
|
+
__messaging_platform: input2.metadata.platform,
|
|
6072
|
+
__messaging_sender_id: input2.metadata.sender.id,
|
|
6073
|
+
__messaging_sender_name: input2.metadata.sender.name ?? "",
|
|
6074
|
+
__messaging_thread_id: input2.metadata.threadId
|
|
6075
|
+
} : void 0
|
|
6076
|
+
};
|
|
6077
|
+
try {
|
|
6078
|
+
for await (const event of harness.runWithTelemetry(runInput)) {
|
|
6079
|
+
if (event.type === "run:started") {
|
|
6080
|
+
latestRunId = event.runId;
|
|
6081
|
+
runOwners.set(event.runId, "local-owner");
|
|
6082
|
+
runConversations.set(event.runId, conversationId);
|
|
6083
|
+
}
|
|
6084
|
+
if (event.type === "model:chunk") {
|
|
6085
|
+
if (currentTools.length > 0) {
|
|
6086
|
+
sections.push({ type: "tools", content: currentTools });
|
|
6087
|
+
currentTools = [];
|
|
6088
|
+
}
|
|
6089
|
+
assistantResponse += event.content;
|
|
6090
|
+
currentText += event.content;
|
|
6091
|
+
}
|
|
6092
|
+
if (event.type === "tool:started") {
|
|
6093
|
+
if (currentText.length > 0) {
|
|
6094
|
+
sections.push({ type: "text", content: currentText });
|
|
6095
|
+
currentText = "";
|
|
6096
|
+
}
|
|
6097
|
+
const toolText = `- start \`${event.tool}\``;
|
|
6098
|
+
toolTimeline.push(toolText);
|
|
6099
|
+
currentTools.push(toolText);
|
|
6100
|
+
}
|
|
6101
|
+
if (event.type === "tool:completed") {
|
|
6102
|
+
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
6103
|
+
toolTimeline.push(toolText);
|
|
6104
|
+
currentTools.push(toolText);
|
|
6105
|
+
}
|
|
6106
|
+
if (event.type === "tool:error") {
|
|
6107
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
6108
|
+
toolTimeline.push(toolText);
|
|
6109
|
+
currentTools.push(toolText);
|
|
6110
|
+
}
|
|
6111
|
+
if (event.type === "step:completed") {
|
|
6112
|
+
await persistDraftAssistantTurn();
|
|
6113
|
+
}
|
|
6114
|
+
if (event.type === "tool:approval:required") {
|
|
6115
|
+
const toolText = `- approval required \`${event.tool}\``;
|
|
6116
|
+
toolTimeline.push(toolText);
|
|
6117
|
+
currentTools.push(toolText);
|
|
6118
|
+
await persistDraftAssistantTurn();
|
|
6119
|
+
await persistConversationPendingApprovals(conversationId);
|
|
6120
|
+
}
|
|
6121
|
+
if (event.type === "tool:approval:granted") {
|
|
6122
|
+
const toolText = `- approval granted (${event.approvalId})`;
|
|
6123
|
+
toolTimeline.push(toolText);
|
|
6124
|
+
currentTools.push(toolText);
|
|
6125
|
+
await persistDraftAssistantTurn();
|
|
6126
|
+
}
|
|
6127
|
+
if (event.type === "tool:approval:denied") {
|
|
6128
|
+
const toolText = `- approval denied (${event.approvalId})`;
|
|
6129
|
+
toolTimeline.push(toolText);
|
|
6130
|
+
currentTools.push(toolText);
|
|
6131
|
+
await persistDraftAssistantTurn();
|
|
6132
|
+
}
|
|
6133
|
+
if (event.type === "run:completed" && assistantResponse.length === 0 && event.result.response) {
|
|
6134
|
+
assistantResponse = event.result.response;
|
|
6135
|
+
}
|
|
6136
|
+
if (event.type === "run:error") {
|
|
6137
|
+
assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
|
|
6138
|
+
}
|
|
6139
|
+
broadcastEvent(conversationId, event);
|
|
6140
|
+
}
|
|
6141
|
+
} catch (err) {
|
|
6142
|
+
console.error("[messaging-runner] run failed:", err instanceof Error ? err.message : err);
|
|
6143
|
+
assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
|
|
6144
|
+
}
|
|
6145
|
+
if (currentTools.length > 0) {
|
|
6146
|
+
sections.push({ type: "tools", content: currentTools });
|
|
6147
|
+
currentTools = [];
|
|
6148
|
+
}
|
|
6149
|
+
if (currentText.length > 0) {
|
|
6150
|
+
sections.push({ type: "text", content: currentText });
|
|
6151
|
+
currentText = "";
|
|
6152
|
+
}
|
|
6153
|
+
await updateConversation((c) => {
|
|
6154
|
+
c.messages = buildMessages();
|
|
6155
|
+
c.runtimeRunId = latestRunId || c.runtimeRunId;
|
|
6156
|
+
c.pendingApprovals = [];
|
|
6157
|
+
});
|
|
6158
|
+
finishConversationStream(conversationId);
|
|
6159
|
+
await persistConversationPendingApprovals(conversationId);
|
|
6160
|
+
if (latestRunId) {
|
|
6161
|
+
runOwners.delete(latestRunId);
|
|
6162
|
+
runConversations.delete(latestRunId);
|
|
6163
|
+
}
|
|
6164
|
+
console.log("[messaging-runner] run complete, response length:", assistantResponse.length);
|
|
6165
|
+
const response = assistantResponse;
|
|
6166
|
+
return { response };
|
|
6167
|
+
}
|
|
6168
|
+
};
|
|
6169
|
+
const messagingBridges = [];
|
|
6170
|
+
if (config?.messaging && config.messaging.length > 0) {
|
|
6171
|
+
let waitUntilHook;
|
|
6172
|
+
if (process.env.VERCEL) {
|
|
6173
|
+
try {
|
|
6174
|
+
const modName = "@vercel/functions";
|
|
6175
|
+
const mod = await import(
|
|
6176
|
+
/* webpackIgnore: true */
|
|
6177
|
+
modName
|
|
6178
|
+
);
|
|
6179
|
+
waitUntilHook = mod.waitUntil;
|
|
6180
|
+
} catch {
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
for (const channelConfig of config.messaging) {
|
|
6184
|
+
if (channelConfig.platform === "slack") {
|
|
6185
|
+
const adapter = new SlackAdapter({
|
|
6186
|
+
botTokenEnv: channelConfig.botTokenEnv,
|
|
6187
|
+
signingSecretEnv: channelConfig.signingSecretEnv
|
|
6188
|
+
});
|
|
6189
|
+
const bridge = new AgentBridge({
|
|
6190
|
+
adapter,
|
|
6191
|
+
runner: messagingRunner,
|
|
6192
|
+
waitUntil: waitUntilHook,
|
|
6193
|
+
ownerId: "local-owner"
|
|
6194
|
+
});
|
|
6195
|
+
adapter.registerRoutes(messagingRouteRegistrar);
|
|
6196
|
+
try {
|
|
6197
|
+
await bridge.start();
|
|
6198
|
+
messagingBridges.push(bridge);
|
|
6199
|
+
console.log(` Slack messaging enabled at /api/messaging/slack`);
|
|
6200
|
+
} catch (err) {
|
|
6201
|
+
console.warn(
|
|
6202
|
+
` Slack messaging disabled: ${err instanceof Error ? err.message : String(err)}`
|
|
6203
|
+
);
|
|
6204
|
+
}
|
|
6205
|
+
} else if (channelConfig.platform === "resend") {
|
|
6206
|
+
const adapter = new ResendAdapter({
|
|
6207
|
+
apiKeyEnv: channelConfig.apiKeyEnv,
|
|
6208
|
+
webhookSecretEnv: channelConfig.webhookSecretEnv,
|
|
6209
|
+
fromEnv: channelConfig.fromEnv,
|
|
6210
|
+
allowedSenders: channelConfig.allowedSenders,
|
|
6211
|
+
mode: channelConfig.mode,
|
|
6212
|
+
allowedRecipients: channelConfig.allowedRecipients,
|
|
6213
|
+
maxSendsPerRun: channelConfig.maxSendsPerRun
|
|
6214
|
+
});
|
|
6215
|
+
const bridge = new AgentBridge({
|
|
6216
|
+
adapter,
|
|
6217
|
+
runner: messagingRunner,
|
|
6218
|
+
waitUntil: waitUntilHook,
|
|
6219
|
+
ownerId: "local-owner"
|
|
6220
|
+
});
|
|
6221
|
+
adapter.registerRoutes(messagingRouteRegistrar);
|
|
6222
|
+
try {
|
|
6223
|
+
await bridge.start();
|
|
6224
|
+
messagingBridges.push(bridge);
|
|
6225
|
+
const adapterTools = adapter.getToolDefinitions?.() ?? [];
|
|
6226
|
+
if (adapterTools.length > 0) {
|
|
6227
|
+
harness.registerTools(adapterTools);
|
|
6228
|
+
}
|
|
6229
|
+
const modeLabel = channelConfig.mode === "tool" ? "tool" : "auto-reply";
|
|
6230
|
+
console.log(` Resend email messaging enabled at /api/messaging/resend (mode: ${modeLabel})`);
|
|
6231
|
+
} catch (err) {
|
|
6232
|
+
console.warn(
|
|
6233
|
+
` Resend email messaging disabled: ${err instanceof Error ? err.message : String(err)}`
|
|
6234
|
+
);
|
|
6235
|
+
}
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
4886
6239
|
const sessionStore = new SessionStore();
|
|
4887
6240
|
const loginRateLimiter = new LoginRateLimiter();
|
|
4888
6241
|
const authToken = process.env.PONCHO_AUTH_TOKEN ?? "";
|
|
4889
6242
|
const authRequired = config?.auth?.required ?? false;
|
|
4890
6243
|
const requireAuth = authRequired && authToken.length > 0;
|
|
6244
|
+
const webUiEnabled = config?.webUi !== false;
|
|
4891
6245
|
const isProduction = resolveHarnessEnvironment() === "production";
|
|
4892
6246
|
const secureCookies = isProduction;
|
|
4893
6247
|
const validateBearerToken = (authHeader) => {
|
|
@@ -4909,36 +6263,54 @@ var createRequestHandler = async (options) => {
|
|
|
4909
6263
|
return;
|
|
4910
6264
|
}
|
|
4911
6265
|
const [pathname] = request.url.split("?");
|
|
4912
|
-
if (
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
6266
|
+
if (webUiEnabled) {
|
|
6267
|
+
if (request.method === "GET" && (pathname === "/" || pathname.startsWith("/c/"))) {
|
|
6268
|
+
writeHtml(response, 200, renderWebUiHtml({ agentName }));
|
|
6269
|
+
return;
|
|
6270
|
+
}
|
|
6271
|
+
if (pathname === "/manifest.json" && request.method === "GET") {
|
|
6272
|
+
response.writeHead(200, { "Content-Type": "application/manifest+json" });
|
|
6273
|
+
response.end(renderManifest({ agentName }));
|
|
6274
|
+
return;
|
|
6275
|
+
}
|
|
6276
|
+
if (pathname === "/sw.js" && request.method === "GET") {
|
|
6277
|
+
response.writeHead(200, {
|
|
6278
|
+
"Content-Type": "application/javascript",
|
|
6279
|
+
"Service-Worker-Allowed": "/"
|
|
6280
|
+
});
|
|
6281
|
+
response.end(renderServiceWorker());
|
|
6282
|
+
return;
|
|
6283
|
+
}
|
|
6284
|
+
if (pathname === "/icon.svg" && request.method === "GET") {
|
|
6285
|
+
response.writeHead(200, { "Content-Type": "image/svg+xml" });
|
|
6286
|
+
response.end(renderIconSvg({ agentName }));
|
|
6287
|
+
return;
|
|
6288
|
+
}
|
|
6289
|
+
if ((pathname === "/icon-192.png" || pathname === "/icon-512.png") && request.method === "GET") {
|
|
6290
|
+
response.writeHead(302, { Location: "/icon.svg" });
|
|
6291
|
+
response.end();
|
|
6292
|
+
return;
|
|
6293
|
+
}
|
|
4920
6294
|
}
|
|
4921
|
-
if (pathname === "/
|
|
4922
|
-
response
|
|
4923
|
-
"Content-Type": "application/javascript",
|
|
4924
|
-
"Service-Worker-Allowed": "/"
|
|
4925
|
-
});
|
|
4926
|
-
response.end(renderServiceWorker());
|
|
6295
|
+
if (pathname === "/health" && request.method === "GET") {
|
|
6296
|
+
writeJson(response, 200, { status: "ok" });
|
|
4927
6297
|
return;
|
|
4928
6298
|
}
|
|
4929
|
-
if (pathname === "/
|
|
4930
|
-
response
|
|
4931
|
-
response.end(renderIconSvg({ agentName }));
|
|
6299
|
+
if (pathname === "/api/openapi.json" && request.method === "GET") {
|
|
6300
|
+
writeJson(response, 200, buildOpenApiSpec({ agentName }));
|
|
4932
6301
|
return;
|
|
4933
6302
|
}
|
|
4934
|
-
if (
|
|
4935
|
-
response
|
|
4936
|
-
response.end();
|
|
6303
|
+
if (pathname === "/api/docs" && request.method === "GET") {
|
|
6304
|
+
writeHtml(response, 200, renderApiDocsHtml("/api/openapi.json"));
|
|
4937
6305
|
return;
|
|
4938
6306
|
}
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
6307
|
+
const messagingByMethod = messagingRoutes.get(pathname ?? "");
|
|
6308
|
+
if (messagingByMethod) {
|
|
6309
|
+
const routeHandler = messagingByMethod.get(request.method ?? "");
|
|
6310
|
+
if (routeHandler) {
|
|
6311
|
+
await routeHandler(request, response);
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
4942
6314
|
}
|
|
4943
6315
|
const cookies = parseCookies(request);
|
|
4944
6316
|
const sessionId = cookies.poncho_session;
|
|
@@ -5127,12 +6499,15 @@ var createRequestHandler = async (options) => {
|
|
|
5127
6499
|
response.end();
|
|
5128
6500
|
return;
|
|
5129
6501
|
}
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
6502
|
+
const liveOnly = (request.url ?? "").includes("live_only=true");
|
|
6503
|
+
if (!liveOnly) {
|
|
6504
|
+
for (const bufferedEvent of stream.buffer) {
|
|
6505
|
+
try {
|
|
6506
|
+
response.write(formatSseEvent(bufferedEvent));
|
|
6507
|
+
} catch {
|
|
6508
|
+
response.end();
|
|
6509
|
+
return;
|
|
6510
|
+
}
|
|
5136
6511
|
}
|
|
5137
6512
|
}
|
|
5138
6513
|
if (stream.finished) {
|
|
@@ -5176,11 +6551,14 @@ var createRequestHandler = async (options) => {
|
|
|
5176
6551
|
for (const approval of livePending) {
|
|
5177
6552
|
mergedPendingById.set(approval.approvalId, approval);
|
|
5178
6553
|
}
|
|
6554
|
+
const activeStream = conversationEventStreams.get(conversationId);
|
|
6555
|
+
const hasActiveRun = !!activeStream && !activeStream.finished;
|
|
5179
6556
|
writeJson(response, 200, {
|
|
5180
6557
|
conversation: {
|
|
5181
6558
|
...conversation,
|
|
5182
6559
|
pendingApprovals: Array.from(mergedPendingById.values())
|
|
5183
|
-
}
|
|
6560
|
+
},
|
|
6561
|
+
hasActiveRun
|
|
5184
6562
|
});
|
|
5185
6563
|
return;
|
|
5186
6564
|
}
|
|
@@ -5441,6 +6819,7 @@ var createRequestHandler = async (options) => {
|
|
|
5441
6819
|
})).filter((item) => item.content.length > 0);
|
|
5442
6820
|
for await (const event of harness.runWithTelemetry({
|
|
5443
6821
|
task: messageText,
|
|
6822
|
+
conversationId,
|
|
5444
6823
|
parameters: {
|
|
5445
6824
|
...bodyParameters ?? {},
|
|
5446
6825
|
__conversationRecallCorpus: recallCorpus,
|
|
@@ -5685,6 +7064,7 @@ var createRequestHandler = async (options) => {
|
|
|
5685
7064
|
const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
|
|
5686
7065
|
for await (const event of harness.runWithTelemetry({
|
|
5687
7066
|
task: cronJob.task,
|
|
7067
|
+
conversationId: conversation.conversationId,
|
|
5688
7068
|
parameters: { __activeConversationId: conversation.conversationId },
|
|
5689
7069
|
messages: historyMessages,
|
|
5690
7070
|
abortSignal: abortController.signal
|
|
@@ -5846,6 +7226,7 @@ var startDevServer = async (port, options) => {
|
|
|
5846
7226
|
let currentText = "";
|
|
5847
7227
|
for await (const event of harness.runWithTelemetry({
|
|
5848
7228
|
task: config.task,
|
|
7229
|
+
conversationId: conversation.conversationId,
|
|
5849
7230
|
parameters: { __activeConversationId: conversation.conversationId },
|
|
5850
7231
|
messages: []
|
|
5851
7232
|
})) {
|
|
@@ -6036,7 +7417,7 @@ var runInteractive = async (workingDir, params) => {
|
|
|
6036
7417
|
await harness.initialize();
|
|
6037
7418
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
6038
7419
|
try {
|
|
6039
|
-
const { runInteractiveInk } = await import("./run-interactive-ink-
|
|
7420
|
+
const { runInteractiveInk } = await import("./run-interactive-ink-SLWDVTDX.js");
|
|
6040
7421
|
await runInteractiveInk({
|
|
6041
7422
|
harness,
|
|
6042
7423
|
params,
|