@poncho-ai/cli 0.13.0 → 0.14.1

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.
@@ -24,6 +24,7 @@ import {
24
24
  import { getTextContent } from "@poncho-ai/sdk";
25
25
  import {
26
26
  AgentBridge,
27
+ ResendAdapter,
27
28
  SlackAdapter
28
29
  } from "@poncho-ai/messaging";
29
30
  import Busboy from "busboy";
@@ -258,7 +259,8 @@ var renderWebUiHtml = (options) => {
258
259
  <head>
259
260
  <meta charset="utf-8">
260
261
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
261
- <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)">
262
264
  <meta name="apple-mobile-web-app-capable" content="yes">
263
265
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
264
266
  <meta name="apple-mobile-web-app-title" content="${agentName}">
@@ -268,12 +270,172 @@ var renderWebUiHtml = (options) => {
268
270
  <title>${agentName}</title>
269
271
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inconsolata:400,700">
270
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
+ --warning: #e8a735;
343
+
344
+ --approve: #78e7a6;
345
+ --approve-border: rgba(58,208,122,0.45);
346
+ --deny: #f59b9b;
347
+ --deny-border: rgba(224,95,95,0.45);
348
+
349
+ --scrollbar: rgba(255,255,255,0.1);
350
+ --scrollbar-hover: rgba(255,255,255,0.16);
351
+ }
352
+
353
+ @media (prefers-color-scheme: light) {
354
+ :root {
355
+ --bg: #ffffff;
356
+ --bg-alt: #f5f5f5;
357
+ --bg-elevated: #e8e8e8;
358
+
359
+ --fg: #1a1a1a;
360
+ --fg-strong: #000;
361
+ --fg-2: #666;
362
+ --fg-3: #555;
363
+ --fg-4: #777;
364
+ --fg-5: #888;
365
+ --fg-6: #888;
366
+ --fg-7: #aaa;
367
+ --fg-8: #bbb;
368
+
369
+ --fg-tool: #666;
370
+ --fg-tool-code: #444;
371
+ --fg-tool-item: #333;
372
+ --fg-approval-label: #666;
373
+ --fg-approval-input: #444;
374
+ --fg-approval-btn: #1a1a1a;
375
+
376
+ --accent: #1a1a1a;
377
+ --accent-fg: #fff;
378
+ --accent-hover: #000;
379
+
380
+ --stop-bg: #d4d4d4;
381
+ --stop-fg: #333;
382
+ --stop-hover: #c4c4c4;
383
+
384
+ --border-1: rgba(0,0,0,0.06);
385
+ --border-2: rgba(0,0,0,0.08);
386
+ --border-3: rgba(0,0,0,0.1);
387
+ --border-4: rgba(0,0,0,0.1);
388
+ --border-5: rgba(0,0,0,0.15);
389
+ --border-focus: rgba(0,0,0,0.2);
390
+ --border-hover: rgba(0,0,0,0.2);
391
+ --border-drag: rgba(0,0,0,0.3);
392
+
393
+ --surface-1: rgba(0,0,0,0.02);
394
+ --surface-2: rgba(0,0,0,0.03);
395
+ --surface-3: rgba(0,0,0,0.03);
396
+ --surface-4: rgba(0,0,0,0.04);
397
+ --surface-5: rgba(0,0,0,0.05);
398
+ --surface-6: rgba(0,0,0,0.07);
399
+ --surface-7: rgba(0,0,0,0.08);
400
+ --surface-8: rgba(0,0,0,0.1);
401
+
402
+ --chip-bg: rgba(255,255,255,0.8);
403
+ --chip-bg-hover: rgba(255,255,255,0.9);
404
+ --backdrop: rgba(0,0,0,0.3);
405
+ --lightbox-bg: rgba(0,0,0,0.75);
406
+ --inset-1: rgba(0,0,0,0.04);
407
+ --inset-2: rgba(0,0,0,0.06);
408
+
409
+ --file-badge-bg: rgba(0,0,0,0.05);
410
+ --file-badge-fg: rgba(0,0,0,0.7);
411
+
412
+ --error: #dc2626;
413
+ --error-soft: #ef4444;
414
+ --error-alt: #ef4444;
415
+ --error-bg: rgba(220,38,38,0.06);
416
+ --error-border: rgba(220,38,38,0.2);
417
+
418
+ --tool-done: #16a34a;
419
+ --tool-error: #dc2626;
420
+
421
+ --warning: #ca8a04;
422
+
423
+ --approve: #16a34a;
424
+ --approve-border: rgba(22,163,74,0.35);
425
+ --deny: #dc2626;
426
+ --deny-border: rgba(220,38,38,0.3);
427
+
428
+ --scrollbar: rgba(0,0,0,0.12);
429
+ --scrollbar-hover: rgba(0,0,0,0.2);
430
+ }
431
+ }
432
+
271
433
  * { box-sizing: border-box; margin: 0; padding: 0; }
272
434
  html, body { height: 100vh; overflow: hidden; overscroll-behavior: none; touch-action: pan-y; }
273
435
  body {
274
436
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;
275
- background: #000;
276
- color: #ededed;
437
+ background: var(--bg);
438
+ color: var(--fg);
277
439
  font-size: 14px;
278
440
  line-height: 1.5;
279
441
  -webkit-font-smoothing: antialiased;
@@ -281,7 +443,7 @@ var renderWebUiHtml = (options) => {
281
443
  }
282
444
  button, input, textarea { font: inherit; color: inherit; }
283
445
  .hidden { display: none !important; }
284
- a { color: #ededed; }
446
+ a { color: var(--fg); }
285
447
 
286
448
  /* Auth */
287
449
  .auth {
@@ -289,39 +451,39 @@ var renderWebUiHtml = (options) => {
289
451
  display: grid;
290
452
  place-items: center;
291
453
  padding: 20px;
292
- background: #000;
454
+ background: var(--bg);
293
455
  }
294
456
  .auth-card {
295
457
  width: min(400px, 90vw);
296
458
  }
297
459
  .auth-shell {
298
- background: #0a0a0a;
299
- border: 1px solid rgba(255,255,255,0.1);
460
+ background: var(--bg-alt);
461
+ border: 1px solid var(--border-3);
300
462
  border-radius: 9999px;
301
463
  display: flex;
302
464
  align-items: center;
303
465
  padding: 4px 6px 4px 18px;
304
466
  transition: border-color 0.15s;
305
467
  }
306
- .auth-shell:focus-within { border-color: rgba(255,255,255,0.2); }
468
+ .auth-shell:focus-within { border-color: var(--border-focus); }
307
469
  .auth-input {
308
470
  flex: 1;
309
471
  background: transparent;
310
472
  border: 0;
311
473
  outline: none;
312
- color: #ededed;
474
+ color: var(--fg);
313
475
  padding: 10px 0 8px;
314
476
  font-size: 14px;
315
477
  margin-top: -2px;
316
478
  }
317
- .auth-input::placeholder { color: #444; }
479
+ .auth-input::placeholder { color: var(--fg-7); }
318
480
  .auth-submit {
319
481
  width: 32px;
320
482
  height: 32px;
321
- background: #ededed;
483
+ background: var(--accent);
322
484
  border: 0;
323
485
  border-radius: 50%;
324
- color: #000;
486
+ color: var(--accent-fg);
325
487
  cursor: pointer;
326
488
  display: grid;
327
489
  place-items: center;
@@ -329,19 +491,19 @@ var renderWebUiHtml = (options) => {
329
491
  margin-bottom: 2px;
330
492
  transition: background 0.15s;
331
493
  }
332
- .auth-submit:hover { background: #fff; }
333
- .error { color: #ff4444; font-size: 13px; min-height: 16px; }
494
+ .auth-submit:hover { background: var(--accent-hover); }
495
+ .error { color: var(--error); font-size: 13px; min-height: 16px; }
334
496
  .message-error {
335
- background: rgba(255,68,68,0.08);
336
- border: 1px solid rgba(255,68,68,0.25);
497
+ background: var(--error-bg);
498
+ border: 1px solid var(--error-border);
337
499
  border-radius: 10px;
338
- color: #ff6b6b;
500
+ color: var(--error-soft);
339
501
  padding: 12px 16px;
340
502
  font-size: 13px;
341
503
  line-height: 1.5;
342
504
  max-width: 600px;
343
505
  }
344
- .message-error strong { color: #ff4444; }
506
+ .message-error strong { color: var(--error); }
345
507
 
346
508
  /* Layout - use fixed positioning with explicit dimensions */
347
509
  .shell {
@@ -373,8 +535,8 @@ var renderWebUiHtml = (options) => {
373
535
  }
374
536
  .sidebar {
375
537
  width: 260px;
376
- background: #000;
377
- border-right: 1px solid rgba(255,255,255,0.06);
538
+ background: var(--bg);
539
+ border-right: 1px solid var(--border-1);
378
540
  display: flex;
379
541
  flex-direction: column;
380
542
  padding: 12px 8px;
@@ -382,7 +544,7 @@ var renderWebUiHtml = (options) => {
382
544
  .new-chat-btn {
383
545
  background: transparent;
384
546
  border: 0;
385
- color: #888;
547
+ color: var(--fg-2);
386
548
  border-radius: 12px;
387
549
  height: 36px;
388
550
  padding: 0 10px;
@@ -393,7 +555,7 @@ var renderWebUiHtml = (options) => {
393
555
  cursor: pointer;
394
556
  transition: background 0.15s, color 0.15s;
395
557
  }
396
- .new-chat-btn:hover { color: #ededed; }
558
+ .new-chat-btn:hover { color: var(--fg); }
397
559
  .new-chat-btn svg { width: 16px; height: 16px; }
398
560
  .conversation-list {
399
561
  flex: 1;
@@ -403,6 +565,20 @@ var renderWebUiHtml = (options) => {
403
565
  flex-direction: column;
404
566
  gap: 2px;
405
567
  }
568
+ .sidebar-section-label {
569
+ font-size: 11px;
570
+ font-weight: 600;
571
+ color: var(--fg-7);
572
+ text-transform: uppercase;
573
+ letter-spacing: 0.04em;
574
+ padding: 10px 10px 4px;
575
+ }
576
+ .sidebar-section-label:first-child { padding-top: 4px; }
577
+ .sidebar-section-divider {
578
+ height: 1px;
579
+ background: var(--border);
580
+ margin: 6px 10px;
581
+ }
406
582
  .conversation-item {
407
583
  height: 36px;
408
584
  min-height: 36px;
@@ -413,16 +589,26 @@ var renderWebUiHtml = (options) => {
413
589
  cursor: pointer;
414
590
  font-size: 13px;
415
591
  line-height: 36px;
416
- color: #555;
592
+ color: var(--fg-6);
417
593
  white-space: nowrap;
418
594
  overflow: hidden;
419
595
  text-overflow: ellipsis;
420
596
  position: relative;
421
597
  transition: color 0.15s;
422
598
  }
423
- .conversation-item:hover { color: #999; }
599
+ .conversation-item .approval-dot {
600
+ display: inline-block;
601
+ width: 6px;
602
+ height: 6px;
603
+ border-radius: 50%;
604
+ background: var(--warning, #e8a735);
605
+ margin-right: 6px;
606
+ flex-shrink: 0;
607
+ vertical-align: middle;
608
+ }
609
+ .conversation-item:hover { color: var(--fg-3); }
424
610
  .conversation-item.active {
425
- color: #ededed;
611
+ color: var(--fg);
426
612
  }
427
613
  .conversation-item .delete-btn {
428
614
  position: absolute;
@@ -430,9 +616,9 @@ var renderWebUiHtml = (options) => {
430
616
  top: 0;
431
617
  bottom: 0;
432
618
  opacity: 0;
433
- background: #000;
619
+ background: var(--bg);
434
620
  border: 0;
435
- color: #444;
621
+ color: var(--fg-7);
436
622
  padding: 0 8px;
437
623
  border-radius: 0 4px 4px 0;
438
624
  cursor: pointer;
@@ -443,7 +629,7 @@ var renderWebUiHtml = (options) => {
443
629
  transition: opacity 0.15s, color 0.15s;
444
630
  }
445
631
  .conversation-item:hover .delete-btn { opacity: 1; }
446
- .conversation-item.active .delete-btn { background: rgba(0,0,0,1); }
632
+ .conversation-item.active .delete-btn { background: var(--bg); }
447
633
  .conversation-item .delete-btn::before {
448
634
  content: "";
449
635
  position: absolute;
@@ -451,23 +637,23 @@ var renderWebUiHtml = (options) => {
451
637
  top: 0;
452
638
  bottom: 0;
453
639
  width: 24px;
454
- background: linear-gradient(to right, transparent, #000);
640
+ background: linear-gradient(to right, transparent, var(--bg));
455
641
  pointer-events: none;
456
642
  }
457
643
  .conversation-item.active .delete-btn::before {
458
- background: linear-gradient(to right, transparent, rgba(0,0,0,1));
644
+ background: linear-gradient(to right, transparent, var(--bg));
459
645
  }
460
- .conversation-item .delete-btn:hover { color: #888; }
646
+ .conversation-item .delete-btn:hover { color: var(--fg-2); }
461
647
  .conversation-item .delete-btn.confirming {
462
648
  opacity: 1;
463
649
  width: auto;
464
650
  padding: 0 8px;
465
651
  font-size: 11px;
466
- color: #ff4444;
652
+ color: var(--error);
467
653
  border-radius: 3px;
468
654
  }
469
655
  .conversation-item .delete-btn.confirming:hover {
470
- color: #ff6666;
656
+ color: var(--error-alt);
471
657
  }
472
658
  .sidebar-footer {
473
659
  margin-top: auto;
@@ -476,7 +662,7 @@ var renderWebUiHtml = (options) => {
476
662
  .logout-btn {
477
663
  background: transparent;
478
664
  border: 0;
479
- color: #555;
665
+ color: var(--fg-6);
480
666
  width: 100%;
481
667
  padding: 8px 10px;
482
668
  text-align: left;
@@ -485,10 +671,10 @@ var renderWebUiHtml = (options) => {
485
671
  font-size: 13px;
486
672
  transition: color 0.15s, background 0.15s;
487
673
  }
488
- .logout-btn:hover { color: #888; }
674
+ .logout-btn:hover { color: var(--fg-2); }
489
675
 
490
676
  /* Main */
491
- .main { flex: 1; display: flex; flex-direction: column; min-width: 0; max-width: 100%; background: #000; overflow: hidden; }
677
+ .main { flex: 1; display: flex; flex-direction: column; min-width: 0; max-width: 100%; background: var(--bg); overflow: hidden; }
492
678
  .topbar {
493
679
  height: calc(52px + env(safe-area-inset-top, 0px));
494
680
  padding-top: env(safe-area-inset-top, 0px);
@@ -497,8 +683,8 @@ var renderWebUiHtml = (options) => {
497
683
  justify-content: center;
498
684
  font-size: 13px;
499
685
  font-weight: 500;
500
- color: #888;
501
- border-bottom: 1px solid rgba(255,255,255,0.06);
686
+ color: var(--fg-2);
687
+ border-bottom: 1px solid var(--border-1);
502
688
  position: relative;
503
689
  flex-shrink: 0;
504
690
  }
@@ -517,7 +703,7 @@ var renderWebUiHtml = (options) => {
517
703
  bottom: 4px; /* Position from bottom of topbar content area */
518
704
  background: transparent;
519
705
  border: 0;
520
- color: #666;
706
+ color: var(--fg-5);
521
707
  width: 44px;
522
708
  height: 44px;
523
709
  border-radius: 6px;
@@ -527,7 +713,7 @@ var renderWebUiHtml = (options) => {
527
713
  z-index: 10;
528
714
  -webkit-tap-highlight-color: transparent;
529
715
  }
530
- .sidebar-toggle:hover { color: #ededed; }
716
+ .sidebar-toggle:hover { color: var(--fg); }
531
717
  .topbar-new-chat {
532
718
  display: none;
533
719
  position: absolute;
@@ -535,7 +721,7 @@ var renderWebUiHtml = (options) => {
535
721
  bottom: 4px;
536
722
  background: transparent;
537
723
  border: 0;
538
- color: #666;
724
+ color: var(--fg-5);
539
725
  width: 44px;
540
726
  height: 44px;
541
727
  border-radius: 6px;
@@ -544,7 +730,7 @@ var renderWebUiHtml = (options) => {
544
730
  z-index: 10;
545
731
  -webkit-tap-highlight-color: transparent;
546
732
  }
547
- .topbar-new-chat:hover { color: #ededed; }
733
+ .topbar-new-chat:hover { color: var(--fg); }
548
734
  .topbar-new-chat svg { width: 16px; height: 16px; }
549
735
 
550
736
  /* Messages */
@@ -556,8 +742,8 @@ var renderWebUiHtml = (options) => {
556
742
  .assistant-avatar {
557
743
  width: 24px;
558
744
  height: 24px;
559
- background: #ededed;
560
- color: #000;
745
+ background: var(--accent);
746
+ color: var(--accent-fg);
561
747
  border-radius: 6px;
562
748
  display: grid;
563
749
  place-items: center;
@@ -568,7 +754,7 @@ var renderWebUiHtml = (options) => {
568
754
  }
569
755
  .assistant-content {
570
756
  line-height: 1.65;
571
- color: #ededed;
757
+ color: var(--fg);
572
758
  font-size: 14px;
573
759
  min-width: 0;
574
760
  max-width: 100%;
@@ -580,32 +766,32 @@ var renderWebUiHtml = (options) => {
580
766
  .assistant-content p:last-child { margin-bottom: 0; }
581
767
  .assistant-content ul, .assistant-content ol { margin: 8px 0; padding-left: 20px; }
582
768
  .assistant-content li { margin: 4px 0; }
583
- .assistant-content strong { font-weight: 600; color: #fff; }
769
+ .assistant-content strong { font-weight: 600; color: var(--fg-strong); }
584
770
  .assistant-content h2 {
585
771
  font-size: 16px;
586
772
  font-weight: 600;
587
773
  letter-spacing: -0.02em;
588
774
  margin: 20px 0 8px;
589
- color: #fff;
775
+ color: var(--fg-strong);
590
776
  }
591
777
  .assistant-content h3 {
592
778
  font-size: 14px;
593
779
  font-weight: 600;
594
780
  letter-spacing: -0.01em;
595
781
  margin: 16px 0 6px;
596
- color: #fff;
782
+ color: var(--fg-strong);
597
783
  }
598
784
  .assistant-content code {
599
- background: rgba(255,255,255,0.06);
600
- border: 1px solid rgba(255,255,255,0.06);
785
+ background: var(--surface-4);
786
+ border: 1px solid var(--border-1);
601
787
  padding: 2px 5px;
602
788
  border-radius: 4px;
603
789
  font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
604
790
  font-size: 0.88em;
605
791
  }
606
792
  .assistant-content pre {
607
- background: #0a0a0a;
608
- border: 1px solid rgba(255,255,255,0.06);
793
+ background: var(--bg-alt);
794
+ border: 1px solid var(--border-1);
609
795
  padding: 14px 16px;
610
796
  border-radius: 8px;
611
797
  overflow-x: auto;
@@ -622,33 +808,33 @@ var renderWebUiHtml = (options) => {
622
808
  margin: 8px 0;
623
809
  font-size: 12px;
624
810
  line-height: 1.45;
625
- color: #8a8a8a;
811
+ color: var(--fg-tool);
626
812
  }
627
813
  .tool-activity-inline code {
628
814
  font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
629
- background: rgba(255,255,255,0.04);
630
- border: 1px solid rgba(255,255,255,0.08);
815
+ background: var(--surface-3);
816
+ border: 1px solid var(--border-2);
631
817
  padding: 4px 8px;
632
818
  border-radius: 6px;
633
- color: #bcbcbc;
819
+ color: var(--fg-tool-code);
634
820
  font-size: 11px;
635
821
  }
636
822
  .tool-status {
637
- color: #8a8a8a;
823
+ color: var(--fg-tool);
638
824
  font-style: italic;
639
825
  }
640
826
  .tool-done {
641
- color: #6a9955;
827
+ color: var(--tool-done);
642
828
  }
643
829
  .tool-error {
644
- color: #f48771;
830
+ color: var(--tool-error);
645
831
  }
646
- .assistant-content table {
832
+ .assistant-content table:not(.approval-request-table) {
647
833
  border-collapse: collapse;
648
834
  width: 100%;
649
835
  margin: 14px 0;
650
836
  font-size: 13px;
651
- border: 1px solid rgba(255,255,255,0.08);
837
+ border: 1px solid var(--border-2);
652
838
  border-radius: 8px;
653
839
  overflow: hidden;
654
840
  display: block;
@@ -656,42 +842,46 @@ var renderWebUiHtml = (options) => {
656
842
  overflow-x: auto;
657
843
  white-space: nowrap;
658
844
  }
659
- .assistant-content th {
660
- background: rgba(255,255,255,0.06);
845
+ .assistant-content table:not(.approval-request-table) th {
846
+ background: var(--surface-4);
661
847
  padding: 10px 12px;
662
848
  text-align: left;
663
849
  font-weight: 600;
664
- border-bottom: 1px solid rgba(255,255,255,0.12);
665
- color: #fff;
850
+ border-bottom: 1px solid var(--border-4);
851
+ color: var(--fg-strong);
666
852
  min-width: 100px;
667
853
  }
668
- .assistant-content td {
854
+ .assistant-content table:not(.approval-request-table) td {
669
855
  padding: 10px 12px;
670
- border-bottom: 1px solid rgba(255,255,255,0.06);
856
+ border-bottom: 1px solid var(--border-1);
671
857
  width: 100%;
672
858
  min-width: 100px;
673
859
  }
674
- .assistant-content tr:last-child td {
860
+ .assistant-content table:not(.approval-request-table) tr:last-child td {
675
861
  border-bottom: none;
676
862
  }
677
- .assistant-content tbody tr:hover {
678
- background: rgba(255,255,255,0.02);
863
+ .assistant-content table:not(.approval-request-table) tbody tr:hover {
864
+ background: var(--surface-1);
679
865
  }
680
866
  .assistant-content hr {
681
867
  border: 0;
682
- border-top: 1px solid rgba(255,255,255,0.1);
868
+ border-top: 1px solid var(--border-3);
683
869
  margin: 20px 0;
684
870
  }
685
871
  .tool-activity {
686
872
  margin-top: 12px;
687
873
  margin-bottom: 12px;
688
- border: 1px solid rgba(255,255,255,0.08);
689
- background: rgba(255,255,255,0.03);
874
+ border: 1px solid var(--border-2);
875
+ background: var(--surface-2);
690
876
  border-radius: 10px;
691
877
  font-size: 12px;
692
878
  line-height: 1.45;
693
- color: #bcbcbc;
879
+ color: var(--fg-tool-code);
694
880
  width: 300px;
881
+ transition: width 0.2s ease;
882
+ }
883
+ .tool-activity.has-approvals {
884
+ width: 100%;
695
885
  }
696
886
  .assistant-content > .tool-activity:first-child {
697
887
  margin-top: 0;
@@ -715,12 +905,12 @@ var renderWebUiHtml = (options) => {
715
905
  font-size: 11px;
716
906
  text-transform: uppercase;
717
907
  letter-spacing: 0.06em;
718
- color: #8a8a8a;
908
+ color: var(--fg-tool);
719
909
  font-weight: 600;
720
910
  }
721
911
  .tool-activity-caret {
722
912
  margin-left: auto;
723
- color: #8a8a8a;
913
+ color: var(--fg-tool);
724
914
  display: inline-flex;
725
915
  align-items: center;
726
916
  justify-content: center;
@@ -742,49 +932,69 @@ var renderWebUiHtml = (options) => {
742
932
  }
743
933
  .tool-activity-item {
744
934
  font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
745
- background: rgba(255,255,255,0.04);
935
+ background: var(--surface-3);
746
936
  border-radius: 6px;
747
937
  padding: 4px 7px;
748
- color: #d6d6d6;
938
+ color: var(--fg-tool-item);
749
939
  }
750
940
  .approval-requests {
751
- border-top: 1px solid rgba(255,255,255,0.08);
941
+ border-top: 1px solid var(--border-2);
752
942
  padding: 10px 12px 12px;
753
943
  display: grid;
754
- gap: 8px;
755
- background: rgba(0,0,0,0.16);
944
+ gap: 10px;
756
945
  }
757
946
  .approval-requests-label {
758
- font-size: 11px;
947
+ font-size: 12px;
759
948
  text-transform: uppercase;
760
949
  letter-spacing: 0.06em;
761
- color: #b0b0b0;
950
+ color: var(--fg-approval-label);
762
951
  font-weight: 600;
763
952
  }
953
+ .approval-requests-label code {
954
+ font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
955
+ text-transform: none;
956
+ letter-spacing: 0;
957
+ color: var(--fg-strong);
958
+ }
764
959
  .approval-request-item {
765
- border: 1px solid rgba(255,255,255,0.1);
766
- background: rgba(255,255,255,0.03);
767
- border-radius: 8px;
768
- padding: 8px;
769
960
  display: grid;
770
- gap: 6px;
961
+ gap: 8px;
771
962
  }
772
- .approval-request-tool {
773
- font-size: 12px;
774
- color: #fff;
963
+ .approval-request-table {
964
+ width: 100%;
965
+ border-collapse: collapse;
966
+ border: none;
967
+ font-size: 14px;
968
+ line-height: 1.5;
969
+ }
970
+ .approval-request-table tr,
971
+ .approval-request-table td {
972
+ border: none;
973
+ background: none;
974
+ }
975
+ .approval-request-table td {
976
+ padding: 4px 0;
977
+ vertical-align: top;
978
+ }
979
+ .approval-request-table .ak {
775
980
  font-weight: 600;
776
- overflow-wrap: anywhere;
981
+ color: var(--fg-approval-label);
982
+ white-space: nowrap;
983
+ width: 1%;
984
+ padding-right: 20px;
777
985
  }
778
- .approval-request-input {
779
- font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
780
- font-size: 11px;
781
- color: #cfcfcf;
782
- background: rgba(0,0,0,0.25);
783
- border-radius: 6px;
784
- padding: 6px;
986
+ .approval-request-table .av,
987
+ .approval-request-table .av-complex {
988
+ color: var(--fg);
785
989
  overflow-wrap: anywhere;
786
- max-height: 80px;
990
+ white-space: pre-wrap;
991
+ max-height: 200px;
787
992
  overflow-y: auto;
993
+ display: block;
994
+ }
995
+ .approval-request-table .av-complex {
996
+ font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
997
+ font-size: 12px;
788
998
  }
789
999
  .approval-request-actions {
790
1000
  display: flex;
@@ -792,32 +1002,32 @@ var renderWebUiHtml = (options) => {
792
1002
  }
793
1003
  .approval-action-btn {
794
1004
  border-radius: 6px;
795
- border: 1px solid rgba(255,255,255,0.18);
796
- background: rgba(255,255,255,0.06);
797
- color: #f0f0f0;
1005
+ border: 1px solid var(--border-5);
1006
+ background: var(--surface-4);
1007
+ color: var(--fg-approval-btn);
798
1008
  font-size: 11px;
799
1009
  font-weight: 600;
800
1010
  padding: 4px 8px;
801
1011
  cursor: pointer;
802
1012
  }
803
1013
  .approval-action-btn:hover {
804
- background: rgba(255,255,255,0.12);
1014
+ background: var(--surface-7);
805
1015
  }
806
1016
  .approval-action-btn.approve {
807
- border-color: rgba(58, 208, 122, 0.45);
808
- color: #78e7a6;
1017
+ border-color: var(--approve-border);
1018
+ color: var(--approve);
809
1019
  }
810
1020
  .approval-action-btn.deny {
811
- border-color: rgba(224, 95, 95, 0.45);
812
- color: #f59b9b;
1021
+ border-color: var(--deny-border);
1022
+ color: var(--deny);
813
1023
  }
814
1024
  .approval-action-btn[disabled] {
815
1025
  opacity: 0.55;
816
1026
  cursor: not-allowed;
817
1027
  }
818
1028
  .user-bubble {
819
- background: #111;
820
- border: 1px solid rgba(255,255,255,0.08);
1029
+ background: var(--bg-elevated);
1030
+ border: 1px solid var(--border-2);
821
1031
  padding: 10px 16px;
822
1032
  border-radius: 18px;
823
1033
  max-width: 70%;
@@ -825,6 +1035,7 @@ var renderWebUiHtml = (options) => {
825
1035
  line-height: 1.5;
826
1036
  overflow-wrap: break-word;
827
1037
  word-break: break-word;
1038
+ white-space: pre-wrap;
828
1039
  }
829
1040
  .empty-state {
830
1041
  display: flex;
@@ -833,7 +1044,7 @@ var renderWebUiHtml = (options) => {
833
1044
  justify-content: center;
834
1045
  height: 100%;
835
1046
  gap: 16px;
836
- color: #555;
1047
+ color: var(--fg-6);
837
1048
  }
838
1049
  .empty-state .assistant-avatar {
839
1050
  width: 36px;
@@ -843,7 +1054,7 @@ var renderWebUiHtml = (options) => {
843
1054
  }
844
1055
  .empty-state-text {
845
1056
  font-size: 14px;
846
- color: #555;
1057
+ color: var(--fg-6);
847
1058
  }
848
1059
  .thinking-indicator {
849
1060
  display: inline-block;
@@ -851,7 +1062,7 @@ var renderWebUiHtml = (options) => {
851
1062
  font-size: 20px;
852
1063
  line-height: 1;
853
1064
  vertical-align: middle;
854
- color: #ededed;
1065
+ color: var(--fg);
855
1066
  opacity: 0.5;
856
1067
  }
857
1068
  .thinking-status {
@@ -859,13 +1070,13 @@ var renderWebUiHtml = (options) => {
859
1070
  align-items: center;
860
1071
  gap: 8px;
861
1072
  margin-top: 2px;
862
- color: #8a8a8a;
1073
+ color: var(--fg-tool);
863
1074
  font-size: 14px;
864
1075
  line-height: 1.65;
865
1076
  font-weight: 400;
866
1077
  }
867
1078
  .thinking-status-label {
868
- color: #8a8a8a;
1079
+ color: var(--fg-tool);
869
1080
  font-size: 14px;
870
1081
  line-height: 1.65;
871
1082
  font-weight: 400;
@@ -896,26 +1107,26 @@ var renderWebUiHtml = (options) => {
896
1107
  right: 0;
897
1108
  bottom: 100%;
898
1109
  height: 48px;
899
- background: linear-gradient(to top, #000 0%, transparent 100%);
1110
+ background: linear-gradient(to top, var(--bg) 0%, transparent 100%);
900
1111
  pointer-events: none;
901
1112
  }
902
1113
  .composer-inner { max-width: 680px; margin: 0 auto; }
903
1114
  .composer-shell {
904
- background: #0a0a0a;
905
- border: 1px solid rgba(255,255,255,0.1);
1115
+ background: var(--bg-alt);
1116
+ border: 1px solid var(--border-3);
906
1117
  border-radius: 24px;
907
1118
  display: flex;
908
1119
  align-items: end;
909
1120
  padding: 4px 6px 4px 6px;
910
1121
  transition: border-color 0.15s;
911
1122
  }
912
- .composer-shell:focus-within { border-color: rgba(255,255,255,0.2); }
1123
+ .composer-shell:focus-within { border-color: var(--border-focus); }
913
1124
  .composer-input {
914
1125
  flex: 1;
915
1126
  background: transparent;
916
1127
  border: 0;
917
1128
  outline: none;
918
- color: #ededed;
1129
+ color: var(--fg);
919
1130
  min-height: 40px;
920
1131
  max-height: 200px;
921
1132
  resize: none;
@@ -924,14 +1135,14 @@ var renderWebUiHtml = (options) => {
924
1135
  line-height: 1.5;
925
1136
  margin-top: -4px;
926
1137
  }
927
- .composer-input::placeholder { color: #444; }
1138
+ .composer-input::placeholder { color: var(--fg-7); }
928
1139
  .send-btn {
929
1140
  width: 32px;
930
1141
  height: 32px;
931
- background: #ededed;
1142
+ background: var(--accent);
932
1143
  border: 0;
933
1144
  border-radius: 50%;
934
- color: #000;
1145
+ color: var(--accent-fg);
935
1146
  cursor: pointer;
936
1147
  display: grid;
937
1148
  place-items: center;
@@ -939,21 +1150,79 @@ var renderWebUiHtml = (options) => {
939
1150
  margin-bottom: 2px;
940
1151
  transition: background 0.15s, opacity 0.15s;
941
1152
  }
942
- .send-btn:hover { background: #fff; }
1153
+ .send-btn:hover { background: var(--accent-hover); }
943
1154
  .send-btn.stop-mode {
944
- background: #4a4a4a;
945
- color: #fff;
1155
+ background: var(--stop-bg);
1156
+ color: var(--stop-fg);
946
1157
  }
947
- .send-btn.stop-mode:hover { background: #565656; }
1158
+ .send-btn.stop-mode:hover { background: var(--stop-hover); }
948
1159
  .send-btn:disabled { opacity: 0.2; cursor: default; }
949
- .send-btn:disabled:hover { background: #ededed; }
1160
+ .send-btn:disabled:hover { background: var(--accent); }
1161
+ .send-btn-wrapper {
1162
+ position: relative;
1163
+ width: 36px;
1164
+ height: 36px;
1165
+ display: grid;
1166
+ place-items: center;
1167
+ flex-shrink: 0;
1168
+ margin-bottom: 0;
1169
+ }
1170
+ .send-btn-wrapper .send-btn {
1171
+ margin-bottom: 0;
1172
+ }
1173
+ .context-ring {
1174
+ position: absolute;
1175
+ inset: 0;
1176
+ width: 36px;
1177
+ height: 36px;
1178
+ pointer-events: none;
1179
+ transform: rotate(-90deg);
1180
+ }
1181
+ .context-ring-fill {
1182
+ fill: none;
1183
+ stroke: var(--bg-alt);
1184
+ stroke-width: 3;
1185
+ stroke-linecap: butt;
1186
+ transition: stroke-dashoffset 0.4s ease, stroke 0.3s ease;
1187
+ }
1188
+ .send-btn-wrapper.stop-mode .context-ring-fill {
1189
+ stroke: var(--fg-3);
1190
+ }
1191
+ .context-ring-fill.warning {
1192
+ stroke: #e5a33d;
1193
+ }
1194
+ .context-ring-fill.critical {
1195
+ stroke: #e55d4a;
1196
+ }
1197
+ .context-tooltip {
1198
+ position: absolute;
1199
+ bottom: calc(100% + 8px);
1200
+ right: 0;
1201
+ background: var(--bg-elevated);
1202
+ border: 1px solid var(--border-3);
1203
+ border-radius: 8px;
1204
+ padding: 6px 10px;
1205
+ font-size: 12px;
1206
+ color: var(--fg-2);
1207
+ white-space: nowrap;
1208
+ pointer-events: none;
1209
+ opacity: 0;
1210
+ transform: translateY(4px);
1211
+ transition: opacity 0.15s, transform 0.15s;
1212
+ z-index: 10;
1213
+ }
1214
+ .send-btn-wrapper:hover .context-tooltip,
1215
+ .send-btn-wrapper:focus-within .context-tooltip {
1216
+ opacity: 1;
1217
+ transform: translateY(0);
1218
+ }
950
1219
  .attach-btn {
951
1220
  width: 32px;
952
1221
  height: 32px;
953
- background: rgba(255,255,255,0.08);
1222
+ background: var(--surface-5);
954
1223
  border: 0;
955
1224
  border-radius: 50%;
956
- color: #999;
1225
+ color: var(--fg-3);
957
1226
  cursor: pointer;
958
1227
  display: grid;
959
1228
  place-items: center;
@@ -962,7 +1231,7 @@ var renderWebUiHtml = (options) => {
962
1231
  margin-right: 8px;
963
1232
  transition: color 0.15s, background 0.15s;
964
1233
  }
965
- .attach-btn:hover { color: #ededed; background: rgba(255,255,255,0.14); }
1234
+ .attach-btn:hover { color: var(--fg); background: var(--surface-8); }
966
1235
  .attachment-preview {
967
1236
  display: flex;
968
1237
  gap: 8px;
@@ -973,12 +1242,12 @@ var renderWebUiHtml = (options) => {
973
1242
  display: inline-flex;
974
1243
  align-items: center;
975
1244
  gap: 6px;
976
- background: rgba(0, 0, 0, 0.6);
977
- border: 1px solid rgba(255, 255, 255, 0.12);
1245
+ background: var(--chip-bg);
1246
+ border: 1px solid var(--border-4);
978
1247
  border-radius: 9999px;
979
1248
  padding: 4px 10px 4px 6px;
980
1249
  font-size: 11px;
981
- color: #777;
1250
+ color: var(--fg-4);
982
1251
  max-width: 200px;
983
1252
  cursor: pointer;
984
1253
  backdrop-filter: blur(6px);
@@ -986,9 +1255,9 @@ var renderWebUiHtml = (options) => {
986
1255
  transition: color 0.15s, border-color 0.15s, background 0.15s;
987
1256
  }
988
1257
  .attachment-chip:hover {
989
- color: #ededed;
990
- border-color: rgba(255, 255, 255, 0.25);
991
- background: rgba(0, 0, 0, 0.75);
1258
+ color: var(--fg);
1259
+ border-color: var(--border-hover);
1260
+ background: var(--chip-bg-hover);
992
1261
  }
993
1262
  .attachment-chip img {
994
1263
  width: 20px;
@@ -1002,7 +1271,7 @@ var renderWebUiHtml = (options) => {
1002
1271
  width: 20px;
1003
1272
  height: 20px;
1004
1273
  border-radius: 50%;
1005
- background: rgba(255,255,255,0.1);
1274
+ background: var(--surface-6);
1006
1275
  display: grid;
1007
1276
  place-items: center;
1008
1277
  font-size: 11px;
@@ -1010,13 +1279,13 @@ var renderWebUiHtml = (options) => {
1010
1279
  }
1011
1280
  .attachment-chip .remove-attachment {
1012
1281
  cursor: pointer;
1013
- color: #555;
1282
+ color: var(--fg-6);
1014
1283
  font-size: 14px;
1015
1284
  margin-left: 2px;
1016
1285
  line-height: 1;
1017
1286
  transition: color 0.15s;
1018
1287
  }
1019
- .attachment-chip .remove-attachment:hover { color: #fff; }
1288
+ .attachment-chip .remove-attachment:hover { color: var(--fg-strong); }
1020
1289
  .attachment-chip .filename { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100px; }
1021
1290
  .user-bubble .user-file-attachments {
1022
1291
  display: flex;
@@ -1046,7 +1315,7 @@ var renderWebUiHtml = (options) => {
1046
1315
  transition: background 0.25s ease, backdrop-filter 0.25s ease;
1047
1316
  }
1048
1317
  .lightbox.active {
1049
- background: rgba(0,0,0,0.85);
1318
+ background: var(--lightbox-bg);
1050
1319
  backdrop-filter: blur(8px);
1051
1320
  }
1052
1321
  .lightbox img {
@@ -1066,16 +1335,16 @@ var renderWebUiHtml = (options) => {
1066
1335
  display: inline-flex;
1067
1336
  align-items: center;
1068
1337
  gap: 4px;
1069
- background: rgba(0,0,0,0.2);
1338
+ background: var(--file-badge-bg);
1070
1339
  border-radius: 6px;
1071
1340
  padding: 4px 8px;
1072
1341
  font-size: 12px;
1073
- color: rgba(255,255,255,0.8);
1342
+ color: var(--file-badge-fg);
1074
1343
  }
1075
1344
  .drag-overlay {
1076
1345
  position: fixed;
1077
1346
  inset: 0;
1078
- background: rgba(0,0,0,0.6);
1347
+ background: var(--backdrop);
1079
1348
  z-index: 9999;
1080
1349
  display: none;
1081
1350
  align-items: center;
@@ -1084,15 +1353,15 @@ var renderWebUiHtml = (options) => {
1084
1353
  }
1085
1354
  .drag-overlay.active { display: flex; }
1086
1355
  .drag-overlay-inner {
1087
- border: 2px dashed rgba(255,255,255,0.4);
1356
+ border: 2px dashed var(--border-drag);
1088
1357
  border-radius: 16px;
1089
1358
  padding: 40px 60px;
1090
- color: #fff;
1359
+ color: var(--fg-strong);
1091
1360
  font-size: 16px;
1092
1361
  }
1093
1362
  .disclaimer {
1094
1363
  text-align: center;
1095
- color: #333;
1364
+ color: var(--fg-8);
1096
1365
  font-size: 12px;
1097
1366
  margin-top: 10px;
1098
1367
  }
@@ -1107,10 +1376,10 @@ var renderWebUiHtml = (options) => {
1107
1376
  align-items: center;
1108
1377
  gap: 6px;
1109
1378
  font-size: 11px;
1110
- color: #777;
1379
+ color: var(--fg-4);
1111
1380
  text-decoration: none;
1112
- background: rgba(0, 0, 0, 0.6);
1113
- border: 1px solid rgba(255, 255, 255, 0.12);
1381
+ background: var(--chip-bg);
1382
+ border: 1px solid var(--border-4);
1114
1383
  border-radius: 9999px;
1115
1384
  padding: 4px 10px 4px 6px;
1116
1385
  backdrop-filter: blur(6px);
@@ -1118,9 +1387,9 @@ var renderWebUiHtml = (options) => {
1118
1387
  transition: color 0.15s, border-color 0.15s, background 0.15s;
1119
1388
  }
1120
1389
  .poncho-badge:hover {
1121
- color: #ededed;
1122
- border-color: rgba(255, 255, 255, 0.25);
1123
- background: rgba(0, 0, 0, 0.75);
1390
+ color: var(--fg);
1391
+ border-color: var(--border-hover);
1392
+ background: var(--chip-bg-hover);
1124
1393
  }
1125
1394
  .poncho-badge-avatar {
1126
1395
  width: 16px;
@@ -1134,8 +1403,8 @@ var renderWebUiHtml = (options) => {
1134
1403
  /* Scrollbar */
1135
1404
  ::-webkit-scrollbar { width: 6px; }
1136
1405
  ::-webkit-scrollbar-track { background: transparent; }
1137
- ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
1138
- ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.16); }
1406
+ ::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; }
1407
+ ::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); }
1139
1408
 
1140
1409
  /* Mobile */
1141
1410
  @media (max-width: 768px) {
@@ -1165,7 +1434,7 @@ var renderWebUiHtml = (options) => {
1165
1434
  .sidebar-backdrop {
1166
1435
  position: fixed;
1167
1436
  inset: 0;
1168
- background: rgba(0,0,0,0.6);
1437
+ background: var(--backdrop);
1169
1438
  z-index: 50;
1170
1439
  backdrop-filter: blur(2px);
1171
1440
  -webkit-backdrop-filter: blur(2px);
@@ -1240,9 +1509,15 @@ var renderWebUiHtml = (options) => {
1240
1509
  </button>
1241
1510
  <input id="file-input" type="file" multiple accept="image/*,video/*,application/pdf,.txt,.csv,.json,.html" style="display:none" />
1242
1511
  <textarea id="prompt" class="composer-input" placeholder="Send a message..." rows="1"></textarea>
1243
- <button id="send" class="send-btn" type="submit">
1244
- <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>
1245
- </button>
1512
+ <div class="send-btn-wrapper" id="send-btn-wrapper">
1513
+ <svg class="context-ring" viewBox="0 0 36 36">
1514
+ <circle class="context-ring-fill" id="context-ring-fill" cx="18" cy="18" r="14.5" />
1515
+ </svg>
1516
+ <button id="send" class="send-btn" type="submit">
1517
+ <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>
1518
+ </button>
1519
+ <div class="context-tooltip" id="context-tooltip"></div>
1520
+ </div>
1246
1521
  </div>
1247
1522
  </div>
1248
1523
  </form>
@@ -1274,6 +1549,8 @@ var renderWebUiHtml = (options) => {
1274
1549
  confirmDeleteId: null,
1275
1550
  approvalRequestsInFlight: {},
1276
1551
  pendingFiles: [],
1552
+ contextTokens: 0,
1553
+ contextWindow: 0,
1277
1554
  };
1278
1555
 
1279
1556
  const agentInitial = document.body.dataset.agentInitial || "A";
@@ -1301,12 +1578,41 @@ var renderWebUiHtml = (options) => {
1301
1578
  attachmentPreview: $("attachment-preview"),
1302
1579
  dragOverlay: $("drag-overlay"),
1303
1580
  lightbox: $("lightbox"),
1581
+ contextRingFill: $("context-ring-fill"),
1582
+ contextTooltip: $("context-tooltip"),
1583
+ sendBtnWrapper: $("send-btn-wrapper"),
1304
1584
  };
1305
1585
  const sendIconMarkup =
1306
1586
  '<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>';
1307
1587
  const stopIconMarkup =
1308
1588
  '<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>';
1309
1589
 
1590
+ const CONTEXT_RING_CIRCUMFERENCE = 2 * Math.PI * 14.5;
1591
+ const formatTokenCount = (n) => {
1592
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\\.0$/, "") + "M";
1593
+ if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\\.0$/, "") + "k";
1594
+ return String(n);
1595
+ };
1596
+ const updateContextRing = () => {
1597
+ const ring = elements.contextRingFill;
1598
+ const tooltip = elements.contextTooltip;
1599
+ if (!ring || !tooltip) return;
1600
+ if (state.contextWindow <= 0) {
1601
+ ring.style.strokeDasharray = String(CONTEXT_RING_CIRCUMFERENCE);
1602
+ ring.style.strokeDashoffset = String(CONTEXT_RING_CIRCUMFERENCE);
1603
+ tooltip.textContent = "";
1604
+ return;
1605
+ }
1606
+ const ratio = Math.min(state.contextTokens / state.contextWindow, 1);
1607
+ const offset = CONTEXT_RING_CIRCUMFERENCE * (1 - ratio);
1608
+ ring.style.strokeDasharray = String(CONTEXT_RING_CIRCUMFERENCE);
1609
+ ring.style.strokeDashoffset = String(offset);
1610
+ ring.classList.toggle("warning", ratio >= 0.7 && ratio < 0.9);
1611
+ ring.classList.toggle("critical", ratio >= 0.9);
1612
+ const pct = (ratio * 100).toFixed(1).replace(/\\.0$/, "");
1613
+ tooltip.textContent = formatTokenCount(state.contextTokens) + " / " + formatTokenCount(state.contextWindow) + " tokens (" + pct + "%)";
1614
+ };
1615
+
1310
1616
  const pushConversationUrl = (conversationId) => {
1311
1617
  const target = conversationId ? "/c/" + encodeURIComponent(conversationId) : "/";
1312
1618
  if (window.location.pathname !== target) {
@@ -1403,18 +1709,20 @@ var renderWebUiHtml = (options) => {
1403
1709
  .map((req) => {
1404
1710
  const approvalId = typeof req.approvalId === "string" ? req.approvalId : "";
1405
1711
  const tool = typeof req.tool === "string" ? req.tool : "tool";
1406
- const inputPreview = typeof req.inputPreview === "string" ? req.inputPreview : "{}";
1712
+ const input = req.input != null ? req.input : {};
1407
1713
  const submitting = req.state === "submitting";
1408
1714
  const approveLabel = submitting && req.pendingDecision === "approve" ? "Approving..." : "Approve";
1409
1715
  const denyLabel = submitting && req.pendingDecision === "deny" ? "Denying..." : "Deny";
1716
+ const errorHtml = req._error
1717
+ ? '<div style="color: var(--deny); font-size: 11px; margin-top: 4px;">Submit failed: ' + escapeHtml(req._error) + "</div>"
1718
+ : "";
1410
1719
  return (
1411
1720
  '<div class="approval-request-item">' +
1412
- '<div class="approval-request-tool">' +
1721
+ '<div class="approval-requests-label">Approval required: <code>' +
1413
1722
  escapeHtml(tool) +
1414
- "</div>" +
1415
- '<div class="approval-request-input">' +
1416
- escapeHtml(inputPreview) +
1417
- "</div>" +
1723
+ "</code></div>" +
1724
+ renderInputTable(input) +
1725
+ errorHtml +
1418
1726
  '<div class="approval-request-actions">' +
1419
1727
  '<button class="approval-action-btn approve" data-approval-id="' +
1420
1728
  escapeHtml(approvalId) +
@@ -1437,7 +1745,6 @@ var renderWebUiHtml = (options) => {
1437
1745
  .join("");
1438
1746
  return (
1439
1747
  '<div class="approval-requests">' +
1440
- '<div class="approval-requests-label">Approval required</div>' +
1441
1748
  rows +
1442
1749
  "</div>"
1443
1750
  );
@@ -1467,22 +1774,46 @@ var renderWebUiHtml = (options) => {
1467
1774
  "</details>"
1468
1775
  )
1469
1776
  : "";
1777
+ const cls = "tool-activity" + (hasApprovals ? " has-approvals" : "");
1470
1778
  return (
1471
- '<div class="tool-activity">' +
1779
+ '<div class="' + cls + '">' +
1472
1780
  disclosure +
1473
1781
  renderApprovalRequests(approvalRequests) +
1474
1782
  "</div>"
1475
1783
  );
1476
1784
  };
1477
1785
 
1478
- const safeJsonPreview = (value) => {
1479
- try {
1480
- return JSON.stringify(value, (_, nestedValue) =>
1481
- typeof nestedValue === "bigint" ? String(nestedValue) : nestedValue,
1482
- );
1483
- } catch {
1484
- return "[unserializable input]";
1786
+ const renderInputTable = (input) => {
1787
+ if (!input || typeof input !== "object") {
1788
+ return '<div class="av-complex">' + escapeHtml(String(input ?? "{}")) + "</div>";
1789
+ }
1790
+ const keys = Object.keys(input);
1791
+ if (keys.length === 0) {
1792
+ return '<div class="av-complex">{}</div>';
1485
1793
  }
1794
+ const formatValue = (val) => {
1795
+ if (val === null || val === undefined) return escapeHtml("null");
1796
+ if (typeof val === "boolean" || typeof val === "number") return escapeHtml(String(val));
1797
+ if (typeof val === "string") return escapeHtml(val);
1798
+ try {
1799
+ const replacer = (_, v) => typeof v === "bigint" ? String(v) : v;
1800
+ return escapeHtml(JSON.stringify(val, replacer, 2));
1801
+ } catch {
1802
+ return escapeHtml("[unserializable]");
1803
+ }
1804
+ };
1805
+ const rows = keys.map((key) => {
1806
+ const val = input[key];
1807
+ const isComplex = val !== null && typeof val === "object";
1808
+ const cls = isComplex ? "av-complex" : "av";
1809
+ return (
1810
+ "<tr>" +
1811
+ '<td class="ak">' + escapeHtml(key) + "</td>" +
1812
+ '<td><div class="' + cls + '">' + formatValue(val) + "</div></td>" +
1813
+ "</tr>"
1814
+ );
1815
+ }).join("");
1816
+ return '<table class="approval-request-table">' + rows + "</table>";
1486
1817
  };
1487
1818
 
1488
1819
  const updatePendingApproval = (approvalId, updater) => {
@@ -1521,12 +1852,10 @@ var renderWebUiHtml = (options) => {
1521
1852
  return null;
1522
1853
  }
1523
1854
  const toolName = item && typeof item.tool === "string" ? item.tool : "tool";
1524
- const preview = safeJsonPreview(item?.input ?? {});
1525
- const inputPreview = preview.length > 600 ? preview.slice(0, 600) + "..." : preview;
1526
1855
  return {
1527
1856
  approvalId,
1528
1857
  tool: toolName,
1529
- inputPreview,
1858
+ input: item?.input ?? {},
1530
1859
  state: "pending",
1531
1860
  };
1532
1861
  })
@@ -1604,50 +1933,87 @@ var renderWebUiHtml = (options) => {
1604
1933
  elements.shell.classList.toggle("sidebar-open", open);
1605
1934
  };
1606
1935
 
1607
- const renderConversationList = () => {
1608
- elements.list.innerHTML = "";
1609
- for (const c of state.conversations) {
1610
- const item = document.createElement("div");
1611
- item.className = "conversation-item" + (c.conversationId === state.activeConversationId ? " active" : "");
1612
- item.textContent = c.title;
1613
-
1614
- const isConfirming = state.confirmDeleteId === c.conversationId;
1615
- const deleteBtn = document.createElement("button");
1616
- deleteBtn.className = "delete-btn" + (isConfirming ? " confirming" : "");
1617
- deleteBtn.textContent = isConfirming ? "sure?" : "\\u00d7";
1618
- deleteBtn.onclick = async (e) => {
1619
- e.stopPropagation();
1620
- if (!isConfirming) {
1621
- state.confirmDeleteId = c.conversationId;
1622
- renderConversationList();
1623
- return;
1624
- }
1625
- await api("/api/conversations/" + c.conversationId, { method: "DELETE" });
1626
- if (state.activeConversationId === c.conversationId) {
1627
- state.activeConversationId = null;
1628
- state.activeMessages = [];
1629
- pushConversationUrl(null);
1630
- elements.chatTitle.textContent = "";
1631
- renderMessages([]);
1632
- }
1633
- state.confirmDeleteId = null;
1634
- await loadConversations();
1635
- };
1636
- item.appendChild(deleteBtn);
1936
+ const buildConversationItem = (c) => {
1937
+ const item = document.createElement("div");
1938
+ item.className = "conversation-item" + (c.conversationId === state.activeConversationId ? " active" : "");
1637
1939
 
1638
- item.onclick = async () => {
1639
- // Clear any delete confirmation, but still navigate
1640
- if (state.confirmDeleteId) {
1641
- state.confirmDeleteId = null;
1642
- }
1643
- state.activeConversationId = c.conversationId;
1644
- pushConversationUrl(c.conversationId);
1940
+ if (c.hasPendingApprovals) {
1941
+ const dot = document.createElement("span");
1942
+ dot.className = "approval-dot";
1943
+ item.appendChild(dot);
1944
+ }
1945
+
1946
+ const titleSpan = document.createElement("span");
1947
+ titleSpan.textContent = c.title;
1948
+ item.appendChild(titleSpan);
1949
+
1950
+ const isConfirming = state.confirmDeleteId === c.conversationId;
1951
+ const deleteBtn = document.createElement("button");
1952
+ deleteBtn.className = "delete-btn" + (isConfirming ? " confirming" : "");
1953
+ deleteBtn.textContent = isConfirming ? "sure?" : "\\u00d7";
1954
+ deleteBtn.onclick = async (e) => {
1955
+ e.stopPropagation();
1956
+ if (!isConfirming) {
1957
+ state.confirmDeleteId = c.conversationId;
1645
1958
  renderConversationList();
1646
- await loadConversation(c.conversationId);
1647
- if (isMobile()) setSidebarOpen(false);
1648
- };
1959
+ return;
1960
+ }
1961
+ await api("/api/conversations/" + c.conversationId, { method: "DELETE" });
1962
+ if (state.activeConversationId === c.conversationId) {
1963
+ state.activeConversationId = null;
1964
+ state.activeMessages = [];
1965
+ state.contextTokens = 0;
1966
+ state.contextWindow = 0;
1967
+ updateContextRing();
1968
+ pushConversationUrl(null);
1969
+ elements.chatTitle.textContent = "";
1970
+ renderMessages([]);
1971
+ }
1972
+ state.confirmDeleteId = null;
1973
+ await loadConversations();
1974
+ };
1975
+ item.appendChild(deleteBtn);
1976
+
1977
+ item.onclick = async () => {
1978
+ if (state.confirmDeleteId) {
1979
+ state.confirmDeleteId = null;
1980
+ }
1981
+ state.activeConversationId = c.conversationId;
1982
+ pushConversationUrl(c.conversationId);
1983
+ renderConversationList();
1984
+ await loadConversation(c.conversationId);
1985
+ if (isMobile()) setSidebarOpen(false);
1986
+ };
1987
+
1988
+ return item;
1989
+ };
1990
+
1991
+ const renderConversationList = () => {
1992
+ elements.list.innerHTML = "";
1993
+ const pending = state.conversations.filter(c => c.hasPendingApprovals);
1994
+ const rest = state.conversations.filter(c => !c.hasPendingApprovals);
1995
+
1996
+ if (pending.length > 0) {
1997
+ const label = document.createElement("div");
1998
+ label.className = "sidebar-section-label";
1999
+ label.textContent = "Awaiting approval";
2000
+ elements.list.appendChild(label);
2001
+ for (const c of pending) {
2002
+ elements.list.appendChild(buildConversationItem(c));
2003
+ }
2004
+ if (rest.length > 0) {
2005
+ const divider = document.createElement("div");
2006
+ divider.className = "sidebar-section-divider";
2007
+ elements.list.appendChild(divider);
2008
+ const recentLabel = document.createElement("div");
2009
+ recentLabel.className = "sidebar-section-label";
2010
+ recentLabel.textContent = "Recent";
2011
+ elements.list.appendChild(recentLabel);
2012
+ }
2013
+ }
1649
2014
 
1650
- elements.list.appendChild(item);
2015
+ for (const c of rest) {
2016
+ elements.list.appendChild(buildConversationItem(c));
1651
2017
  }
1652
2018
  };
1653
2019
 
@@ -1722,8 +2088,14 @@ var renderWebUiHtml = (options) => {
1722
2088
  } else if (shouldRenderEmptyStreamingIndicator) {
1723
2089
  content.appendChild(createThinkingIndicator(getThinkingStatusLabel(m)));
1724
2090
  } else {
1725
- // Check for sections in _sections (streaming) or metadata.sections (stored)
1726
- const sections = m._sections || (m.metadata && m.metadata.sections);
2091
+ // Merge stored sections (persisted) with live sections (from
2092
+ // an active stream). For normal messages only one source
2093
+ // exists; for liveOnly reconnects both contribute.
2094
+ const storedSections = (m.metadata && m.metadata.sections) || [];
2095
+ const liveSections = m._sections || [];
2096
+ const sections = liveSections.length > 0 && storedSections.length > 0
2097
+ ? storedSections.concat(liveSections)
2098
+ : liveSections.length > 0 ? liveSections : (storedSections.length > 0 ? storedSections : null);
1727
2099
  const pendingApprovals = Array.isArray(m._pendingApprovals) ? m._pendingApprovals : [];
1728
2100
 
1729
2101
  if (sections && sections.length > 0) {
@@ -1864,11 +2236,24 @@ var renderWebUiHtml = (options) => {
1864
2236
  payload.conversation.messages || [],
1865
2237
  payload.conversation.pendingApprovals || payload.pendingApprovals || [],
1866
2238
  );
2239
+ state.contextTokens = 0;
2240
+ state.contextWindow = 0;
2241
+ updateContextRing();
1867
2242
  renderMessages(state.activeMessages, false, { forceScrollBottom: true });
1868
2243
  elements.prompt.focus();
2244
+ if (payload.hasActiveRun && !state.isStreaming) {
2245
+ setStreaming(true);
2246
+ streamConversationEvents(conversationId, { liveOnly: true }).finally(() => {
2247
+ if (state.activeConversationId === conversationId) {
2248
+ setStreaming(false);
2249
+ renderMessages(state.activeMessages, false);
2250
+ }
2251
+ });
2252
+ }
1869
2253
  };
1870
2254
 
1871
- const streamConversationEvents = (conversationId) => {
2255
+ const streamConversationEvents = (conversationId, options) => {
2256
+ const liveOnly = options && options.liveOnly;
1872
2257
  return new Promise((resolve) => {
1873
2258
  const localMessages = state.activeMessages || [];
1874
2259
  const renderIfActiveConversation = (streaming) => {
@@ -1887,20 +2272,36 @@ var renderWebUiHtml = (options) => {
1887
2272
  _currentText: "",
1888
2273
  _currentTools: [],
1889
2274
  _pendingApprovals: [],
2275
+ _activeActivities: [],
1890
2276
  metadata: { toolActivity: [] },
1891
2277
  };
1892
2278
  localMessages.push(assistantMessage);
1893
2279
  state.activeMessages = localMessages;
1894
2280
  }
1895
- if (!assistantMessage._sections) assistantMessage._sections = [];
1896
- if (!assistantMessage._currentText) assistantMessage._currentText = "";
1897
- if (!assistantMessage._currentTools) assistantMessage._currentTools = [];
1898
- if (!assistantMessage._activeActivities) assistantMessage._activeActivities = [];
1899
- if (!assistantMessage._pendingApprovals) assistantMessage._pendingApprovals = [];
1900
- if (!assistantMessage.metadata) assistantMessage.metadata = {};
1901
- if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
2281
+ if (liveOnly) {
2282
+ // Live-only mode: keep metadata.sections intact (the stored
2283
+ // base content) and start _sections empty so it only collects
2284
+ // NEW sections from live events. The renderer merges both.
2285
+ assistantMessage._sections = [];
2286
+ assistantMessage._currentText = "";
2287
+ assistantMessage._currentTools = [];
2288
+ if (!assistantMessage._activeActivities) assistantMessage._activeActivities = [];
2289
+ if (!assistantMessage._pendingApprovals) assistantMessage._pendingApprovals = [];
2290
+ if (!assistantMessage.metadata) assistantMessage.metadata = {};
2291
+ if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
2292
+ } else {
2293
+ // Full replay mode: reset transient state so replayed events
2294
+ // rebuild from scratch (the buffer has the full event history).
2295
+ assistantMessage.content = "";
2296
+ assistantMessage._sections = [];
2297
+ assistantMessage._currentText = "";
2298
+ assistantMessage._currentTools = [];
2299
+ assistantMessage._activeActivities = [];
2300
+ assistantMessage._pendingApprovals = [];
2301
+ assistantMessage.metadata = { toolActivity: [] };
2302
+ }
1902
2303
 
1903
- const url = "/api/conversations/" + encodeURIComponent(conversationId) + "/events";
2304
+ const url = "/api/conversations/" + encodeURIComponent(conversationId) + "/events" + (liveOnly ? "?live_only=true" : "");
1904
2305
  fetch(url, { credentials: "include" }).then((response) => {
1905
2306
  if (!response.ok || !response.body) {
1906
2307
  resolve(undefined);
@@ -1921,6 +2322,11 @@ var renderWebUiHtml = (options) => {
1921
2322
  if (eventName === "stream:end") {
1922
2323
  return;
1923
2324
  }
2325
+ if (eventName === "run:started") {
2326
+ if (typeof payload.contextWindow === "number" && payload.contextWindow > 0) {
2327
+ state.contextWindow = payload.contextWindow;
2328
+ }
2329
+ }
1924
2330
  if (eventName === "model:chunk") {
1925
2331
  const chunk = String(payload.content || "");
1926
2332
  if (assistantMessage._currentTools.length > 0 && chunk.length > 0) {
@@ -1934,6 +2340,12 @@ var renderWebUiHtml = (options) => {
1934
2340
  assistantMessage._currentText += chunk;
1935
2341
  renderIfActiveConversation(true);
1936
2342
  }
2343
+ if (eventName === "model:response") {
2344
+ if (typeof payload.usage?.input === "number") {
2345
+ state.contextTokens = payload.usage.input;
2346
+ updateContextRing();
2347
+ }
2348
+ }
1937
2349
  if (eventName === "tool:started") {
1938
2350
  const toolName = payload.tool || "tool";
1939
2351
  const startedActivity = addActiveActivityFromToolStart(
@@ -2006,6 +2418,73 @@ var renderWebUiHtml = (options) => {
2006
2418
  assistantMessage.metadata.toolActivity.push(toolText);
2007
2419
  renderIfActiveConversation(true);
2008
2420
  }
2421
+ if (eventName === "tool:approval:required") {
2422
+ const toolName = payload.tool || "tool";
2423
+ const activeActivity = removeActiveActivityForTool(
2424
+ assistantMessage,
2425
+ toolName,
2426
+ );
2427
+ const detailFromPayload = describeToolStart(payload);
2428
+ const detail =
2429
+ (activeActivity && typeof activeActivity.detail === "string"
2430
+ ? activeActivity.detail.trim()
2431
+ : "") ||
2432
+ (detailFromPayload && typeof detailFromPayload.detail === "string"
2433
+ ? detailFromPayload.detail.trim()
2434
+ : "");
2435
+ const toolText =
2436
+ "- approval required \\x60" +
2437
+ toolName +
2438
+ "\\x60" +
2439
+ (detail ? " (" + detail + ")" : "");
2440
+ assistantMessage._currentTools.push(toolText);
2441
+ assistantMessage.metadata.toolActivity.push(toolText);
2442
+ const approvalId =
2443
+ typeof payload.approvalId === "string" ? payload.approvalId : "";
2444
+ if (approvalId) {
2445
+ if (!Array.isArray(assistantMessage._pendingApprovals)) {
2446
+ assistantMessage._pendingApprovals = [];
2447
+ }
2448
+ const exists = assistantMessage._pendingApprovals.some(
2449
+ (req) => req.approvalId === approvalId,
2450
+ );
2451
+ if (!exists) {
2452
+ assistantMessage._pendingApprovals.push({
2453
+ approvalId,
2454
+ tool: toolName,
2455
+ input: payload.input ?? {},
2456
+ state: "pending",
2457
+ });
2458
+ }
2459
+ }
2460
+ renderIfActiveConversation(true);
2461
+ }
2462
+ if (eventName === "tool:approval:granted") {
2463
+ const toolText = "- approval granted";
2464
+ assistantMessage._currentTools.push(toolText);
2465
+ assistantMessage.metadata.toolActivity.push(toolText);
2466
+ const approvalId =
2467
+ typeof payload.approvalId === "string" ? payload.approvalId : "";
2468
+ if (approvalId && Array.isArray(assistantMessage._pendingApprovals)) {
2469
+ assistantMessage._pendingApprovals = assistantMessage._pendingApprovals.filter(
2470
+ (req) => req.approvalId !== approvalId,
2471
+ );
2472
+ }
2473
+ renderIfActiveConversation(true);
2474
+ }
2475
+ if (eventName === "tool:approval:denied") {
2476
+ const toolText = "- approval denied";
2477
+ assistantMessage._currentTools.push(toolText);
2478
+ assistantMessage.metadata.toolActivity.push(toolText);
2479
+ const approvalId =
2480
+ typeof payload.approvalId === "string" ? payload.approvalId : "";
2481
+ if (approvalId && Array.isArray(assistantMessage._pendingApprovals)) {
2482
+ assistantMessage._pendingApprovals = assistantMessage._pendingApprovals.filter(
2483
+ (req) => req.approvalId !== approvalId,
2484
+ );
2485
+ }
2486
+ renderIfActiveConversation(true);
2487
+ }
2009
2488
  if (eventName === "run:completed") {
2010
2489
  assistantMessage._activeActivities = [];
2011
2490
  if (
@@ -2052,9 +2531,22 @@ var renderWebUiHtml = (options) => {
2052
2531
  }
2053
2532
  if (eventName === "run:error") {
2054
2533
  assistantMessage._activeActivities = [];
2534
+ if (assistantMessage._currentTools.length > 0) {
2535
+ assistantMessage._sections.push({
2536
+ type: "tools",
2537
+ content: assistantMessage._currentTools,
2538
+ });
2539
+ assistantMessage._currentTools = [];
2540
+ }
2541
+ if (assistantMessage._currentText.length > 0) {
2542
+ assistantMessage._sections.push({
2543
+ type: "text",
2544
+ content: assistantMessage._currentText,
2545
+ });
2546
+ assistantMessage._currentText = "";
2547
+ }
2055
2548
  const errMsg =
2056
2549
  payload.error?.message || "Something went wrong";
2057
- assistantMessage.content = "";
2058
2550
  assistantMessage._error = errMsg;
2059
2551
  renderIfActiveConversation(false);
2060
2552
  }
@@ -2127,6 +2619,9 @@ var renderWebUiHtml = (options) => {
2127
2619
  elements.send.disabled = value ? !canStop : false;
2128
2620
  elements.send.innerHTML = value ? stopIconMarkup : sendIconMarkup;
2129
2621
  elements.send.classList.toggle("stop-mode", value);
2622
+ if (elements.sendBtnWrapper) {
2623
+ elements.sendBtnWrapper.classList.toggle("stop-mode", value);
2624
+ }
2130
2625
  elements.send.setAttribute("aria-label", value ? "Stop response" : "Send message");
2131
2626
  elements.send.setAttribute(
2132
2627
  "title",
@@ -2499,8 +2994,17 @@ var renderWebUiHtml = (options) => {
2499
2994
  }
2500
2995
  if (eventName === "run:started") {
2501
2996
  state.activeStreamRunId = typeof payload.runId === "string" ? payload.runId : null;
2997
+ if (typeof payload.contextWindow === "number" && payload.contextWindow > 0) {
2998
+ state.contextWindow = payload.contextWindow;
2999
+ }
2502
3000
  setStreaming(state.isStreaming);
2503
3001
  }
3002
+ if (eventName === "model:response") {
3003
+ if (typeof payload.usage?.input === "number") {
3004
+ state.contextTokens = payload.usage.input;
3005
+ updateContextRing();
3006
+ }
3007
+ }
2504
3008
  if (eventName === "tool:started") {
2505
3009
  const toolName = payload.tool || "tool";
2506
3010
  const startedActivity = addActiveActivityFromToolStart(
@@ -2596,8 +3100,6 @@ var renderWebUiHtml = (options) => {
2596
3100
  const approvalId =
2597
3101
  typeof payload.approvalId === "string" ? payload.approvalId : "";
2598
3102
  if (approvalId) {
2599
- const preview = safeJsonPreview(payload.input ?? {});
2600
- const inputPreview = preview.length > 600 ? preview.slice(0, 600) + "..." : preview;
2601
3103
  if (!Array.isArray(assistantMessage._pendingApprovals)) {
2602
3104
  assistantMessage._pendingApprovals = [];
2603
3105
  }
@@ -2608,7 +3110,7 @@ var renderWebUiHtml = (options) => {
2608
3110
  assistantMessage._pendingApprovals.push({
2609
3111
  approvalId,
2610
3112
  tool: toolName,
2611
- inputPreview,
3113
+ input: payload.input ?? {},
2612
3114
  state: "pending",
2613
3115
  });
2614
3116
  }
@@ -2663,9 +3165,8 @@ var renderWebUiHtml = (options) => {
2663
3165
  renderIfActiveConversation(false);
2664
3166
  }
2665
3167
  if (eventName === "run:error") {
2666
- assistantMessage._activeActivities = [];
3168
+ finalizeAssistantMessage();
2667
3169
  const errMsg = payload.error?.message || "Something went wrong";
2668
- assistantMessage.content = "";
2669
3170
  assistantMessage._error = errMsg;
2670
3171
  renderIfActiveConversation(false);
2671
3172
  }
@@ -2768,6 +3269,9 @@ var renderWebUiHtml = (options) => {
2768
3269
  state.activeConversationId = null;
2769
3270
  state.activeMessages = [];
2770
3271
  state.confirmDeleteId = null;
3272
+ state.contextTokens = 0;
3273
+ state.contextWindow = 0;
3274
+ updateContextRing();
2771
3275
  pushConversationUrl(null);
2772
3276
  elements.chatTitle.textContent = "";
2773
3277
  renderMessages([]);
@@ -2805,6 +3309,9 @@ var renderWebUiHtml = (options) => {
2805
3309
  state.confirmDeleteId = null;
2806
3310
  state.conversations = [];
2807
3311
  state.csrfToken = "";
3312
+ state.contextTokens = 0;
3313
+ state.contextWindow = 0;
3314
+ updateContextRing();
2808
3315
  await requireAuth();
2809
3316
  });
2810
3317
 
@@ -2952,17 +3459,23 @@ var renderWebUiHtml = (options) => {
2952
3459
  });
2953
3460
  updatePendingApproval(approvalId, () => null);
2954
3461
  renderMessages(state.activeMessages, state.isStreaming);
3462
+ loadConversations();
2955
3463
  if (!wasStreaming && state.activeConversationId) {
2956
- await streamConversationEvents(state.activeConversationId);
3464
+ await streamConversationEvents(state.activeConversationId, { liveOnly: true });
2957
3465
  }
2958
3466
  } catch (error) {
2959
- const errMsg = error instanceof Error ? error.message : String(error);
2960
- updatePendingApproval(approvalId, (request) => ({
2961
- ...request,
2962
- state: "pending",
2963
- pendingDecision: null,
2964
- inputPreview: String(request.inputPreview || "") + " (submit failed: " + errMsg + ")",
2965
- }));
3467
+ const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
3468
+ if (isStale) {
3469
+ updatePendingApproval(approvalId, () => null);
3470
+ } else {
3471
+ const errMsg = error instanceof Error ? error.message : String(error);
3472
+ updatePendingApproval(approvalId, (request) => ({
3473
+ ...request,
3474
+ state: "pending",
3475
+ pendingDecision: null,
3476
+ _error: errMsg,
3477
+ }));
3478
+ }
2966
3479
  renderMessages(state.activeMessages, state.isStreaming);
2967
3480
  } finally {
2968
3481
  if (!wasStreaming) {
@@ -3009,6 +3522,9 @@ var renderWebUiHtml = (options) => {
3009
3522
  } else {
3010
3523
  state.activeConversationId = null;
3011
3524
  state.activeMessages = [];
3525
+ state.contextTokens = 0;
3526
+ state.contextWindow = 0;
3527
+ updateContextRing();
3012
3528
  elements.chatTitle.textContent = "";
3013
3529
  renderMessages([]);
3014
3530
  renderConversationList();
@@ -3050,6 +3566,7 @@ var renderWebUiHtml = (options) => {
3050
3566
  await createConversation();
3051
3567
  }
3052
3568
  autoResizePrompt();
3569
+ updateContextRing();
3053
3570
  elements.prompt.focus();
3054
3571
  })();
3055
3572
 
@@ -3266,42 +3783,667 @@ var renderWebUiHtml = (options) => {
3266
3783
  </html>`;
3267
3784
  };
3268
3785
 
3269
- // src/index.ts
3270
- import { createInterface } from "readline/promises";
3271
-
3272
- // src/init-onboarding.ts
3273
- import { stdin, stdout } from "process";
3274
- import { input, password, select } from "@inquirer/prompts";
3275
- import {
3276
- fieldsForScope
3277
- } from "@poncho-ai/sdk";
3278
- var C = {
3279
- reset: "\x1B[0m",
3280
- bold: "\x1B[1m",
3281
- dim: "\x1B[2m",
3282
- cyan: "\x1B[36m"
3283
- };
3284
- var dim = (s) => `${C.dim}${s}${C.reset}`;
3285
- var bold = (s) => `${C.bold}${s}${C.reset}`;
3286
- var INPUT_CARET = "\xBB";
3287
- var shouldAskField = (field, answers) => {
3288
- if (!field.dependsOn) {
3289
- return true;
3290
- }
3291
- const value = answers[field.dependsOn.fieldId];
3292
- if (typeof field.dependsOn.equals !== "undefined") {
3293
- return value === field.dependsOn.equals;
3294
- }
3295
- if (field.dependsOn.oneOf) {
3296
- return field.dependsOn.oneOf.includes(value);
3297
- }
3298
- return true;
3299
- };
3300
- var parsePromptValue = (field, answer) => {
3301
- if (field.kind === "boolean") {
3302
- const normalized = answer.trim().toLowerCase();
3303
- if (normalized === "y" || normalized === "yes" || normalized === "true") {
3304
- return true;
3786
+ // src/api-docs.ts
3787
+ var buildOpenApiSpec = (options) => ({
3788
+ openapi: "3.1.0",
3789
+ info: {
3790
+ title: `${options.agentName} API`,
3791
+ 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.",
3792
+ version: "1.0.0"
3793
+ },
3794
+ servers: [{ url: "/", description: "Current host" }],
3795
+ components: {
3796
+ securitySchemes: {
3797
+ bearerAuth: {
3798
+ type: "http",
3799
+ scheme: "bearer",
3800
+ description: "Pass the PONCHO_AUTH_TOKEN value as a Bearer token. Required only when `auth.required: true` in poncho.config.js."
3801
+ }
3802
+ },
3803
+ schemas: {
3804
+ ConversationSummary: {
3805
+ type: "object",
3806
+ properties: {
3807
+ conversationId: { type: "string" },
3808
+ title: { type: "string" },
3809
+ runtimeRunId: { type: "string" },
3810
+ ownerId: { type: "string" },
3811
+ tenantId: { type: ["string", "null"] },
3812
+ createdAt: { type: "number", description: "Unix epoch ms" },
3813
+ updatedAt: { type: "number", description: "Unix epoch ms" },
3814
+ messageCount: { type: "integer" }
3815
+ }
3816
+ },
3817
+ Message: {
3818
+ type: "object",
3819
+ properties: {
3820
+ role: { type: "string", enum: ["user", "assistant"] },
3821
+ content: {
3822
+ oneOf: [
3823
+ { type: "string" },
3824
+ {
3825
+ type: "array",
3826
+ items: {
3827
+ oneOf: [
3828
+ {
3829
+ type: "object",
3830
+ properties: {
3831
+ type: { type: "string", const: "text" },
3832
+ text: { type: "string" }
3833
+ }
3834
+ },
3835
+ {
3836
+ type: "object",
3837
+ properties: {
3838
+ type: { type: "string", const: "file" },
3839
+ data: { type: "string" },
3840
+ mediaType: { type: "string" },
3841
+ filename: { type: "string" }
3842
+ }
3843
+ }
3844
+ ]
3845
+ }
3846
+ }
3847
+ ]
3848
+ },
3849
+ metadata: {
3850
+ type: "object",
3851
+ properties: {
3852
+ id: { type: "string" },
3853
+ timestamp: { type: "number" },
3854
+ tokenCount: { type: "number" },
3855
+ step: { type: "number" },
3856
+ toolActivity: { type: "array", items: { type: "string" } }
3857
+ }
3858
+ }
3859
+ }
3860
+ },
3861
+ Conversation: {
3862
+ type: "object",
3863
+ properties: {
3864
+ conversationId: { type: "string" },
3865
+ title: { type: "string" },
3866
+ ownerId: { type: "string" },
3867
+ tenantId: { type: ["string", "null"] },
3868
+ createdAt: { type: "number" },
3869
+ updatedAt: { type: "number" },
3870
+ messages: { type: "array", items: { $ref: "#/components/schemas/Message" } },
3871
+ pendingApprovals: {
3872
+ type: "array",
3873
+ items: { $ref: "#/components/schemas/PendingApproval" }
3874
+ }
3875
+ }
3876
+ },
3877
+ PendingApproval: {
3878
+ type: "object",
3879
+ properties: {
3880
+ approvalId: { type: "string" },
3881
+ runId: { type: "string" },
3882
+ tool: { type: "string" },
3883
+ input: {}
3884
+ }
3885
+ },
3886
+ TokenUsage: {
3887
+ type: "object",
3888
+ properties: {
3889
+ input: { type: "integer" },
3890
+ output: { type: "integer" },
3891
+ cached: { type: "integer" }
3892
+ }
3893
+ },
3894
+ RunResult: {
3895
+ type: "object",
3896
+ properties: {
3897
+ status: { type: "string", enum: ["completed", "error", "cancelled"] },
3898
+ response: { type: "string" },
3899
+ steps: { type: "integer" },
3900
+ tokens: { $ref: "#/components/schemas/TokenUsage" },
3901
+ duration: { type: "number", description: "Duration in ms" },
3902
+ continuation: { type: "boolean" },
3903
+ maxSteps: { type: "integer" }
3904
+ }
3905
+ },
3906
+ FileAttachment: {
3907
+ type: "object",
3908
+ properties: {
3909
+ data: { type: "string", description: "base64-encoded file data" },
3910
+ mediaType: { type: "string" },
3911
+ filename: { type: "string" }
3912
+ },
3913
+ required: ["data", "mediaType"]
3914
+ },
3915
+ Error: {
3916
+ type: "object",
3917
+ properties: {
3918
+ code: { type: "string" },
3919
+ message: { type: "string" }
3920
+ }
3921
+ }
3922
+ }
3923
+ },
3924
+ security: [{ bearerAuth: [] }],
3925
+ paths: {
3926
+ "/health": {
3927
+ get: {
3928
+ tags: ["Health"],
3929
+ summary: "Health check",
3930
+ security: [],
3931
+ responses: {
3932
+ "200": {
3933
+ description: "Server is healthy",
3934
+ content: {
3935
+ "application/json": {
3936
+ schema: {
3937
+ type: "object",
3938
+ properties: { status: { type: "string", const: "ok" } }
3939
+ }
3940
+ }
3941
+ }
3942
+ }
3943
+ }
3944
+ }
3945
+ },
3946
+ "/api/auth/session": {
3947
+ get: {
3948
+ tags: ["Auth"],
3949
+ summary: "Check session status",
3950
+ description: "Returns whether the caller is authenticated and provides a CSRF token for subsequent mutating requests.",
3951
+ security: [],
3952
+ responses: {
3953
+ "200": {
3954
+ description: "Session status",
3955
+ content: {
3956
+ "application/json": {
3957
+ schema: {
3958
+ type: "object",
3959
+ properties: {
3960
+ authenticated: { type: "boolean" },
3961
+ sessionId: { type: "string" },
3962
+ ownerId: { type: "string" },
3963
+ csrfToken: { type: "string" }
3964
+ },
3965
+ required: ["authenticated"]
3966
+ }
3967
+ }
3968
+ }
3969
+ }
3970
+ }
3971
+ }
3972
+ },
3973
+ "/api/auth/login": {
3974
+ post: {
3975
+ tags: ["Auth"],
3976
+ summary: "Authenticate with passphrase",
3977
+ description: "Creates a session cookie. Only needed for browser-based auth; API clients should use Bearer tokens instead.",
3978
+ security: [],
3979
+ requestBody: {
3980
+ required: true,
3981
+ content: {
3982
+ "application/json": {
3983
+ schema: {
3984
+ type: "object",
3985
+ properties: { passphrase: { type: "string" } },
3986
+ required: ["passphrase"]
3987
+ }
3988
+ }
3989
+ }
3990
+ },
3991
+ responses: {
3992
+ "200": {
3993
+ description: "Login successful",
3994
+ content: {
3995
+ "application/json": {
3996
+ schema: {
3997
+ type: "object",
3998
+ properties: {
3999
+ ok: { type: "boolean" },
4000
+ sessionId: { type: "string" },
4001
+ csrfToken: { type: "string" }
4002
+ }
4003
+ }
4004
+ }
4005
+ }
4006
+ },
4007
+ "401": {
4008
+ description: "Invalid passphrase",
4009
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4010
+ },
4011
+ "429": {
4012
+ description: "Too many login attempts",
4013
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4014
+ }
4015
+ }
4016
+ }
4017
+ },
4018
+ "/api/auth/logout": {
4019
+ post: {
4020
+ tags: ["Auth"],
4021
+ summary: "End session",
4022
+ responses: {
4023
+ "200": {
4024
+ description: "Logged out",
4025
+ content: {
4026
+ "application/json": {
4027
+ schema: { type: "object", properties: { ok: { type: "boolean" } } }
4028
+ }
4029
+ }
4030
+ }
4031
+ }
4032
+ }
4033
+ },
4034
+ "/api/conversations": {
4035
+ get: {
4036
+ tags: ["Conversations"],
4037
+ summary: "List conversations",
4038
+ responses: {
4039
+ "200": {
4040
+ description: "Conversation list",
4041
+ content: {
4042
+ "application/json": {
4043
+ schema: {
4044
+ type: "object",
4045
+ properties: {
4046
+ conversations: {
4047
+ type: "array",
4048
+ items: { $ref: "#/components/schemas/ConversationSummary" }
4049
+ }
4050
+ }
4051
+ }
4052
+ }
4053
+ }
4054
+ }
4055
+ }
4056
+ },
4057
+ post: {
4058
+ tags: ["Conversations"],
4059
+ summary: "Create a conversation",
4060
+ requestBody: {
4061
+ content: {
4062
+ "application/json": {
4063
+ schema: {
4064
+ type: "object",
4065
+ properties: { title: { type: "string" } }
4066
+ }
4067
+ }
4068
+ }
4069
+ },
4070
+ responses: {
4071
+ "201": {
4072
+ description: "Conversation created",
4073
+ content: {
4074
+ "application/json": {
4075
+ schema: {
4076
+ type: "object",
4077
+ properties: { conversation: { $ref: "#/components/schemas/Conversation" } }
4078
+ }
4079
+ }
4080
+ }
4081
+ }
4082
+ }
4083
+ }
4084
+ },
4085
+ "/api/conversations/{conversationId}": {
4086
+ get: {
4087
+ tags: ["Conversations"],
4088
+ summary: "Get conversation",
4089
+ description: "Returns the full conversation including messages and any pending tool approval requests.",
4090
+ parameters: [
4091
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } }
4092
+ ],
4093
+ responses: {
4094
+ "200": {
4095
+ description: "Conversation with messages",
4096
+ content: {
4097
+ "application/json": {
4098
+ schema: {
4099
+ type: "object",
4100
+ properties: { conversation: { $ref: "#/components/schemas/Conversation" } }
4101
+ }
4102
+ }
4103
+ }
4104
+ },
4105
+ "404": {
4106
+ description: "Conversation not found",
4107
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4108
+ }
4109
+ }
4110
+ },
4111
+ patch: {
4112
+ tags: ["Conversations"],
4113
+ summary: "Rename conversation",
4114
+ parameters: [
4115
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } }
4116
+ ],
4117
+ requestBody: {
4118
+ required: true,
4119
+ content: {
4120
+ "application/json": {
4121
+ schema: {
4122
+ type: "object",
4123
+ properties: { title: { type: "string" } },
4124
+ required: ["title"]
4125
+ }
4126
+ }
4127
+ }
4128
+ },
4129
+ responses: {
4130
+ "200": {
4131
+ description: "Conversation renamed",
4132
+ content: {
4133
+ "application/json": {
4134
+ schema: {
4135
+ type: "object",
4136
+ properties: { conversation: { $ref: "#/components/schemas/Conversation" } }
4137
+ }
4138
+ }
4139
+ }
4140
+ }
4141
+ }
4142
+ },
4143
+ delete: {
4144
+ tags: ["Conversations"],
4145
+ summary: "Delete conversation",
4146
+ parameters: [
4147
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } }
4148
+ ],
4149
+ responses: {
4150
+ "200": {
4151
+ description: "Conversation deleted",
4152
+ content: {
4153
+ "application/json": {
4154
+ schema: { type: "object", properties: { ok: { type: "boolean" } } }
4155
+ }
4156
+ }
4157
+ }
4158
+ }
4159
+ }
4160
+ },
4161
+ "/api/conversations/{conversationId}/messages": {
4162
+ post: {
4163
+ tags: ["Messages"],
4164
+ summary: "Send a message (streaming)",
4165
+ 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.",
4166
+ parameters: [
4167
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } }
4168
+ ],
4169
+ requestBody: {
4170
+ required: true,
4171
+ content: {
4172
+ "application/json": {
4173
+ schema: {
4174
+ type: "object",
4175
+ properties: {
4176
+ message: { type: "string", description: "User message text" },
4177
+ parameters: {
4178
+ type: "object",
4179
+ additionalProperties: true,
4180
+ description: "Key-value parameters passed to the agent run"
4181
+ },
4182
+ files: {
4183
+ type: "array",
4184
+ items: { $ref: "#/components/schemas/FileAttachment" },
4185
+ description: "Attached files (base64-encoded)"
4186
+ }
4187
+ },
4188
+ required: ["message"]
4189
+ }
4190
+ },
4191
+ "multipart/form-data": {
4192
+ schema: {
4193
+ type: "object",
4194
+ properties: {
4195
+ message: { type: "string" },
4196
+ parameters: { type: "string", description: "JSON-encoded parameters object" },
4197
+ files: { type: "array", items: { type: "string", format: "binary" } }
4198
+ }
4199
+ }
4200
+ }
4201
+ }
4202
+ },
4203
+ responses: {
4204
+ "200": {
4205
+ description: "SSE stream of agent events",
4206
+ content: { "text/event-stream": { schema: { type: "string" } } }
4207
+ },
4208
+ "404": {
4209
+ description: "Conversation not found",
4210
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4211
+ }
4212
+ }
4213
+ }
4214
+ },
4215
+ "/api/conversations/{conversationId}/events": {
4216
+ get: {
4217
+ tags: ["Messages"],
4218
+ summary: "Attach to live event stream",
4219
+ 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.",
4220
+ parameters: [
4221
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } }
4222
+ ],
4223
+ responses: {
4224
+ "200": {
4225
+ description: "SSE stream (same event format as POST /messages)",
4226
+ content: { "text/event-stream": { schema: { type: "string" } } }
4227
+ },
4228
+ "404": {
4229
+ description: "Conversation not found",
4230
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4231
+ }
4232
+ }
4233
+ }
4234
+ },
4235
+ "/api/conversations/{conversationId}/stop": {
4236
+ post: {
4237
+ tags: ["Messages"],
4238
+ summary: "Stop an in-flight run",
4239
+ parameters: [
4240
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } }
4241
+ ],
4242
+ requestBody: {
4243
+ required: true,
4244
+ content: {
4245
+ "application/json": {
4246
+ schema: {
4247
+ type: "object",
4248
+ properties: {
4249
+ runId: { type: "string", description: "The run ID to cancel (from run:started event)" }
4250
+ },
4251
+ required: ["runId"]
4252
+ }
4253
+ }
4254
+ }
4255
+ },
4256
+ responses: {
4257
+ "200": {
4258
+ description: "Stop result",
4259
+ content: {
4260
+ "application/json": {
4261
+ schema: {
4262
+ type: "object",
4263
+ properties: {
4264
+ ok: { type: "boolean" },
4265
+ stopped: { type: "boolean" },
4266
+ runId: { type: "string" }
4267
+ }
4268
+ }
4269
+ }
4270
+ }
4271
+ }
4272
+ }
4273
+ }
4274
+ },
4275
+ "/api/approvals/{approvalId}": {
4276
+ post: {
4277
+ tags: ["Approvals"],
4278
+ summary: "Resolve a tool approval request",
4279
+ 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.",
4280
+ parameters: [
4281
+ { name: "approvalId", in: "path", required: true, schema: { type: "string" } }
4282
+ ],
4283
+ requestBody: {
4284
+ required: true,
4285
+ content: {
4286
+ "application/json": {
4287
+ schema: {
4288
+ type: "object",
4289
+ properties: { approved: { type: "boolean" } },
4290
+ required: ["approved"]
4291
+ }
4292
+ }
4293
+ }
4294
+ },
4295
+ responses: {
4296
+ "200": {
4297
+ description: "Approval resolved",
4298
+ content: {
4299
+ "application/json": {
4300
+ schema: {
4301
+ type: "object",
4302
+ properties: {
4303
+ ok: { type: "boolean" },
4304
+ approvalId: { type: "string" },
4305
+ approved: { type: "boolean" }
4306
+ }
4307
+ }
4308
+ }
4309
+ }
4310
+ },
4311
+ "404": {
4312
+ description: "Approval not found or expired",
4313
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4314
+ }
4315
+ }
4316
+ }
4317
+ },
4318
+ "/api/uploads/{key}": {
4319
+ get: {
4320
+ tags: ["Assets"],
4321
+ summary: "Retrieve an uploaded file",
4322
+ description: "Serves a file previously uploaded during a conversation. The key is returned in file content part references.",
4323
+ parameters: [
4324
+ {
4325
+ name: "key",
4326
+ in: "path",
4327
+ required: true,
4328
+ schema: { type: "string" },
4329
+ description: "Upload key (e.g. filename or storage path)"
4330
+ }
4331
+ ],
4332
+ responses: {
4333
+ "200": {
4334
+ description: "File content with appropriate Content-Type",
4335
+ content: { "application/octet-stream": { schema: { type: "string", format: "binary" } } }
4336
+ },
4337
+ "404": {
4338
+ description: "Upload not found",
4339
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4340
+ }
4341
+ }
4342
+ }
4343
+ },
4344
+ "/api/cron/{jobName}": {
4345
+ get: {
4346
+ tags: ["Cron"],
4347
+ summary: "Trigger a cron job",
4348
+ description: "Triggers a named cron job defined in AGENT.md frontmatter. Supports continuation via the `continue` query parameter.",
4349
+ parameters: [
4350
+ { name: "jobName", in: "path", required: true, schema: { type: "string" } },
4351
+ {
4352
+ name: "continue",
4353
+ in: "query",
4354
+ schema: { type: "string" },
4355
+ description: "Conversation ID to continue a previous cron run"
4356
+ }
4357
+ ],
4358
+ responses: {
4359
+ "200": {
4360
+ description: "Cron job result",
4361
+ content: {
4362
+ "application/json": {
4363
+ schema: {
4364
+ type: "object",
4365
+ properties: {
4366
+ conversationId: { type: "string" },
4367
+ response: { type: "string" },
4368
+ steps: { type: "integer" },
4369
+ status: { type: "string" },
4370
+ continuation: { type: "string", description: "URL to continue this run" }
4371
+ }
4372
+ }
4373
+ }
4374
+ }
4375
+ },
4376
+ "404": {
4377
+ description: "Cron job not found",
4378
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
4379
+ }
4380
+ }
4381
+ }
4382
+ }
4383
+ },
4384
+ tags: [
4385
+ { name: "Health", description: "Server health check" },
4386
+ { name: "Auth", description: "Session and authentication management" },
4387
+ { name: "Conversations", description: "Create, list, read, rename, and delete conversations" },
4388
+ {
4389
+ name: "Messages",
4390
+ description: "Send messages and stream agent responses via SSE"
4391
+ },
4392
+ { name: "Approvals", description: "Resolve gated tool approval requests" },
4393
+ { name: "Assets", description: "Retrieve uploaded files" },
4394
+ { name: "Cron", description: "Trigger cron jobs defined in AGENT.md" }
4395
+ ]
4396
+ });
4397
+ var renderApiDocsHtml = (specUrl) => `<!DOCTYPE html>
4398
+ <html lang="en">
4399
+ <head>
4400
+ <meta charset="utf-8" />
4401
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
4402
+ <title>API Documentation</title>
4403
+ <style>body { margin: 0; }</style>
4404
+ </head>
4405
+ <body>
4406
+ <script id="api-reference" data-url="${specUrl}"></script>
4407
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
4408
+ </body>
4409
+ </html>`;
4410
+
4411
+ // src/index.ts
4412
+ import { createInterface } from "readline/promises";
4413
+
4414
+ // src/init-onboarding.ts
4415
+ import { stdin, stdout } from "process";
4416
+ import { input, password, select } from "@inquirer/prompts";
4417
+ import {
4418
+ fieldsForScope
4419
+ } from "@poncho-ai/sdk";
4420
+ var C = {
4421
+ reset: "\x1B[0m",
4422
+ bold: "\x1B[1m",
4423
+ dim: "\x1B[2m",
4424
+ cyan: "\x1B[36m"
4425
+ };
4426
+ var dim = (s) => `${C.dim}${s}${C.reset}`;
4427
+ var bold = (s) => `${C.bold}${s}${C.reset}`;
4428
+ var INPUT_CARET = "\xBB";
4429
+ var shouldAskField = (field, answers) => {
4430
+ if (!field.dependsOn) {
4431
+ return true;
4432
+ }
4433
+ const value = answers[field.dependsOn.fieldId];
4434
+ if (typeof field.dependsOn.equals !== "undefined") {
4435
+ return value === field.dependsOn.equals;
4436
+ }
4437
+ if (field.dependsOn.oneOf) {
4438
+ return field.dependsOn.oneOf.includes(value);
4439
+ }
4440
+ return true;
4441
+ };
4442
+ var parsePromptValue = (field, answer) => {
4443
+ if (field.kind === "boolean") {
4444
+ const normalized = answer.trim().toLowerCase();
4445
+ if (normalized === "y" || normalized === "yes" || normalized === "true") {
4446
+ return true;
3305
4447
  }
3306
4448
  if (normalized === "n" || normalized === "no" || normalized === "false") {
3307
4449
  return false;
@@ -3521,7 +4663,20 @@ var buildConfigFromOnboardingAnswers = (answers) => {
3521
4663
  telemetry
3522
4664
  };
3523
4665
  if (messagingPlatform !== "none") {
3524
- config.messaging = [{ platform: messagingPlatform }];
4666
+ const channelConfig = {
4667
+ platform: messagingPlatform
4668
+ };
4669
+ if (messagingPlatform === "resend") {
4670
+ const mode = String(answers["messaging.resend.mode"] ?? "auto-reply");
4671
+ if (mode === "tool") {
4672
+ channelConfig.mode = "tool";
4673
+ }
4674
+ const recipientsRaw = String(answers["messaging.resend.allowedRecipients"] ?? "");
4675
+ if (recipientsRaw.trim().length > 0) {
4676
+ channelConfig.allowedRecipients = recipientsRaw.split(",").map((s) => s.trim()).filter(Boolean);
4677
+ }
4678
+ }
4679
+ config.messaging = [channelConfig];
3525
4680
  }
3526
4681
  return config;
3527
4682
  };
@@ -3993,7 +5148,7 @@ cp .env.example .env
3993
5148
  poncho dev
3994
5149
  \`\`\`
3995
5150
 
3996
- Open \`http://localhost:3000\` for the web UI.
5151
+ Open \`http://localhost:3000\` for the web UI, or \`http://localhost:3000/api/docs\` for interactive API documentation.
3997
5152
 
3998
5153
  On your first interactive session, the agent introduces its configurable capabilities.
3999
5154
  While a response is streaming, you can stop it:
@@ -4115,7 +5270,7 @@ Core files:
4115
5270
 
4116
5271
  - \`AGENT.md\`: behavior, model selection, runtime guidance
4117
5272
  - \`poncho.config.js\`: runtime config (storage, auth, telemetry, MCP, tools)
4118
- - \`.env\`: secrets and environment variables
5273
+ - \`.env\`: secrets and environment variables (loaded before the harness starts, so \`process.env\` is available in skill scripts)
4119
5274
 
4120
5275
  Example \`poncho.config.js\`:
4121
5276
 
@@ -4141,18 +5296,20 @@ export default {
4141
5296
  auth: { type: "bearer", tokenEnv: "GITHUB_TOKEN" },
4142
5297
  },
4143
5298
  ],
5299
+ // Tool access: true (available), false (disabled), 'approval' (requires human approval)
4144
5300
  tools: {
4145
- defaults: {
4146
- list_directory: true,
4147
- read_file: true,
4148
- write_file: true, // still gated by environment/policy
4149
- },
5301
+ write_file: true, // gated by environment for writes
5302
+ send_email: 'approval', // requires human approval
4150
5303
  byEnvironment: {
4151
5304
  production: {
4152
- read_file: false, // example override
5305
+ write_file: false,
5306
+ },
5307
+ development: {
5308
+ send_email: true, // skip approval in dev
4153
5309
  },
4154
5310
  },
4155
5311
  },
5312
+ // webUi: false, // Disable built-in UI for API-only deployments
4156
5313
  };
4157
5314
  \`\`\`
4158
5315
 
@@ -4211,6 +5368,26 @@ Connect your agent to Slack so it responds to @mentions:
4211
5368
  messaging: [{ platform: 'slack' }]
4212
5369
  \`\`\`
4213
5370
 
5371
+ ## Messaging (Email via Resend)
5372
+
5373
+ Connect your agent to email so users can interact by sending emails:
5374
+
5375
+ 1. Set up a domain and enable Inbound at [resend.com](https://resend.com)
5376
+ 2. Create a webhook for \`email.received\` pointing to \`https://<your-url>/api/messaging/resend\`
5377
+ 3. Install the Resend SDK: \`npm install resend\`
5378
+ 4. Set env vars:
5379
+ \`\`\`
5380
+ RESEND_API_KEY=re_...
5381
+ RESEND_WEBHOOK_SECRET=whsec_...
5382
+ RESEND_FROM=Agent <agent@yourdomain.com>
5383
+ \`\`\`
5384
+ 5. Add to \`poncho.config.js\`:
5385
+ \`\`\`javascript
5386
+ messaging: [{ platform: 'resend' }]
5387
+ \`\`\`
5388
+
5389
+ 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.
5390
+
4214
5391
  ## Deployment
4215
5392
 
4216
5393
  \`\`\`bash
@@ -4828,7 +6005,6 @@ var createRequestHandler = async (options) => {
4828
6005
  const runOwners = /* @__PURE__ */ new Map();
4829
6006
  const runConversations = /* @__PURE__ */ new Map();
4830
6007
  const activeConversationRuns = /* @__PURE__ */ new Map();
4831
- const pendingApprovals = /* @__PURE__ */ new Map();
4832
6008
  const conversationEventStreams = /* @__PURE__ */ new Map();
4833
6009
  const broadcastEvent = (conversationId, event) => {
4834
6010
  let stream = conversationEventStreams.get(conversationId);
@@ -4860,51 +6036,19 @@ var createRequestHandler = async (options) => {
4860
6036
  setTimeout(() => conversationEventStreams.delete(conversationId), 3e4);
4861
6037
  }
4862
6038
  };
4863
- const persistConversationPendingApprovals = async (conversationId) => {
4864
- const conversation = await conversationStore.get(conversationId);
4865
- if (!conversation) {
4866
- return;
4867
- }
4868
- conversation.pendingApprovals = Array.from(pendingApprovals.entries()).filter(
4869
- ([, pending]) => pending.ownerId === conversation.ownerId && pending.conversationId === conversationId
4870
- ).map(([approvalId, pending]) => ({
4871
- approvalId,
4872
- runId: pending.runId,
4873
- tool: pending.tool,
4874
- input: pending.input
4875
- }));
4876
- await conversationStore.update(conversation);
4877
- };
4878
6039
  const clearPendingApprovalsForConversation = async (conversationId) => {
4879
- for (const [approvalId, pending] of pendingApprovals.entries()) {
4880
- if (pending.conversationId !== conversationId) {
4881
- continue;
4882
- }
4883
- pendingApprovals.delete(approvalId);
4884
- pending.resolve(false);
6040
+ const conversation = await conversationStore.get(conversationId);
6041
+ if (!conversation) return;
6042
+ if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) {
6043
+ conversation.pendingApprovals = [];
6044
+ await conversationStore.update(conversation);
4885
6045
  }
4886
- await persistConversationPendingApprovals(conversationId);
4887
6046
  };
4888
6047
  const uploadStore = await createUploadStore(config?.uploads, workingDir);
4889
6048
  const harness = new AgentHarness({
4890
6049
  workingDir,
4891
6050
  environment: resolveHarnessEnvironment(),
4892
- uploadStore,
4893
- approvalHandler: async (request) => new Promise((resolveApproval) => {
4894
- const ownerIdForRun = runOwners.get(request.runId) ?? "local-owner";
4895
- const conversationIdForRun = runConversations.get(request.runId) ?? null;
4896
- pendingApprovals.set(request.approvalId, {
4897
- ownerId: ownerIdForRun,
4898
- runId: request.runId,
4899
- conversationId: conversationIdForRun,
4900
- tool: request.tool,
4901
- input: request.input,
4902
- resolve: resolveApproval
4903
- });
4904
- if (conversationIdForRun) {
4905
- void persistConversationPendingApprovals(conversationIdForRun);
4906
- }
4907
- })
6051
+ uploadStore
4908
6052
  });
4909
6053
  await harness.initialize();
4910
6054
  const telemetry = new TelemetryEmitter(config?.telemetry);
@@ -4913,6 +6057,162 @@ var createRequestHandler = async (options) => {
4913
6057
  workingDir,
4914
6058
  agentId: identity.id
4915
6059
  });
6060
+ const resumeRunFromCheckpoint = async (conversationId, conversation, checkpoint, toolResults) => {
6061
+ const abortController = new AbortController();
6062
+ activeConversationRuns.set(conversationId, {
6063
+ ownerId: conversation.ownerId,
6064
+ abortController,
6065
+ runId: null
6066
+ });
6067
+ let latestRunId = conversation.runtimeRunId ?? "";
6068
+ let assistantResponse = "";
6069
+ const toolTimeline = [];
6070
+ const sections = [];
6071
+ let currentText = "";
6072
+ let currentTools = [];
6073
+ let checkpointedRun = false;
6074
+ const baseMessages = checkpoint.baseMessageCount != null ? conversation.messages.slice(0, checkpoint.baseMessageCount) : [];
6075
+ const fullCheckpointMessages = [...baseMessages, ...checkpoint.checkpointMessages];
6076
+ try {
6077
+ for await (const event of harness.continueFromToolResult({
6078
+ messages: fullCheckpointMessages,
6079
+ toolResults,
6080
+ conversationId,
6081
+ abortSignal: abortController.signal
6082
+ })) {
6083
+ if (event.type === "run:started") {
6084
+ latestRunId = event.runId;
6085
+ runOwners.set(event.runId, conversation.ownerId);
6086
+ runConversations.set(event.runId, conversationId);
6087
+ const active = activeConversationRuns.get(conversationId);
6088
+ if (active && active.abortController === abortController) {
6089
+ active.runId = event.runId;
6090
+ }
6091
+ }
6092
+ if (event.type === "model:chunk") {
6093
+ if (currentTools.length > 0) {
6094
+ sections.push({ type: "tools", content: currentTools });
6095
+ currentTools = [];
6096
+ }
6097
+ assistantResponse += event.content;
6098
+ currentText += event.content;
6099
+ }
6100
+ if (event.type === "tool:started") {
6101
+ if (currentText.length > 0) {
6102
+ sections.push({ type: "text", content: currentText });
6103
+ currentText = "";
6104
+ }
6105
+ const toolText = `- start \`${event.tool}\``;
6106
+ toolTimeline.push(toolText);
6107
+ currentTools.push(toolText);
6108
+ }
6109
+ if (event.type === "tool:completed") {
6110
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
6111
+ toolTimeline.push(toolText);
6112
+ currentTools.push(toolText);
6113
+ }
6114
+ if (event.type === "tool:error") {
6115
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
6116
+ toolTimeline.push(toolText);
6117
+ currentTools.push(toolText);
6118
+ }
6119
+ if (event.type === "tool:approval:required") {
6120
+ const toolText = `- approval required \`${event.tool}\``;
6121
+ toolTimeline.push(toolText);
6122
+ currentTools.push(toolText);
6123
+ }
6124
+ if (event.type === "tool:approval:checkpoint") {
6125
+ const conv = await conversationStore.get(conversationId);
6126
+ if (conv) {
6127
+ conv.pendingApprovals = [{
6128
+ approvalId: event.approvalId,
6129
+ runId: latestRunId,
6130
+ tool: event.tool,
6131
+ toolCallId: event.toolCallId,
6132
+ input: event.input,
6133
+ checkpointMessages: [...fullCheckpointMessages, ...event.checkpointMessages],
6134
+ baseMessageCount: 0,
6135
+ pendingToolCalls: event.pendingToolCalls
6136
+ }];
6137
+ conv.updatedAt = Date.now();
6138
+ await conversationStore.update(conv);
6139
+ }
6140
+ checkpointedRun = true;
6141
+ }
6142
+ if (event.type === "run:completed" && assistantResponse.length === 0 && event.result.response) {
6143
+ assistantResponse = event.result.response;
6144
+ }
6145
+ if (event.type === "run:error") {
6146
+ assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
6147
+ }
6148
+ await telemetry.emit(event);
6149
+ broadcastEvent(conversationId, event);
6150
+ }
6151
+ } catch (err) {
6152
+ console.error("[resume-run] error:", err instanceof Error ? err.message : err);
6153
+ assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
6154
+ }
6155
+ if (currentTools.length > 0) {
6156
+ sections.push({ type: "tools", content: currentTools });
6157
+ }
6158
+ if (currentText.length > 0) {
6159
+ sections.push({ type: "text", content: currentText });
6160
+ }
6161
+ if (!checkpointedRun) {
6162
+ const conv = await conversationStore.get(conversationId);
6163
+ if (conv) {
6164
+ const prevMessages = conv.messages;
6165
+ const hasAssistantContent = assistantResponse.length > 0 || toolTimeline.length > 0 || sections.length > 0;
6166
+ if (hasAssistantContent) {
6167
+ const lastMsg = prevMessages[prevMessages.length - 1];
6168
+ if (lastMsg && lastMsg.role === "assistant" && lastMsg.metadata) {
6169
+ const existingToolActivity = lastMsg.metadata.toolActivity;
6170
+ const existingSections = lastMsg.metadata.sections;
6171
+ const mergedTimeline = [
6172
+ ...Array.isArray(existingToolActivity) ? existingToolActivity : [],
6173
+ ...toolTimeline
6174
+ ];
6175
+ const mergedSections = [
6176
+ ...Array.isArray(existingSections) ? existingSections : [],
6177
+ ...sections
6178
+ ];
6179
+ const mergedText = (typeof lastMsg.content === "string" ? lastMsg.content : "") + assistantResponse;
6180
+ conv.messages = [
6181
+ ...prevMessages.slice(0, -1),
6182
+ {
6183
+ role: "assistant",
6184
+ content: mergedText,
6185
+ metadata: {
6186
+ toolActivity: mergedTimeline,
6187
+ sections: mergedSections.length > 0 ? mergedSections : void 0
6188
+ }
6189
+ }
6190
+ ];
6191
+ } else {
6192
+ conv.messages = [
6193
+ ...prevMessages,
6194
+ {
6195
+ role: "assistant",
6196
+ content: assistantResponse,
6197
+ metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
6198
+ }
6199
+ ];
6200
+ }
6201
+ }
6202
+ conv.runtimeRunId = latestRunId || conv.runtimeRunId;
6203
+ conv.pendingApprovals = [];
6204
+ conv.updatedAt = Date.now();
6205
+ await conversationStore.update(conv);
6206
+ }
6207
+ }
6208
+ finishConversationStream(conversationId);
6209
+ activeConversationRuns.delete(conversationId);
6210
+ if (latestRunId) {
6211
+ runOwners.delete(latestRunId);
6212
+ runConversations.delete(latestRunId);
6213
+ }
6214
+ console.log("[resume-run] complete for", conversationId);
6215
+ };
4916
6216
  const messagingRoutes = /* @__PURE__ */ new Map();
4917
6217
  const messagingRouteRegistrar = (method, path, routeHandler) => {
4918
6218
  let byMethod = messagingRoutes.get(path);
@@ -4942,20 +6242,167 @@ var createRequestHandler = async (options) => {
4942
6242
  return { messages: [] };
4943
6243
  },
4944
6244
  async run(conversationId, input2) {
4945
- const output = await harness.runToCompletion({
4946
- task: input2.task,
4947
- messages: input2.messages
6245
+ console.log("[messaging-runner] starting run for", conversationId, "task:", input2.task.slice(0, 80));
6246
+ const historyMessages = [...input2.messages];
6247
+ const userContent = input2.task;
6248
+ const updateConversation = async (patch) => {
6249
+ const fresh = await conversationStore.get(conversationId);
6250
+ if (!fresh) return;
6251
+ patch(fresh);
6252
+ fresh.updatedAt = Date.now();
6253
+ await conversationStore.update(fresh);
6254
+ };
6255
+ await updateConversation((c) => {
6256
+ c.messages = [...historyMessages, { role: "user", content: userContent }];
4948
6257
  });
4949
- const response = output.result.response ?? "";
4950
- const conversation = await conversationStore.get(conversationId);
4951
- if (conversation) {
4952
- conversation.messages = [
4953
- ...input2.messages,
4954
- { role: "user", content: input2.task },
4955
- { role: "assistant", content: response }
6258
+ let latestRunId = "";
6259
+ let assistantResponse = "";
6260
+ const toolTimeline = [];
6261
+ const sections = [];
6262
+ let currentTools = [];
6263
+ let currentText = "";
6264
+ let checkpointedRun = false;
6265
+ const buildMessages = () => {
6266
+ const draftSections = [
6267
+ ...sections.map((s) => ({
6268
+ type: s.type,
6269
+ content: Array.isArray(s.content) ? [...s.content] : s.content
6270
+ }))
4956
6271
  ];
4957
- await conversationStore.update(conversation);
6272
+ if (currentTools.length > 0) {
6273
+ draftSections.push({ type: "tools", content: [...currentTools] });
6274
+ }
6275
+ if (currentText.length > 0) {
6276
+ draftSections.push({ type: "text", content: currentText });
6277
+ }
6278
+ const hasDraftContent = assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
6279
+ if (!hasDraftContent) {
6280
+ return [...historyMessages, { role: "user", content: userContent }];
6281
+ }
6282
+ return [
6283
+ ...historyMessages,
6284
+ { role: "user", content: userContent },
6285
+ {
6286
+ role: "assistant",
6287
+ content: assistantResponse,
6288
+ metadata: toolTimeline.length > 0 || draftSections.length > 0 ? {
6289
+ toolActivity: [...toolTimeline],
6290
+ sections: draftSections.length > 0 ? draftSections : void 0
6291
+ } : void 0
6292
+ }
6293
+ ];
6294
+ };
6295
+ const persistDraftAssistantTurn = async () => {
6296
+ if (assistantResponse.length === 0 && toolTimeline.length === 0) return;
6297
+ await updateConversation((c) => {
6298
+ c.messages = buildMessages();
6299
+ });
6300
+ };
6301
+ const runInput = {
6302
+ task: input2.task,
6303
+ conversationId,
6304
+ messages: input2.messages,
6305
+ files: input2.files,
6306
+ parameters: input2.metadata ? {
6307
+ __messaging_platform: input2.metadata.platform,
6308
+ __messaging_sender_id: input2.metadata.sender.id,
6309
+ __messaging_sender_name: input2.metadata.sender.name ?? "",
6310
+ __messaging_thread_id: input2.metadata.threadId
6311
+ } : void 0
6312
+ };
6313
+ try {
6314
+ for await (const event of harness.runWithTelemetry(runInput)) {
6315
+ if (event.type === "run:started") {
6316
+ latestRunId = event.runId;
6317
+ runOwners.set(event.runId, "local-owner");
6318
+ runConversations.set(event.runId, conversationId);
6319
+ }
6320
+ if (event.type === "model:chunk") {
6321
+ if (currentTools.length > 0) {
6322
+ sections.push({ type: "tools", content: currentTools });
6323
+ currentTools = [];
6324
+ }
6325
+ assistantResponse += event.content;
6326
+ currentText += event.content;
6327
+ }
6328
+ if (event.type === "tool:started") {
6329
+ if (currentText.length > 0) {
6330
+ sections.push({ type: "text", content: currentText });
6331
+ currentText = "";
6332
+ }
6333
+ const toolText = `- start \`${event.tool}\``;
6334
+ toolTimeline.push(toolText);
6335
+ currentTools.push(toolText);
6336
+ }
6337
+ if (event.type === "tool:completed") {
6338
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
6339
+ toolTimeline.push(toolText);
6340
+ currentTools.push(toolText);
6341
+ }
6342
+ if (event.type === "tool:error") {
6343
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
6344
+ toolTimeline.push(toolText);
6345
+ currentTools.push(toolText);
6346
+ }
6347
+ if (event.type === "step:completed") {
6348
+ await persistDraftAssistantTurn();
6349
+ }
6350
+ if (event.type === "tool:approval:required") {
6351
+ const toolText = `- approval required \`${event.tool}\``;
6352
+ toolTimeline.push(toolText);
6353
+ currentTools.push(toolText);
6354
+ await persistDraftAssistantTurn();
6355
+ }
6356
+ if (event.type === "tool:approval:checkpoint") {
6357
+ await updateConversation((c) => {
6358
+ c.messages = buildMessages();
6359
+ c.pendingApprovals = [{
6360
+ approvalId: event.approvalId,
6361
+ runId: latestRunId,
6362
+ tool: event.tool,
6363
+ toolCallId: event.toolCallId,
6364
+ input: event.input,
6365
+ checkpointMessages: event.checkpointMessages,
6366
+ baseMessageCount: historyMessages.length,
6367
+ pendingToolCalls: event.pendingToolCalls
6368
+ }];
6369
+ });
6370
+ checkpointedRun = true;
6371
+ }
6372
+ if (event.type === "run:completed" && assistantResponse.length === 0 && event.result.response) {
6373
+ assistantResponse = event.result.response;
6374
+ }
6375
+ if (event.type === "run:error") {
6376
+ assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
6377
+ }
6378
+ broadcastEvent(conversationId, event);
6379
+ }
6380
+ } catch (err) {
6381
+ console.error("[messaging-runner] run failed:", err instanceof Error ? err.message : err);
6382
+ assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
6383
+ }
6384
+ if (currentTools.length > 0) {
6385
+ sections.push({ type: "tools", content: currentTools });
6386
+ currentTools = [];
6387
+ }
6388
+ if (currentText.length > 0) {
6389
+ sections.push({ type: "text", content: currentText });
6390
+ currentText = "";
4958
6391
  }
6392
+ if (!checkpointedRun) {
6393
+ await updateConversation((c) => {
6394
+ c.messages = buildMessages();
6395
+ c.runtimeRunId = latestRunId || c.runtimeRunId;
6396
+ c.pendingApprovals = [];
6397
+ });
6398
+ }
6399
+ finishConversationStream(conversationId);
6400
+ if (latestRunId) {
6401
+ runOwners.delete(latestRunId);
6402
+ runConversations.delete(latestRunId);
6403
+ }
6404
+ console.log("[messaging-runner] run complete, response length:", assistantResponse.length);
6405
+ const response = assistantResponse;
4959
6406
  return { response };
4960
6407
  }
4961
6408
  };
@@ -4982,7 +6429,8 @@ var createRequestHandler = async (options) => {
4982
6429
  const bridge = new AgentBridge({
4983
6430
  adapter,
4984
6431
  runner: messagingRunner,
4985
- waitUntil: waitUntilHook
6432
+ waitUntil: waitUntilHook,
6433
+ ownerId: "local-owner"
4986
6434
  });
4987
6435
  adapter.registerRoutes(messagingRouteRegistrar);
4988
6436
  try {
@@ -4994,6 +6442,37 @@ var createRequestHandler = async (options) => {
4994
6442
  ` Slack messaging disabled: ${err instanceof Error ? err.message : String(err)}`
4995
6443
  );
4996
6444
  }
6445
+ } else if (channelConfig.platform === "resend") {
6446
+ const adapter = new ResendAdapter({
6447
+ apiKeyEnv: channelConfig.apiKeyEnv,
6448
+ webhookSecretEnv: channelConfig.webhookSecretEnv,
6449
+ fromEnv: channelConfig.fromEnv,
6450
+ allowedSenders: channelConfig.allowedSenders,
6451
+ mode: channelConfig.mode,
6452
+ allowedRecipients: channelConfig.allowedRecipients,
6453
+ maxSendsPerRun: channelConfig.maxSendsPerRun
6454
+ });
6455
+ const bridge = new AgentBridge({
6456
+ adapter,
6457
+ runner: messagingRunner,
6458
+ waitUntil: waitUntilHook,
6459
+ ownerId: "local-owner"
6460
+ });
6461
+ adapter.registerRoutes(messagingRouteRegistrar);
6462
+ try {
6463
+ await bridge.start();
6464
+ messagingBridges.push(bridge);
6465
+ const adapterTools = adapter.getToolDefinitions?.() ?? [];
6466
+ if (adapterTools.length > 0) {
6467
+ harness.registerTools(adapterTools);
6468
+ }
6469
+ const modeLabel = channelConfig.mode === "tool" ? "tool" : "auto-reply";
6470
+ console.log(` Resend email messaging enabled at /api/messaging/resend (mode: ${modeLabel})`);
6471
+ } catch (err) {
6472
+ console.warn(
6473
+ ` Resend email messaging disabled: ${err instanceof Error ? err.message : String(err)}`
6474
+ );
6475
+ }
4997
6476
  }
4998
6477
  }
4999
6478
  }
@@ -5002,6 +6481,7 @@ var createRequestHandler = async (options) => {
5002
6481
  const authToken = process.env.PONCHO_AUTH_TOKEN ?? "";
5003
6482
  const authRequired = config?.auth?.required ?? false;
5004
6483
  const requireAuth = authRequired && authToken.length > 0;
6484
+ const webUiEnabled = config?.webUi !== false;
5005
6485
  const isProduction = resolveHarnessEnvironment() === "production";
5006
6486
  const secureCookies = isProduction;
5007
6487
  const validateBearerToken = (authHeader) => {
@@ -5023,35 +6503,45 @@ var createRequestHandler = async (options) => {
5023
6503
  return;
5024
6504
  }
5025
6505
  const [pathname] = request.url.split("?");
5026
- if (request.method === "GET" && (pathname === "/" || pathname.startsWith("/c/"))) {
5027
- writeHtml(response, 200, renderWebUiHtml({ agentName }));
5028
- return;
5029
- }
5030
- if (pathname === "/manifest.json" && request.method === "GET") {
5031
- response.writeHead(200, { "Content-Type": "application/manifest+json" });
5032
- response.end(renderManifest({ agentName }));
5033
- return;
5034
- }
5035
- if (pathname === "/sw.js" && request.method === "GET") {
5036
- response.writeHead(200, {
5037
- "Content-Type": "application/javascript",
5038
- "Service-Worker-Allowed": "/"
5039
- });
5040
- response.end(renderServiceWorker());
5041
- return;
6506
+ if (webUiEnabled) {
6507
+ if (request.method === "GET" && (pathname === "/" || pathname.startsWith("/c/"))) {
6508
+ writeHtml(response, 200, renderWebUiHtml({ agentName }));
6509
+ return;
6510
+ }
6511
+ if (pathname === "/manifest.json" && request.method === "GET") {
6512
+ response.writeHead(200, { "Content-Type": "application/manifest+json" });
6513
+ response.end(renderManifest({ agentName }));
6514
+ return;
6515
+ }
6516
+ if (pathname === "/sw.js" && request.method === "GET") {
6517
+ response.writeHead(200, {
6518
+ "Content-Type": "application/javascript",
6519
+ "Service-Worker-Allowed": "/"
6520
+ });
6521
+ response.end(renderServiceWorker());
6522
+ return;
6523
+ }
6524
+ if (pathname === "/icon.svg" && request.method === "GET") {
6525
+ response.writeHead(200, { "Content-Type": "image/svg+xml" });
6526
+ response.end(renderIconSvg({ agentName }));
6527
+ return;
6528
+ }
6529
+ if ((pathname === "/icon-192.png" || pathname === "/icon-512.png") && request.method === "GET") {
6530
+ response.writeHead(302, { Location: "/icon.svg" });
6531
+ response.end();
6532
+ return;
6533
+ }
5042
6534
  }
5043
- if (pathname === "/icon.svg" && request.method === "GET") {
5044
- response.writeHead(200, { "Content-Type": "image/svg+xml" });
5045
- response.end(renderIconSvg({ agentName }));
6535
+ if (pathname === "/health" && request.method === "GET") {
6536
+ writeJson(response, 200, { status: "ok" });
5046
6537
  return;
5047
6538
  }
5048
- if ((pathname === "/icon-192.png" || pathname === "/icon-512.png") && request.method === "GET") {
5049
- response.writeHead(302, { Location: "/icon.svg" });
5050
- response.end();
6539
+ if (pathname === "/api/openapi.json" && request.method === "GET") {
6540
+ writeJson(response, 200, buildOpenApiSpec({ agentName }));
5051
6541
  return;
5052
6542
  }
5053
- if (pathname === "/health" && request.method === "GET") {
5054
- writeJson(response, 200, { status: "ok" });
6543
+ if (pathname === "/api/docs" && request.method === "GET") {
6544
+ writeHtml(response, 200, renderApiDocsHtml("/api/openapi.json"));
5055
6545
  return;
5056
6546
  }
5057
6547
  const messagingByMethod = messagingRoutes.get(pathname ?? "");
@@ -5168,7 +6658,8 @@ var createRequestHandler = async (options) => {
5168
6658
  tenantId: conversation.tenantId,
5169
6659
  createdAt: conversation.createdAt,
5170
6660
  updatedAt: conversation.updatedAt,
5171
- messageCount: conversation.messages.length
6661
+ messageCount: conversation.messages.length,
6662
+ hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0
5172
6663
  }))
5173
6664
  });
5174
6665
  return;
@@ -5192,36 +6683,77 @@ var createRequestHandler = async (options) => {
5192
6683
  const approvalMatch = pathname.match(/^\/api\/approvals\/([^/]+)$/);
5193
6684
  if (approvalMatch && request.method === "POST") {
5194
6685
  const approvalId = decodeURIComponent(approvalMatch[1] ?? "");
5195
- const pending = pendingApprovals.get(approvalId);
5196
- if (!pending || pending.ownerId !== ownerId) {
5197
- const conversations = await conversationStore.list(ownerId);
5198
- let prunedStale = false;
5199
- for (const conversation of conversations) {
5200
- if (!Array.isArray(conversation.pendingApprovals)) {
5201
- continue;
5202
- }
5203
- const next = conversation.pendingApprovals.filter(
5204
- (approval) => approval.approvalId !== approvalId
5205
- );
5206
- if (next.length !== conversation.pendingApprovals.length) {
5207
- conversation.pendingApprovals = next;
5208
- await conversationStore.update(conversation);
5209
- prunedStale = true;
5210
- }
6686
+ const body = await readRequestBody(request);
6687
+ const approved = body.approved === true;
6688
+ const conversations = await conversationStore.list(ownerId);
6689
+ let foundConversation;
6690
+ let foundApproval;
6691
+ for (const conv of conversations) {
6692
+ if (!Array.isArray(conv.pendingApprovals)) continue;
6693
+ const match = conv.pendingApprovals.find((a) => a.approvalId === approvalId);
6694
+ if (match) {
6695
+ foundConversation = conv;
6696
+ foundApproval = match;
6697
+ break;
5211
6698
  }
6699
+ }
6700
+ if (!foundConversation || !foundApproval) {
5212
6701
  writeJson(response, 404, {
5213
6702
  code: "APPROVAL_NOT_FOUND",
5214
- message: prunedStale ? "Approval request is no longer active" : "Approval request not found"
6703
+ message: "Approval request not found"
5215
6704
  });
5216
6705
  return;
5217
6706
  }
5218
- const body = await readRequestBody(request);
5219
- const approved = body.approved === true;
5220
- pendingApprovals.delete(approvalId);
5221
- if (pending.conversationId) {
5222
- await persistConversationPendingApprovals(pending.conversationId);
6707
+ const conversationId = foundConversation.conversationId;
6708
+ if (!foundApproval.checkpointMessages || !foundApproval.toolCallId) {
6709
+ foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).filter((a) => a.approvalId !== approvalId);
6710
+ await conversationStore.update(foundConversation);
6711
+ writeJson(response, 404, {
6712
+ code: "APPROVAL_NOT_FOUND",
6713
+ message: "Approval request is no longer active (no checkpoint data)"
6714
+ });
6715
+ return;
5223
6716
  }
5224
- pending.resolve(approved);
6717
+ foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).filter((a) => a.approvalId !== approvalId);
6718
+ await conversationStore.update(foundConversation);
6719
+ broadcastEvent(
6720
+ conversationId,
6721
+ approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
6722
+ );
6723
+ void (async () => {
6724
+ let toolResults;
6725
+ if (approved) {
6726
+ const toolContext = {
6727
+ runId: foundApproval.runId,
6728
+ agentId: identity.id,
6729
+ step: 0,
6730
+ workingDir,
6731
+ parameters: {}
6732
+ };
6733
+ const execResults = await harness.executeTools(
6734
+ [{ id: foundApproval.toolCallId, name: foundApproval.tool, input: foundApproval.input }],
6735
+ toolContext
6736
+ );
6737
+ toolResults = execResults.map((r) => ({
6738
+ callId: r.callId,
6739
+ toolName: r.tool,
6740
+ result: r.output,
6741
+ error: r.error
6742
+ }));
6743
+ } else {
6744
+ toolResults = [{
6745
+ callId: foundApproval.toolCallId,
6746
+ toolName: foundApproval.tool,
6747
+ error: "Tool execution denied by user"
6748
+ }];
6749
+ }
6750
+ await resumeRunFromCheckpoint(
6751
+ conversationId,
6752
+ foundConversation,
6753
+ foundApproval,
6754
+ toolResults
6755
+ );
6756
+ })();
5225
6757
  writeJson(response, 200, { ok: true, approvalId, approved });
5226
6758
  return;
5227
6759
  }
@@ -5249,12 +6781,15 @@ var createRequestHandler = async (options) => {
5249
6781
  response.end();
5250
6782
  return;
5251
6783
  }
5252
- for (const bufferedEvent of stream.buffer) {
5253
- try {
5254
- response.write(formatSseEvent(bufferedEvent));
5255
- } catch {
5256
- response.end();
5257
- return;
6784
+ const liveOnly = (request.url ?? "").includes("live_only=true");
6785
+ if (!liveOnly) {
6786
+ for (const bufferedEvent of stream.buffer) {
6787
+ try {
6788
+ response.write(formatSseEvent(bufferedEvent));
6789
+ } catch {
6790
+ response.end();
6791
+ return;
6792
+ }
5258
6793
  }
5259
6794
  }
5260
6795
  if (stream.finished) {
@@ -5280,29 +6815,20 @@ var createRequestHandler = async (options) => {
5280
6815
  return;
5281
6816
  }
5282
6817
  if (request.method === "GET") {
5283
- const storedPending = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
5284
- const livePending = Array.from(pendingApprovals.entries()).filter(
5285
- ([, pending]) => pending.ownerId === ownerId && pending.conversationId === conversationId
5286
- ).map(([approvalId, pending]) => ({
5287
- approvalId,
5288
- runId: pending.runId,
5289
- tool: pending.tool,
5290
- input: pending.input
5291
- }));
5292
- const mergedPendingById = /* @__PURE__ */ new Map();
5293
- for (const approval of storedPending) {
5294
- if (approval && typeof approval.approvalId === "string") {
5295
- mergedPendingById.set(approval.approvalId, approval);
5296
- }
5297
- }
5298
- for (const approval of livePending) {
5299
- mergedPendingById.set(approval.approvalId, approval);
5300
- }
6818
+ const storedPending = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals.map((a) => ({
6819
+ approvalId: a.approvalId,
6820
+ runId: a.runId,
6821
+ tool: a.tool,
6822
+ input: a.input
6823
+ })) : [];
6824
+ const activeStream = conversationEventStreams.get(conversationId);
6825
+ const hasActiveRun = !!activeStream && !activeStream.finished;
5301
6826
  writeJson(response, 200, {
5302
6827
  conversation: {
5303
6828
  ...conversation,
5304
- pendingApprovals: Array.from(mergedPendingById.values())
5305
- }
6829
+ pendingApprovals: storedPending
6830
+ },
6831
+ hasActiveRun
5306
6832
  });
5307
6833
  return;
5308
6834
  }
@@ -5484,6 +7010,7 @@ var createRequestHandler = async (options) => {
5484
7010
  let currentText = "";
5485
7011
  let currentTools = [];
5486
7012
  let runCancelled = false;
7013
+ let checkpointedRun = false;
5487
7014
  let userContent = messageText;
5488
7015
  if (files.length > 0) {
5489
7016
  try {
@@ -5563,6 +7090,7 @@ var createRequestHandler = async (options) => {
5563
7090
  })).filter((item) => item.content.length > 0);
5564
7091
  for await (const event of harness.runWithTelemetry({
5565
7092
  task: messageText,
7093
+ conversationId,
5566
7094
  parameters: {
5567
7095
  ...bodyParameters ?? {},
5568
7096
  __conversationRecallCorpus: recallCorpus,
@@ -5620,17 +7148,36 @@ var createRequestHandler = async (options) => {
5620
7148
  currentTools.push(toolText);
5621
7149
  await persistDraftAssistantTurn();
5622
7150
  }
5623
- if (event.type === "tool:approval:granted") {
5624
- const toolText = `- approval granted (${event.approvalId})`;
5625
- toolTimeline.push(toolText);
5626
- currentTools.push(toolText);
5627
- await persistDraftAssistantTurn();
5628
- }
5629
- if (event.type === "tool:approval:denied") {
5630
- const toolText = `- approval denied (${event.approvalId})`;
5631
- toolTimeline.push(toolText);
5632
- currentTools.push(toolText);
5633
- await persistDraftAssistantTurn();
7151
+ if (event.type === "tool:approval:checkpoint") {
7152
+ const checkpointSections = [...sections];
7153
+ if (currentTools.length > 0) {
7154
+ checkpointSections.push({ type: "tools", content: [...currentTools] });
7155
+ }
7156
+ if (currentText.length > 0) {
7157
+ checkpointSections.push({ type: "text", content: currentText });
7158
+ }
7159
+ conversation.messages = [
7160
+ ...historyMessages,
7161
+ { role: "user", content: userContent },
7162
+ ...assistantResponse.length > 0 || toolTimeline.length > 0 || checkpointSections.length > 0 ? [{
7163
+ role: "assistant",
7164
+ content: assistantResponse,
7165
+ metadata: toolTimeline.length > 0 || checkpointSections.length > 0 ? { toolActivity: [...toolTimeline], sections: checkpointSections.length > 0 ? checkpointSections : void 0 } : void 0
7166
+ }] : []
7167
+ ];
7168
+ conversation.pendingApprovals = [{
7169
+ approvalId: event.approvalId,
7170
+ runId: latestRunId,
7171
+ tool: event.tool,
7172
+ toolCallId: event.toolCallId,
7173
+ input: event.input,
7174
+ checkpointMessages: event.checkpointMessages,
7175
+ baseMessageCount: historyMessages.length,
7176
+ pendingToolCalls: event.pendingToolCalls
7177
+ }];
7178
+ conversation.updatedAt = Date.now();
7179
+ await conversationStore.update(conversation);
7180
+ checkpointedRun = true;
5634
7181
  }
5635
7182
  if (event.type === "run:completed" && assistantResponse.length === 0 && event.result.response) {
5636
7183
  assistantResponse = event.result.response;
@@ -5648,23 +7195,25 @@ var createRequestHandler = async (options) => {
5648
7195
  if (currentText.length > 0) {
5649
7196
  sections.push({ type: "text", content: currentText });
5650
7197
  }
5651
- const hasAssistantContent = assistantResponse.length > 0 || toolTimeline.length > 0 || sections.length > 0;
5652
- conversation.messages = hasAssistantContent ? [
5653
- ...historyMessages,
5654
- { role: "user", content: userContent },
5655
- {
5656
- role: "assistant",
5657
- content: assistantResponse,
5658
- metadata: toolTimeline.length > 0 || sections.length > 0 ? {
5659
- toolActivity: toolTimeline,
5660
- sections: sections.length > 0 ? sections : void 0
5661
- } : void 0
5662
- }
5663
- ] : [...historyMessages, { role: "user", content: userContent }];
5664
- conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
5665
- conversation.pendingApprovals = [];
5666
- conversation.updatedAt = Date.now();
5667
- await conversationStore.update(conversation);
7198
+ if (!checkpointedRun) {
7199
+ const hasAssistantContent = assistantResponse.length > 0 || toolTimeline.length > 0 || sections.length > 0;
7200
+ conversation.messages = hasAssistantContent ? [
7201
+ ...historyMessages,
7202
+ { role: "user", content: userContent },
7203
+ {
7204
+ role: "assistant",
7205
+ content: assistantResponse,
7206
+ metadata: toolTimeline.length > 0 || sections.length > 0 ? {
7207
+ toolActivity: toolTimeline,
7208
+ sections: sections.length > 0 ? sections : void 0
7209
+ } : void 0
7210
+ }
7211
+ ] : [...historyMessages, { role: "user", content: userContent }];
7212
+ conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
7213
+ conversation.pendingApprovals = [];
7214
+ conversation.updatedAt = Date.now();
7215
+ await conversationStore.update(conversation);
7216
+ }
5668
7217
  } catch (error) {
5669
7218
  if (abortController.signal.aborted || runCancelled) {
5670
7219
  const fallbackSections = [...sections];
@@ -5735,7 +7284,6 @@ var createRequestHandler = async (options) => {
5735
7284
  activeConversationRuns.delete(conversationId);
5736
7285
  }
5737
7286
  finishConversationStream(conversationId);
5738
- await persistConversationPendingApprovals(conversationId);
5739
7287
  if (latestRunId) {
5740
7288
  runOwners.delete(latestRunId);
5741
7289
  runConversations.delete(latestRunId);
@@ -5807,6 +7355,7 @@ var createRequestHandler = async (options) => {
5807
7355
  const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
5808
7356
  for await (const event of harness.runWithTelemetry({
5809
7357
  task: cronJob.task,
7358
+ conversationId: conversation.conversationId,
5810
7359
  parameters: { __activeConversationId: conversation.conversationId },
5811
7360
  messages: historyMessages,
5812
7361
  abortSignal: abortController.signal
@@ -5968,6 +7517,7 @@ var startDevServer = async (port, options) => {
5968
7517
  let currentText = "";
5969
7518
  for await (const event of harness.runWithTelemetry({
5970
7519
  task: config.task,
7520
+ conversationId: conversation.conversationId,
5971
7521
  parameters: { __activeConversationId: conversation.conversationId },
5972
7522
  messages: []
5973
7523
  })) {
@@ -6132,33 +7682,16 @@ Error: ${event.error.message}
6132
7682
  var runInteractive = async (workingDir, params) => {
6133
7683
  dotenv.config({ path: resolve3(workingDir, ".env") });
6134
7684
  const config = await loadPonchoConfig(workingDir);
6135
- let pendingApproval = null;
6136
- let onApprovalRequest = null;
6137
- const approvalHandler = async (request) => {
6138
- return new Promise((resolveApproval) => {
6139
- const req = {
6140
- tool: request.tool,
6141
- input: request.input,
6142
- approvalId: request.approvalId,
6143
- resolve: resolveApproval
6144
- };
6145
- pendingApproval = req;
6146
- if (onApprovalRequest) {
6147
- onApprovalRequest(req);
6148
- }
6149
- });
6150
- };
6151
7685
  const uploadStore = await createUploadStore(config?.uploads, workingDir);
6152
7686
  const harness = new AgentHarness({
6153
7687
  workingDir,
6154
7688
  environment: resolveHarnessEnvironment(),
6155
- approvalHandler,
6156
7689
  uploadStore
6157
7690
  });
6158
7691
  await harness.initialize();
6159
7692
  const identity = await ensureAgentIdentity2(workingDir);
6160
7693
  try {
6161
- const { runInteractiveInk } = await import("./run-interactive-ink-VZBOYJYS.js");
7694
+ const { runInteractiveInk } = await import("./run-interactive-ink-7ULE5JJI.js");
6162
7695
  await runInteractiveInk({
6163
7696
  harness,
6164
7697
  params,
@@ -6167,13 +7700,7 @@ var runInteractive = async (workingDir, params) => {
6167
7700
  conversationStore: createConversationStore(resolveStateConfig(config), {
6168
7701
  workingDir,
6169
7702
  agentId: identity.id
6170
- }),
6171
- onSetApprovalCallback: (cb) => {
6172
- onApprovalRequest = cb;
6173
- if (pendingApproval) {
6174
- cb(pendingApproval);
6175
- }
6176
- }
7703
+ })
6177
7704
  });
6178
7705
  } finally {
6179
7706
  await harness.shutdown();