@poncho-ai/cli 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +167 -0
- package/dist/{chunk-U643TWFX.js → chunk-XCDN62XL.js} +1983 -135
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-CE7U47S5.js → run-interactive-ink-W5YJS7UH.js} +1 -1
- package/package.json +4 -4
- package/src/cron-helpers.ts +13 -4
- package/src/index.ts +441 -21
- package/src/vfs-zip.ts +94 -0
- package/src/web-ui-client.ts +1028 -26
- package/src/web-ui-styles.ts +413 -15
- package/src/web-ui.ts +6 -1
|
@@ -36,6 +36,85 @@ import {
|
|
|
36
36
|
AgentOrchestrator
|
|
37
37
|
} from "@poncho-ai/harness";
|
|
38
38
|
import { getTextContent } from "@poncho-ai/sdk";
|
|
39
|
+
|
|
40
|
+
// src/vfs-zip.ts
|
|
41
|
+
var CRC_TABLE = (() => {
|
|
42
|
+
const table = new Uint32Array(256);
|
|
43
|
+
for (let i = 0; i < 256; i++) {
|
|
44
|
+
let c = i;
|
|
45
|
+
for (let k = 0; k < 8; k++) c = (c & 1) !== 0 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
46
|
+
table[i] = c >>> 0;
|
|
47
|
+
}
|
|
48
|
+
return table;
|
|
49
|
+
})();
|
|
50
|
+
var crc32 = (data) => {
|
|
51
|
+
let crc = 4294967295;
|
|
52
|
+
for (let i = 0; i < data.length; i++) crc = (CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8) >>> 0;
|
|
53
|
+
return (crc ^ 4294967295) >>> 0;
|
|
54
|
+
};
|
|
55
|
+
var dosDateTime = (date) => {
|
|
56
|
+
const time = (date.getHours() & 31) << 11 | (date.getMinutes() & 63) << 5 | Math.floor(date.getSeconds() / 2) & 31;
|
|
57
|
+
const year = Math.max(1980, date.getFullYear());
|
|
58
|
+
const day = (year - 1980 & 127) << 9 | (date.getMonth() + 1 & 15) << 5 | date.getDate() & 31;
|
|
59
|
+
return { time, day };
|
|
60
|
+
};
|
|
61
|
+
var buildZip = (entries) => {
|
|
62
|
+
const local = [];
|
|
63
|
+
const central = [];
|
|
64
|
+
let offset = 0;
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const nameBuf = Buffer.from(entry.name, "utf8");
|
|
67
|
+
const data = Buffer.from(entry.content);
|
|
68
|
+
const crc = crc32(entry.content);
|
|
69
|
+
const { time, day } = dosDateTime(entry.mtime ?? /* @__PURE__ */ new Date());
|
|
70
|
+
const lfh = Buffer.alloc(30);
|
|
71
|
+
lfh.writeUInt32LE(67324752, 0);
|
|
72
|
+
lfh.writeUInt16LE(20, 4);
|
|
73
|
+
lfh.writeUInt16LE(2048, 6);
|
|
74
|
+
lfh.writeUInt16LE(0, 8);
|
|
75
|
+
lfh.writeUInt16LE(time, 10);
|
|
76
|
+
lfh.writeUInt16LE(day, 12);
|
|
77
|
+
lfh.writeUInt32LE(crc, 14);
|
|
78
|
+
lfh.writeUInt32LE(data.length, 18);
|
|
79
|
+
lfh.writeUInt32LE(data.length, 22);
|
|
80
|
+
lfh.writeUInt16LE(nameBuf.length, 26);
|
|
81
|
+
lfh.writeUInt16LE(0, 28);
|
|
82
|
+
local.push(lfh, nameBuf, data);
|
|
83
|
+
const cdh = Buffer.alloc(46);
|
|
84
|
+
cdh.writeUInt32LE(33639248, 0);
|
|
85
|
+
cdh.writeUInt16LE(20, 4);
|
|
86
|
+
cdh.writeUInt16LE(20, 6);
|
|
87
|
+
cdh.writeUInt16LE(2048, 8);
|
|
88
|
+
cdh.writeUInt16LE(0, 10);
|
|
89
|
+
cdh.writeUInt16LE(time, 12);
|
|
90
|
+
cdh.writeUInt16LE(day, 14);
|
|
91
|
+
cdh.writeUInt32LE(crc, 16);
|
|
92
|
+
cdh.writeUInt32LE(data.length, 20);
|
|
93
|
+
cdh.writeUInt32LE(data.length, 24);
|
|
94
|
+
cdh.writeUInt16LE(nameBuf.length, 28);
|
|
95
|
+
cdh.writeUInt16LE(0, 30);
|
|
96
|
+
cdh.writeUInt16LE(0, 32);
|
|
97
|
+
cdh.writeUInt16LE(0, 34);
|
|
98
|
+
cdh.writeUInt16LE(0, 36);
|
|
99
|
+
cdh.writeUInt32LE(0, 38);
|
|
100
|
+
cdh.writeUInt32LE(offset, 42);
|
|
101
|
+
central.push(cdh, nameBuf);
|
|
102
|
+
offset += lfh.length + nameBuf.length + data.length;
|
|
103
|
+
}
|
|
104
|
+
const centralBuf = Buffer.concat(central);
|
|
105
|
+
const eocd = Buffer.alloc(22);
|
|
106
|
+
eocd.writeUInt32LE(101010256, 0);
|
|
107
|
+
eocd.writeUInt16LE(0, 4);
|
|
108
|
+
eocd.writeUInt16LE(0, 6);
|
|
109
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
110
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
111
|
+
eocd.writeUInt32LE(centralBuf.length, 12);
|
|
112
|
+
eocd.writeUInt32LE(offset, 16);
|
|
113
|
+
eocd.writeUInt16LE(0, 20);
|
|
114
|
+
return Buffer.concat([...local, centralBuf, eocd]);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/index.ts
|
|
39
118
|
import {
|
|
40
119
|
AgentBridge,
|
|
41
120
|
ResendAdapter,
|
|
@@ -359,7 +438,7 @@ var WEB_UI_STYLES = `
|
|
|
359
438
|
.conversation-list {
|
|
360
439
|
flex: 1;
|
|
361
440
|
overflow-y: auto;
|
|
362
|
-
margin-top:
|
|
441
|
+
margin-top: 8px;
|
|
363
442
|
display: flex;
|
|
364
443
|
flex-direction: column;
|
|
365
444
|
gap: 2px;
|
|
@@ -489,6 +568,379 @@ var WEB_UI_STYLES = `
|
|
|
489
568
|
user-select: none;
|
|
490
569
|
}
|
|
491
570
|
.cron-view-more:hover { color: var(--fg-3); }
|
|
571
|
+
.sidebar-segmented {
|
|
572
|
+
display: inline-flex;
|
|
573
|
+
align-self: stretch;
|
|
574
|
+
margin: 12px 6px 0;
|
|
575
|
+
padding: 3px;
|
|
576
|
+
background: var(--surface-3);
|
|
577
|
+
border-radius: 999px;
|
|
578
|
+
gap: 2px;
|
|
579
|
+
}
|
|
580
|
+
.seg-btn {
|
|
581
|
+
flex: 1;
|
|
582
|
+
background: transparent;
|
|
583
|
+
border: 0;
|
|
584
|
+
color: var(--fg-5);
|
|
585
|
+
font-size: 12px;
|
|
586
|
+
font-weight: 500;
|
|
587
|
+
padding: 5px 10px;
|
|
588
|
+
border-radius: 999px;
|
|
589
|
+
cursor: pointer;
|
|
590
|
+
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
|
|
591
|
+
}
|
|
592
|
+
.seg-btn:hover:not(.active) { color: var(--fg-2); }
|
|
593
|
+
.seg-btn.active {
|
|
594
|
+
background: #fff;
|
|
595
|
+
color: #000;
|
|
596
|
+
}
|
|
597
|
+
.file-explorer {
|
|
598
|
+
flex: 1;
|
|
599
|
+
overflow-y: auto;
|
|
600
|
+
margin-top: 8px;
|
|
601
|
+
display: flex;
|
|
602
|
+
flex-direction: column;
|
|
603
|
+
gap: 1px;
|
|
604
|
+
}
|
|
605
|
+
.file-children {
|
|
606
|
+
border-radius: 6px;
|
|
607
|
+
transition: background 0.1s, box-shadow 0.1s;
|
|
608
|
+
}
|
|
609
|
+
.file-children.drop-target {
|
|
610
|
+
background: var(--surface-3);
|
|
611
|
+
box-shadow: inset 0 0 0 1px var(--border-drag);
|
|
612
|
+
}
|
|
613
|
+
.file-row {
|
|
614
|
+
display: flex;
|
|
615
|
+
align-items: center;
|
|
616
|
+
height: 28px;
|
|
617
|
+
padding: 0 8px;
|
|
618
|
+
border-radius: 8px;
|
|
619
|
+
cursor: pointer;
|
|
620
|
+
font-size: 13px;
|
|
621
|
+
color: var(--fg-6);
|
|
622
|
+
user-select: none;
|
|
623
|
+
position: relative;
|
|
624
|
+
transition: color 0.15s, background 0.15s;
|
|
625
|
+
}
|
|
626
|
+
.file-row:hover { color: var(--fg-3); }
|
|
627
|
+
.file-row.active { color: var(--fg); }
|
|
628
|
+
.file-row.is-dir.drop-target { background: var(--surface-4); box-shadow: inset 0 0 0 1px var(--border-drag); }
|
|
629
|
+
.file-row .file-caret {
|
|
630
|
+
display: grid;
|
|
631
|
+
place-items: center;
|
|
632
|
+
width: 14px;
|
|
633
|
+
height: 14px;
|
|
634
|
+
flex-shrink: 0;
|
|
635
|
+
transition: transform 0.15s;
|
|
636
|
+
color: var(--fg-7);
|
|
637
|
+
}
|
|
638
|
+
.file-row .file-caret.open { transform: rotate(90deg); }
|
|
639
|
+
.file-row .file-caret.empty { visibility: hidden; }
|
|
640
|
+
.file-row .file-icon {
|
|
641
|
+
display: inline-flex;
|
|
642
|
+
width: 16px;
|
|
643
|
+
margin-right: 6px;
|
|
644
|
+
flex-shrink: 0;
|
|
645
|
+
color: var(--fg-7);
|
|
646
|
+
}
|
|
647
|
+
.file-row .file-name {
|
|
648
|
+
flex: 1;
|
|
649
|
+
min-width: 0;
|
|
650
|
+
overflow: hidden;
|
|
651
|
+
text-overflow: ellipsis;
|
|
652
|
+
white-space: nowrap;
|
|
653
|
+
}
|
|
654
|
+
.file-row.is-dir .file-name { color: var(--fg-3); }
|
|
655
|
+
.file-row-actions {
|
|
656
|
+
position: absolute;
|
|
657
|
+
right: 0;
|
|
658
|
+
top: 0;
|
|
659
|
+
bottom: 0;
|
|
660
|
+
display: flex;
|
|
661
|
+
align-items: center;
|
|
662
|
+
opacity: 0;
|
|
663
|
+
background: var(--bg);
|
|
664
|
+
border-radius: 0 4px 4px 0;
|
|
665
|
+
transition: opacity 0.15s;
|
|
666
|
+
}
|
|
667
|
+
.file-row:hover .file-row-actions,
|
|
668
|
+
.file-row-actions.confirming { opacity: 1; }
|
|
669
|
+
.file-row-actions::before {
|
|
670
|
+
content: "";
|
|
671
|
+
position: absolute;
|
|
672
|
+
right: 100%;
|
|
673
|
+
top: 0;
|
|
674
|
+
bottom: 0;
|
|
675
|
+
width: 24px;
|
|
676
|
+
background: linear-gradient(to right, transparent, var(--bg));
|
|
677
|
+
pointer-events: none;
|
|
678
|
+
}
|
|
679
|
+
.file-row-action {
|
|
680
|
+
background: transparent;
|
|
681
|
+
border: 0;
|
|
682
|
+
color: var(--fg-7);
|
|
683
|
+
padding: 0 8px;
|
|
684
|
+
cursor: pointer;
|
|
685
|
+
display: grid;
|
|
686
|
+
place-items: center;
|
|
687
|
+
text-decoration: none;
|
|
688
|
+
height: 100%;
|
|
689
|
+
transition: color 0.15s;
|
|
690
|
+
}
|
|
691
|
+
.file-row-action:hover { color: var(--fg-2); }
|
|
692
|
+
.file-row-action svg { width: 14px; height: 14px; }
|
|
693
|
+
.file-row .file-delete.confirming {
|
|
694
|
+
width: auto;
|
|
695
|
+
padding: 0 8px;
|
|
696
|
+
font-size: 11px;
|
|
697
|
+
color: var(--error);
|
|
698
|
+
}
|
|
699
|
+
.file-row .file-delete.confirming:hover { color: var(--error-alt); }
|
|
700
|
+
.file-explorer-empty,
|
|
701
|
+
.file-explorer-error {
|
|
702
|
+
padding: 12px 10px;
|
|
703
|
+
font-size: 12px;
|
|
704
|
+
color: var(--fg-7);
|
|
705
|
+
line-height: 1.5;
|
|
706
|
+
}
|
|
707
|
+
.file-explorer-error { color: var(--error-soft); }
|
|
708
|
+
.file-explorer-error button {
|
|
709
|
+
background: transparent;
|
|
710
|
+
border: 1px solid var(--border-3);
|
|
711
|
+
color: var(--fg-3);
|
|
712
|
+
border-radius: 6px;
|
|
713
|
+
font-size: 12px;
|
|
714
|
+
padding: 4px 10px;
|
|
715
|
+
cursor: pointer;
|
|
716
|
+
margin-top: 6px;
|
|
717
|
+
}
|
|
718
|
+
.file-upload-row {
|
|
719
|
+
display: flex;
|
|
720
|
+
align-items: center;
|
|
721
|
+
gap: 6px;
|
|
722
|
+
padding: 4px 10px;
|
|
723
|
+
font-size: 12px;
|
|
724
|
+
color: var(--fg-7);
|
|
725
|
+
}
|
|
726
|
+
.file-upload-spinner {
|
|
727
|
+
width: 12px;
|
|
728
|
+
height: 12px;
|
|
729
|
+
border-radius: 50%;
|
|
730
|
+
border: 1.5px solid var(--fg-7);
|
|
731
|
+
border-top-color: transparent;
|
|
732
|
+
animation: file-spin 0.8s linear infinite;
|
|
733
|
+
flex-shrink: 0;
|
|
734
|
+
}
|
|
735
|
+
@keyframes file-spin { to { transform: rotate(360deg); } }
|
|
736
|
+
.file-explorer-footer {
|
|
737
|
+
display: flex;
|
|
738
|
+
align-items: center;
|
|
739
|
+
gap: 8px;
|
|
740
|
+
padding: 8px 10px;
|
|
741
|
+
font-size: 11px;
|
|
742
|
+
color: var(--fg-7);
|
|
743
|
+
border-top: 1px solid var(--border-1);
|
|
744
|
+
margin-top: 8px;
|
|
745
|
+
}
|
|
746
|
+
.file-explorer-usage {
|
|
747
|
+
flex: 1;
|
|
748
|
+
min-width: 0;
|
|
749
|
+
display: flex;
|
|
750
|
+
flex-direction: column;
|
|
751
|
+
gap: 2px;
|
|
752
|
+
line-height: 1.3;
|
|
753
|
+
}
|
|
754
|
+
.file-explorer-usage span {
|
|
755
|
+
overflow: hidden;
|
|
756
|
+
text-overflow: ellipsis;
|
|
757
|
+
white-space: nowrap;
|
|
758
|
+
}
|
|
759
|
+
.file-explorer-upload {
|
|
760
|
+
display: inline-flex;
|
|
761
|
+
align-items: center;
|
|
762
|
+
gap: 6px;
|
|
763
|
+
background: transparent;
|
|
764
|
+
border: 1px solid var(--border-3);
|
|
765
|
+
color: var(--fg-3);
|
|
766
|
+
font-size: 12px;
|
|
767
|
+
border-radius: 999px;
|
|
768
|
+
padding: 4px 12px;
|
|
769
|
+
cursor: pointer;
|
|
770
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
771
|
+
}
|
|
772
|
+
.file-explorer-upload:hover { color: var(--fg); border-color: var(--border-5); background: var(--surface-3); }
|
|
773
|
+
.file-explorer-upload svg { width: 14px; height: 14px; }
|
|
774
|
+
.file-explorer-icon-btn {
|
|
775
|
+
display: grid;
|
|
776
|
+
place-items: center;
|
|
777
|
+
width: 26px;
|
|
778
|
+
height: 26px;
|
|
779
|
+
background: transparent;
|
|
780
|
+
border: 1px solid var(--border-3);
|
|
781
|
+
border-radius: 999px;
|
|
782
|
+
color: var(--fg-3);
|
|
783
|
+
cursor: pointer;
|
|
784
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
785
|
+
flex-shrink: 0;
|
|
786
|
+
}
|
|
787
|
+
.file-explorer-icon-btn:hover { color: var(--fg); border-color: var(--border-5); background: var(--surface-3); }
|
|
788
|
+
.file-explorer-icon-btn svg { width: 13px; height: 13px; }
|
|
789
|
+
.file-preview {
|
|
790
|
+
display: flex;
|
|
791
|
+
flex-direction: column;
|
|
792
|
+
min-height: 100%;
|
|
793
|
+
height: 100%;
|
|
794
|
+
}
|
|
795
|
+
.file-preview-text {
|
|
796
|
+
flex: 1;
|
|
797
|
+
overflow: auto;
|
|
798
|
+
padding: 16px 20px;
|
|
799
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
800
|
+
font-size: 12.5px;
|
|
801
|
+
line-height: 1.5;
|
|
802
|
+
color: var(--fg-2);
|
|
803
|
+
white-space: pre-wrap;
|
|
804
|
+
overflow-wrap: anywhere;
|
|
805
|
+
margin: 0;
|
|
806
|
+
}
|
|
807
|
+
.file-preview-markdown {
|
|
808
|
+
flex: 1;
|
|
809
|
+
overflow: auto;
|
|
810
|
+
padding: 16px 20px;
|
|
811
|
+
}
|
|
812
|
+
.file-preview-table-wrap {
|
|
813
|
+
flex: 1;
|
|
814
|
+
overflow: auto;
|
|
815
|
+
padding: 0 16px 16px;
|
|
816
|
+
}
|
|
817
|
+
.file-preview-table {
|
|
818
|
+
border-collapse: separate;
|
|
819
|
+
border-spacing: 0;
|
|
820
|
+
font-size: 12.5px;
|
|
821
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
822
|
+
color: var(--fg-2);
|
|
823
|
+
width: max-content;
|
|
824
|
+
min-width: 100%;
|
|
825
|
+
}
|
|
826
|
+
.file-preview-table th,
|
|
827
|
+
.file-preview-table td {
|
|
828
|
+
border-right: 1px solid var(--border-1);
|
|
829
|
+
border-bottom: 1px solid var(--border-1);
|
|
830
|
+
padding: 6px 10px;
|
|
831
|
+
text-align: left;
|
|
832
|
+
vertical-align: top;
|
|
833
|
+
white-space: pre-wrap;
|
|
834
|
+
max-width: 480px;
|
|
835
|
+
overflow-wrap: anywhere;
|
|
836
|
+
}
|
|
837
|
+
.file-preview-table tr th:first-child,
|
|
838
|
+
.file-preview-table tr td:first-child {
|
|
839
|
+
border-left: 1px solid var(--border-1);
|
|
840
|
+
}
|
|
841
|
+
.file-preview-table thead th {
|
|
842
|
+
position: sticky;
|
|
843
|
+
top: 0;
|
|
844
|
+
background: var(--bg);
|
|
845
|
+
color: var(--fg);
|
|
846
|
+
font-weight: 600;
|
|
847
|
+
z-index: 2;
|
|
848
|
+
border-top: 1px solid var(--border-1);
|
|
849
|
+
box-shadow: 0 1px 0 var(--border-1);
|
|
850
|
+
}
|
|
851
|
+
.file-preview-table tbody td { background: var(--bg); }
|
|
852
|
+
.file-preview-table tbody tr:hover td { background: var(--surface-2); }
|
|
853
|
+
.file-preview-table-truncated {
|
|
854
|
+
padding: 6px 4px 0;
|
|
855
|
+
font-size: 11px;
|
|
856
|
+
color: var(--fg-7);
|
|
857
|
+
}
|
|
858
|
+
.file-preview-actions {
|
|
859
|
+
display: flex;
|
|
860
|
+
justify-content: space-between;
|
|
861
|
+
gap: 6px;
|
|
862
|
+
padding: 8px 16px 12px;
|
|
863
|
+
}
|
|
864
|
+
.file-preview-actions-group {
|
|
865
|
+
display: flex;
|
|
866
|
+
gap: 6px;
|
|
867
|
+
}
|
|
868
|
+
.file-preview-action-btn {
|
|
869
|
+
background: transparent;
|
|
870
|
+
border: 1px solid var(--border-3);
|
|
871
|
+
color: var(--fg-3);
|
|
872
|
+
border-radius: 999px;
|
|
873
|
+
font-size: 12px;
|
|
874
|
+
padding: 4px 12px;
|
|
875
|
+
cursor: pointer;
|
|
876
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
877
|
+
}
|
|
878
|
+
.file-preview-action-btn { text-decoration: none; }
|
|
879
|
+
.file-preview-action-btn:hover { color: var(--fg); border-color: var(--border-5); }
|
|
880
|
+
.file-preview-action-btn.primary {
|
|
881
|
+
background: var(--accent);
|
|
882
|
+
border-color: var(--accent);
|
|
883
|
+
color: var(--accent-fg);
|
|
884
|
+
}
|
|
885
|
+
.file-preview-action-btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-fg); }
|
|
886
|
+
.file-preview-action-btn:disabled { opacity: 0.5; cursor: default; }
|
|
887
|
+
.file-edit-textarea {
|
|
888
|
+
flex: 1;
|
|
889
|
+
width: 100%;
|
|
890
|
+
box-sizing: border-box;
|
|
891
|
+
background: transparent;
|
|
892
|
+
border: 0;
|
|
893
|
+
outline: none;
|
|
894
|
+
resize: none;
|
|
895
|
+
padding: 16px 20px;
|
|
896
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
897
|
+
font-size: 12.5px;
|
|
898
|
+
line-height: 1.5;
|
|
899
|
+
color: var(--fg);
|
|
900
|
+
white-space: pre-wrap;
|
|
901
|
+
overflow-wrap: anywhere;
|
|
902
|
+
}
|
|
903
|
+
.file-preview-image,
|
|
904
|
+
.file-preview-pdf,
|
|
905
|
+
.file-preview-media {
|
|
906
|
+
flex: 1;
|
|
907
|
+
display: grid;
|
|
908
|
+
place-items: center;
|
|
909
|
+
padding: 16px;
|
|
910
|
+
overflow: auto;
|
|
911
|
+
min-height: 0;
|
|
912
|
+
}
|
|
913
|
+
.file-preview-image img { max-width: 100%; max-height: 100%; object-fit: contain; }
|
|
914
|
+
.file-preview-pdf iframe { width: 100%; height: 100%; border: 0; min-height: 600px; }
|
|
915
|
+
.file-preview-placeholder {
|
|
916
|
+
flex: 1;
|
|
917
|
+
display: grid;
|
|
918
|
+
place-items: center;
|
|
919
|
+
color: var(--fg-7);
|
|
920
|
+
text-align: center;
|
|
921
|
+
padding: 32px;
|
|
922
|
+
}
|
|
923
|
+
.file-preview-placeholder .file-preview-name {
|
|
924
|
+
font-size: 14px;
|
|
925
|
+
color: var(--fg-3);
|
|
926
|
+
margin-bottom: 8px;
|
|
927
|
+
word-break: break-all;
|
|
928
|
+
}
|
|
929
|
+
.file-preview-placeholder .file-preview-meta {
|
|
930
|
+
font-size: 12px;
|
|
931
|
+
margin-bottom: 16px;
|
|
932
|
+
}
|
|
933
|
+
.file-preview-download {
|
|
934
|
+
display: inline-block;
|
|
935
|
+
background: var(--surface-4);
|
|
936
|
+
color: var(--fg);
|
|
937
|
+
text-decoration: none;
|
|
938
|
+
padding: 8px 16px;
|
|
939
|
+
border-radius: 8px;
|
|
940
|
+
font-size: 13px;
|
|
941
|
+
transition: background 0.15s;
|
|
942
|
+
}
|
|
943
|
+
.file-preview-download:hover { background: var(--surface-6); }
|
|
492
944
|
.sidebar-footer {
|
|
493
945
|
margin-top: auto;
|
|
494
946
|
padding-top: 8px;
|
|
@@ -680,6 +1132,40 @@ var WEB_UI_STYLES = `
|
|
|
680
1132
|
}
|
|
681
1133
|
.topbar-new-chat:hover { color: var(--fg); }
|
|
682
1134
|
.topbar-new-chat svg { width: 16px; height: 16px; }
|
|
1135
|
+
.dev-view-toggle {
|
|
1136
|
+
position: fixed;
|
|
1137
|
+
left: 12px;
|
|
1138
|
+
bottom: 12px;
|
|
1139
|
+
background: var(--chip-bg);
|
|
1140
|
+
border: 1px solid var(--border-4);
|
|
1141
|
+
color: var(--fg-4);
|
|
1142
|
+
font-size: 11px;
|
|
1143
|
+
letter-spacing: 0.04em;
|
|
1144
|
+
padding: 4px 10px;
|
|
1145
|
+
border-radius: 999px;
|
|
1146
|
+
cursor: pointer;
|
|
1147
|
+
z-index: 1000;
|
|
1148
|
+
backdrop-filter: blur(6px);
|
|
1149
|
+
-webkit-backdrop-filter: blur(6px);
|
|
1150
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
1151
|
+
}
|
|
1152
|
+
.dev-view-toggle:hover { color: var(--fg); border-color: var(--border-hover); background: var(--chip-bg-hover); }
|
|
1153
|
+
.dev-view-toggle.is-harness { color: var(--fg); border-color: var(--fg); }
|
|
1154
|
+
.harness-debug-view {
|
|
1155
|
+
padding: 16px 20px;
|
|
1156
|
+
font-family: var(--mono, ui-monospace, SFMono-Regular, Consolas, monospace);
|
|
1157
|
+
font-size: 11px;
|
|
1158
|
+
color: var(--fg);
|
|
1159
|
+
white-space: pre-wrap;
|
|
1160
|
+
word-break: break-word;
|
|
1161
|
+
}
|
|
1162
|
+
.harness-debug-view .hd-msg { margin-bottom: 16px; padding: 8px 10px; border-left: 3px solid var(--fg-5); background: var(--bg-2, rgba(255,255,255,0.02)); }
|
|
1163
|
+
.harness-debug-view .hd-msg.role-user { border-left-color: #6ea8fe; }
|
|
1164
|
+
.harness-debug-view .hd-msg.role-assistant { border-left-color: #22c55e; }
|
|
1165
|
+
.harness-debug-view .hd-msg.role-tool { border-left-color: #f59e0b; }
|
|
1166
|
+
.harness-debug-view .hd-msg.role-system { border-left-color: #9ca3af; }
|
|
1167
|
+
.harness-debug-view .hd-role { font-weight: 600; opacity: 0.7; margin-bottom: 4px; text-transform: uppercase; font-size: 10px; letter-spacing: 0.06em; }
|
|
1168
|
+
.harness-debug-view .hd-meta { opacity: 0.5; font-size: 10px; margin-bottom: 6px; }
|
|
683
1169
|
|
|
684
1170
|
/* Messages */
|
|
685
1171
|
.messages { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 24px 24px; }
|
|
@@ -2280,21 +2766,12 @@ var WEB_UI_STYLES = `
|
|
|
2280
2766
|
border-radius: 4px;
|
|
2281
2767
|
}
|
|
2282
2768
|
.thread-panel-close:hover { color: var(--fg); }
|
|
2283
|
-
.thread-panel-parent {
|
|
2284
|
-
padding: 12px 16px;
|
|
2285
|
-
border-bottom: 1px solid var(--border);
|
|
2286
|
-
background: var(--bg-bubble-user, rgba(0,0,0,0.02));
|
|
2287
|
-
font-size: 13px;
|
|
2288
|
-
}
|
|
2289
|
-
.thread-panel-parent .message-row {
|
|
2290
|
-
margin: 0;
|
|
2291
|
-
}
|
|
2292
|
-
.thread-panel-parent-empty {
|
|
2293
|
-
color: var(--fg-3);
|
|
2294
|
-
font-style: italic;
|
|
2295
|
-
}
|
|
2296
2769
|
.thread-panel-messages {
|
|
2297
|
-
flex: 1;
|
|
2770
|
+
flex: 1 1 0;
|
|
2771
|
+
/* min-height: 0 is required so this flex item can shrink below its
|
|
2772
|
+
content size \u2014 without it, overflow-y:auto never engages because
|
|
2773
|
+
the element grows to fit all messages instead of scrolling. */
|
|
2774
|
+
min-height: 0;
|
|
2298
2775
|
overflow-y: auto;
|
|
2299
2776
|
padding: 12px 16px;
|
|
2300
2777
|
}
|
|
@@ -2363,6 +2840,11 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2363
2840
|
conversations: [],
|
|
2364
2841
|
activeConversationId: null,
|
|
2365
2842
|
activeMessages: [],
|
|
2843
|
+
// Verbose dev (-v) only: mirror of conversation._harnessMessages plus
|
|
2844
|
+
// the current view mode the user toggled to.
|
|
2845
|
+
verboseDev: false,
|
|
2846
|
+
viewMode: "user", // "user" | "harness"
|
|
2847
|
+
harnessMessages: null,
|
|
2366
2848
|
isStreaming: false,
|
|
2367
2849
|
activeStreamAbortController: null,
|
|
2368
2850
|
activeStreamConversationId: null,
|
|
@@ -2391,12 +2873,19 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2391
2873
|
open: false,
|
|
2392
2874
|
threadId: null,
|
|
2393
2875
|
parentMessageId: null,
|
|
2394
|
-
parentMessage: null,
|
|
2395
2876
|
messages: [],
|
|
2396
2877
|
isStreaming: false,
|
|
2397
2878
|
abortController: null,
|
|
2398
2879
|
pendingFiles: [],
|
|
2399
2880
|
},
|
|
2881
|
+
sidebarMode: "conversations",
|
|
2882
|
+
expandedDirs: new Set(["/"]),
|
|
2883
|
+
dirCache: new Map(),
|
|
2884
|
+
activeFilePath: null,
|
|
2885
|
+
pendingUploads: 0,
|
|
2886
|
+
fileExplorerError: null,
|
|
2887
|
+
fileExplorerUsage: null,
|
|
2888
|
+
confirmDeletePath: null,
|
|
2400
2889
|
};
|
|
2401
2890
|
|
|
2402
2891
|
const agentInitial = document.body.dataset.agentInitial || "A";
|
|
@@ -2412,6 +2901,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2412
2901
|
topbarNewChat: $("topbar-new-chat"),
|
|
2413
2902
|
messages: $("messages"),
|
|
2414
2903
|
chatTitle: $("chat-title"),
|
|
2904
|
+
viewToggle: $("view-toggle"),
|
|
2415
2905
|
logout: $("logout"),
|
|
2416
2906
|
composer: $("composer"),
|
|
2417
2907
|
prompt: $("prompt"),
|
|
@@ -2438,7 +2928,6 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2438
2928
|
threadPanel: $("thread-panel"),
|
|
2439
2929
|
threadPanelResize: $("thread-panel-resize"),
|
|
2440
2930
|
threadPanelClose: $("thread-panel-close"),
|
|
2441
|
-
threadPanelParent: $("thread-panel-parent"),
|
|
2442
2931
|
threadPanelMessages: $("thread-panel-messages"),
|
|
2443
2932
|
threadComposer: $("thread-composer"),
|
|
2444
2933
|
threadAttachBtn: $("thread-attach-btn"),
|
|
@@ -2446,6 +2935,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2446
2935
|
threadAttachmentPreview: $("thread-attachment-preview"),
|
|
2447
2936
|
threadPrompt: $("thread-prompt"),
|
|
2448
2937
|
threadSend: $("thread-send"),
|
|
2938
|
+
fileExplorer: $("file-explorer"),
|
|
2449
2939
|
};
|
|
2450
2940
|
const sendIconMarkup =
|
|
2451
2941
|
'<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>';
|
|
@@ -2497,6 +2987,25 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2497
2987
|
return match ? decodeURIComponent(match[1]) : null;
|
|
2498
2988
|
};
|
|
2499
2989
|
|
|
2990
|
+
const pushFileUrl = (filePath) => {
|
|
2991
|
+
const target = filePath ? "/f/" + encodeURIComponent(filePath) : "/";
|
|
2992
|
+
if (window.location.pathname !== target) {
|
|
2993
|
+
history.pushState({ filePath: filePath || null }, "", target);
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
|
|
2997
|
+
const replaceFileUrl = (filePath) => {
|
|
2998
|
+
const target = filePath ? "/f/" + encodeURIComponent(filePath) : "/";
|
|
2999
|
+
if (window.location.pathname !== target) {
|
|
3000
|
+
history.replaceState({ filePath: filePath || null }, "", target);
|
|
3001
|
+
}
|
|
3002
|
+
};
|
|
3003
|
+
|
|
3004
|
+
const getFilePathFromUrl = () => {
|
|
3005
|
+
const match = window.location.pathname.match(/^\\/f\\/(.+)/);
|
|
3006
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
3007
|
+
};
|
|
3008
|
+
|
|
2500
3009
|
const mutatingMethods = new Set(["POST", "PATCH", "PUT", "DELETE"]);
|
|
2501
3010
|
|
|
2502
3011
|
const api = async (path, options = {}) => {
|
|
@@ -3165,6 +3674,8 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3165
3674
|
}
|
|
3166
3675
|
var switchingFamily = state.subagentsParentId !== c.conversationId;
|
|
3167
3676
|
state.activeConversationId = c.conversationId;
|
|
3677
|
+
state.activeFilePath = null;
|
|
3678
|
+
if (elements.composer) elements.composer.classList.remove("hidden");
|
|
3168
3679
|
state.viewingSubagentId = null;
|
|
3169
3680
|
state.parentConversationId = null;
|
|
3170
3681
|
if (switchingFamily) {
|
|
@@ -3765,26 +4276,8 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3765
4276
|
col.className = "messages-column";
|
|
3766
4277
|
msgs.forEach((m) => col.appendChild(buildSimpleMessageRow(m)));
|
|
3767
4278
|
root.appendChild(col);
|
|
3768
|
-
}
|
|
3769
|
-
root.scrollTop = root.scrollHeight;
|
|
3770
|
-
};
|
|
3771
|
-
|
|
3772
|
-
const renderThreadPanelParent = () => {
|
|
3773
|
-
const root = elements.threadPanelParent;
|
|
3774
|
-
if (!root) return;
|
|
3775
|
-
root.innerHTML = "";
|
|
3776
|
-
const parent = state.threadPanel.parentMessage;
|
|
3777
|
-
if (!parent) {
|
|
3778
|
-
const empty = document.createElement("div");
|
|
3779
|
-
empty.className = "thread-panel-parent-empty";
|
|
3780
|
-
empty.textContent = "No parent context";
|
|
3781
|
-
root.appendChild(empty);
|
|
3782
|
-
return;
|
|
3783
|
-
}
|
|
3784
|
-
const col = document.createElement("div");
|
|
3785
|
-
col.className = "messages-column";
|
|
3786
|
-
col.appendChild(buildSimpleMessageRow(parent));
|
|
3787
|
-
root.appendChild(col);
|
|
4279
|
+
}
|
|
4280
|
+
root.scrollTop = root.scrollHeight;
|
|
3788
4281
|
};
|
|
3789
4282
|
|
|
3790
4283
|
const closeThreadPanel = () => {
|
|
@@ -3794,7 +4287,6 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3794
4287
|
state.threadPanel.open = false;
|
|
3795
4288
|
state.threadPanel.threadId = null;
|
|
3796
4289
|
state.threadPanel.parentMessageId = null;
|
|
3797
|
-
state.threadPanel.parentMessage = null;
|
|
3798
4290
|
state.threadPanel.messages = [];
|
|
3799
4291
|
state.threadPanel.isStreaming = false;
|
|
3800
4292
|
state.threadPanel.abortController = null;
|
|
@@ -3823,14 +4315,15 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3823
4315
|
const renderActiveTopForThreadPanel = (payload) => {
|
|
3824
4316
|
const conv = payload.conversation || {};
|
|
3825
4317
|
const allMsgs = Array.isArray(conv.messages) ? conv.messages : [];
|
|
4318
|
+
// Show the anchor message + replies. The earlier snapshot is still
|
|
4319
|
+
// part of the thread's context server-side, but the panel only
|
|
4320
|
+
// displays what's relevant: the message you forked on, plus what
|
|
4321
|
+
// came after.
|
|
3826
4322
|
const snapshotLength = (conv.threadMeta && typeof conv.threadMeta.snapshotLength === "number")
|
|
3827
4323
|
? conv.threadMeta.snapshotLength
|
|
3828
4324
|
: allMsgs.length;
|
|
3829
|
-
const
|
|
3830
|
-
|
|
3831
|
-
state.threadPanel.parentMessage = parent;
|
|
3832
|
-
state.threadPanel.messages = replies;
|
|
3833
|
-
renderThreadPanelParent();
|
|
4325
|
+
const startIdx = Math.max(0, snapshotLength - 1);
|
|
4326
|
+
state.threadPanel.messages = allMsgs.slice(startIdx);
|
|
3834
4327
|
renderThreadPanelMessages();
|
|
3835
4328
|
};
|
|
3836
4329
|
|
|
@@ -4089,7 +4582,66 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4089
4582
|
}
|
|
4090
4583
|
};
|
|
4091
4584
|
|
|
4585
|
+
const renderHarnessView = () => {
|
|
4586
|
+
const msgs = Array.isArray(state.harnessMessages) ? state.harnessMessages : [];
|
|
4587
|
+
if (msgs.length === 0) {
|
|
4588
|
+
elements.messages.innerHTML = '<div class="harness-debug-view"><em>No harness messages yet \u2014 they appear after the first assistant turn.</em></div>';
|
|
4589
|
+
return;
|
|
4590
|
+
}
|
|
4591
|
+
const rows = msgs.map((m, i) => {
|
|
4592
|
+
const role = String(m.role || "?");
|
|
4593
|
+
const meta = m.metadata && typeof m.metadata === "object" ? m.metadata : null;
|
|
4594
|
+
const metaLine = meta
|
|
4595
|
+
? "step=" + (meta.step != null ? meta.step : "-") +
|
|
4596
|
+
" runId=" + (meta.runId ? String(meta.runId).slice(0, 16) : "-") +
|
|
4597
|
+
" id=" + (meta.id ? String(meta.id).slice(0, 12) : "-")
|
|
4598
|
+
: "";
|
|
4599
|
+
let content = m.content;
|
|
4600
|
+
if (typeof content !== "string") content = JSON.stringify(content, null, 2);
|
|
4601
|
+
// Pretty-print JSON content where possible (assistant tool calls,
|
|
4602
|
+
// tool result arrays, etc.) so it's actually readable.
|
|
4603
|
+
const trimmed = content.trim();
|
|
4604
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
4605
|
+
try { content = JSON.stringify(JSON.parse(trimmed), null, 2); } catch (_e) { /* leave as-is */ }
|
|
4606
|
+
}
|
|
4607
|
+
return '<div class="hd-msg role-' + escapeHtml(role) + '">' +
|
|
4608
|
+
'<div class="hd-role">#' + i + ' \xB7 ' + escapeHtml(role) + '</div>' +
|
|
4609
|
+
(metaLine ? '<div class="hd-meta">' + escapeHtml(metaLine) + '</div>' : '') +
|
|
4610
|
+
'<div>' + escapeHtml(content) + '</div>' +
|
|
4611
|
+
'</div>';
|
|
4612
|
+
}).join("");
|
|
4613
|
+
elements.messages.innerHTML = '<div class="harness-debug-view">' + rows + '</div>';
|
|
4614
|
+
};
|
|
4615
|
+
|
|
4616
|
+
const updateViewToggleVisibility = () => {
|
|
4617
|
+
if (!elements.viewToggle) return;
|
|
4618
|
+
if (!state.verboseDev) {
|
|
4619
|
+
elements.viewToggle.hidden = true;
|
|
4620
|
+
return;
|
|
4621
|
+
}
|
|
4622
|
+
elements.viewToggle.hidden = false;
|
|
4623
|
+
elements.viewToggle.textContent = state.viewMode === "harness" ? "harness view" : "user view";
|
|
4624
|
+
elements.viewToggle.classList.toggle("is-harness", state.viewMode === "harness");
|
|
4625
|
+
};
|
|
4626
|
+
|
|
4627
|
+
if (elements.viewToggle) {
|
|
4628
|
+
elements.viewToggle.addEventListener("click", () => {
|
|
4629
|
+
state.viewMode = state.viewMode === "harness" ? "user" : "harness";
|
|
4630
|
+
updateViewToggleVisibility();
|
|
4631
|
+
if (state.viewMode === "harness") {
|
|
4632
|
+
renderHarnessView();
|
|
4633
|
+
} else {
|
|
4634
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
4635
|
+
}
|
|
4636
|
+
});
|
|
4637
|
+
}
|
|
4638
|
+
|
|
4092
4639
|
const renderMessages = (messages, isStreaming = false, options = {}) => {
|
|
4640
|
+
// In harness debug view, the user-facing renderer is bypassed.
|
|
4641
|
+
if (state.viewMode === "harness") {
|
|
4642
|
+
renderHarnessView();
|
|
4643
|
+
return;
|
|
4644
|
+
}
|
|
4093
4645
|
const previousScrollTop = elements.messages.scrollTop;
|
|
4094
4646
|
const shouldStickToBottom =
|
|
4095
4647
|
options.forceScrollBottom === true || state.isMessagesPinnedToBottom;
|
|
@@ -4353,6 +4905,13 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4353
4905
|
.catch(() => ({ threads: [] }));
|
|
4354
4906
|
const payload = await conversationPromise;
|
|
4355
4907
|
elements.chatTitle.textContent = payload.conversation.title;
|
|
4908
|
+
// Verbose dev (-v) only \u2014 server includes verboseDev: true and the
|
|
4909
|
+
// raw harness-message stream so we can offer a debug toggle.
|
|
4910
|
+
state.verboseDev = payload.verboseDev === true;
|
|
4911
|
+
state.harnessMessages = state.verboseDev && Array.isArray(payload.conversation._harnessMessages)
|
|
4912
|
+
? payload.conversation._harnessMessages
|
|
4913
|
+
: null;
|
|
4914
|
+
updateViewToggleVisibility();
|
|
4356
4915
|
// Merge own pending approvals + subagent pending approvals
|
|
4357
4916
|
var allPendingApprovals = [].concat(
|
|
4358
4917
|
payload.conversation.pendingApprovals || payload.pendingApprovals || [],
|
|
@@ -4651,6 +5210,12 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4651
5210
|
if (typeof payload.conversation.contextWindow === "number" && payload.conversation.contextWindow > 0) {
|
|
4652
5211
|
state.contextWindow = payload.conversation.contextWindow;
|
|
4653
5212
|
}
|
|
5213
|
+
// Keep harness debug view fresh on refetches in -v mode.
|
|
5214
|
+
state.verboseDev = payload.verboseDev === true;
|
|
5215
|
+
state.harnessMessages = state.verboseDev && Array.isArray(payload.conversation._harnessMessages)
|
|
5216
|
+
? payload.conversation._harnessMessages
|
|
5217
|
+
: null;
|
|
5218
|
+
updateViewToggleVisibility();
|
|
4654
5219
|
updateContextRing();
|
|
4655
5220
|
renderMessages(state.activeMessages, streaming);
|
|
4656
5221
|
return payload;
|
|
@@ -6315,6 +6880,8 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
6315
6880
|
const startNewChat = () => {
|
|
6316
6881
|
if (window._resetBrowserPanel) window._resetBrowserPanel();
|
|
6317
6882
|
state.activeConversationId = null;
|
|
6883
|
+
state.activeFilePath = null;
|
|
6884
|
+
if (elements.composer) elements.composer.classList.remove("hidden");
|
|
6318
6885
|
state.activeMessages = [];
|
|
6319
6886
|
state.confirmDeleteId = null;
|
|
6320
6887
|
state.contextTokens = 0;
|
|
@@ -6653,13 +7220,17 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
6653
7220
|
});
|
|
6654
7221
|
|
|
6655
7222
|
let dragCounter = 0;
|
|
7223
|
+
const _inFileExplorer = (target) =>
|
|
7224
|
+
elements.fileExplorer && target instanceof Node && elements.fileExplorer.contains(target);
|
|
6656
7225
|
document.addEventListener("dragenter", (e) => {
|
|
6657
7226
|
e.preventDefault();
|
|
7227
|
+
if (_inFileExplorer(e.target)) return;
|
|
6658
7228
|
dragCounter++;
|
|
6659
7229
|
if (dragCounter === 1) elements.dragOverlay.classList.add("active");
|
|
6660
7230
|
});
|
|
6661
7231
|
document.addEventListener("dragleave", (e) => {
|
|
6662
7232
|
e.preventDefault();
|
|
7233
|
+
if (_inFileExplorer(e.target)) return;
|
|
6663
7234
|
dragCounter--;
|
|
6664
7235
|
if (dragCounter <= 0) { dragCounter = 0; elements.dragOverlay.classList.remove("active"); }
|
|
6665
7236
|
});
|
|
@@ -6668,6 +7239,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
6668
7239
|
e.preventDefault();
|
|
6669
7240
|
dragCounter = 0;
|
|
6670
7241
|
elements.dragOverlay.classList.remove("active");
|
|
7242
|
+
if (_inFileExplorer(e.target)) return;
|
|
6671
7243
|
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
|
6672
7244
|
addFiles(e.dataTransfer.files);
|
|
6673
7245
|
}
|
|
@@ -6875,89 +7447,976 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
6875
7447
|
if (!approvalId || (decision !== "approve" && decision !== "deny")) {
|
|
6876
7448
|
return;
|
|
6877
7449
|
}
|
|
6878
|
-
if (state.approvalRequestsInFlight[approvalId]) {
|
|
6879
|
-
return;
|
|
7450
|
+
if (state.approvalRequestsInFlight[approvalId]) {
|
|
7451
|
+
return;
|
|
7452
|
+
}
|
|
7453
|
+
const wasStreaming = state.isStreaming;
|
|
7454
|
+
if (!wasStreaming) {
|
|
7455
|
+
setStreaming(true);
|
|
7456
|
+
}
|
|
7457
|
+
submitApproval(approvalId, decision);
|
|
7458
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
7459
|
+
loadConversations();
|
|
7460
|
+
if (!wasStreaming && state.activeConversationId) {
|
|
7461
|
+
const cid = state.activeConversationId;
|
|
7462
|
+
await streamConversationEvents(cid, { liveOnly: true });
|
|
7463
|
+
if (state.activeConversationId === cid) {
|
|
7464
|
+
pollUntilRunIdle(cid);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
});
|
|
7468
|
+
|
|
7469
|
+
elements.messages.addEventListener("click", (e) => {
|
|
7470
|
+
const link = e.target instanceof Element && e.target.closest(".subagent-link");
|
|
7471
|
+
if (!link) return;
|
|
7472
|
+
e.preventDefault();
|
|
7473
|
+
const subId = link.getAttribute("data-subagent-id");
|
|
7474
|
+
if (subId) {
|
|
7475
|
+
state.viewingSubagentId = subId;
|
|
7476
|
+
state.activeConversationId = subId;
|
|
7477
|
+
replaceConversationUrl(subId);
|
|
7478
|
+
loadConversation(subId);
|
|
7479
|
+
}
|
|
7480
|
+
});
|
|
7481
|
+
|
|
7482
|
+
elements.messages.addEventListener("scroll", () => {
|
|
7483
|
+
state.isMessagesPinnedToBottom = isNearBottom(elements.messages);
|
|
7484
|
+
}, { passive: true });
|
|
7485
|
+
|
|
7486
|
+
document.addEventListener("click", (event) => {
|
|
7487
|
+
if (!(event.target instanceof Node)) {
|
|
7488
|
+
return;
|
|
7489
|
+
}
|
|
7490
|
+
if (!event.target.closest(".conversation-item") && state.confirmDeleteId) {
|
|
7491
|
+
state.confirmDeleteId = null;
|
|
7492
|
+
renderConversationList();
|
|
7493
|
+
}
|
|
7494
|
+
if (!event.target.closest(".thread-row") && state.confirmDeleteThreadId) {
|
|
7495
|
+
state.confirmDeleteThreadId = null;
|
|
7496
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
7497
|
+
}
|
|
7498
|
+
});
|
|
7499
|
+
|
|
7500
|
+
window.addEventListener("resize", () => {
|
|
7501
|
+
setSidebarOpen(false);
|
|
7502
|
+
});
|
|
7503
|
+
|
|
7504
|
+
const navigateToConversation = async (conversationId) => {
|
|
7505
|
+
state.activeFilePath = null;
|
|
7506
|
+
if (elements.composer) elements.composer.classList.remove("hidden");
|
|
7507
|
+
if (conversationId) {
|
|
7508
|
+
state.activeConversationId = conversationId;
|
|
7509
|
+
renderConversationList();
|
|
7510
|
+
try {
|
|
7511
|
+
await loadConversation(conversationId);
|
|
7512
|
+
} catch {
|
|
7513
|
+
// Conversation not found \u2013 fall back to empty state
|
|
7514
|
+
state.activeConversationId = null;
|
|
7515
|
+
state.activeMessages = [];
|
|
7516
|
+
replaceConversationUrl(null);
|
|
7517
|
+
elements.chatTitle.textContent = "";
|
|
7518
|
+
renderMessages([]);
|
|
7519
|
+
renderConversationList();
|
|
7520
|
+
}
|
|
7521
|
+
} else {
|
|
7522
|
+
state.activeConversationId = null;
|
|
7523
|
+
state.activeMessages = [];
|
|
7524
|
+
state.contextTokens = 0;
|
|
7525
|
+
state.contextWindow = 0;
|
|
7526
|
+
updateContextRing();
|
|
7527
|
+
elements.chatTitle.textContent = "";
|
|
7528
|
+
renderMessages([]);
|
|
7529
|
+
renderConversationList();
|
|
7530
|
+
}
|
|
7531
|
+
};
|
|
7532
|
+
|
|
7533
|
+
// ----- File explorer (sidebar Files mode) -----
|
|
7534
|
+
|
|
7535
|
+
const formatBytes = (n) => {
|
|
7536
|
+
if (typeof n !== "number" || !isFinite(n)) return "";
|
|
7537
|
+
if (n < 1024) return n + " B";
|
|
7538
|
+
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
|
|
7539
|
+
if (n < 1024 * 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + " MB";
|
|
7540
|
+
return (n / (1024 * 1024 * 1024)).toFixed(2) + " GB";
|
|
7541
|
+
};
|
|
7542
|
+
|
|
7543
|
+
const joinPath = (parent, name) => {
|
|
7544
|
+
if (parent === "/") return "/" + name;
|
|
7545
|
+
return parent + "/" + name;
|
|
7546
|
+
};
|
|
7547
|
+
|
|
7548
|
+
const parentPath = (p) => {
|
|
7549
|
+
if (!p || p === "/") return "/";
|
|
7550
|
+
const idx = p.lastIndexOf("/");
|
|
7551
|
+
if (idx <= 0) return "/";
|
|
7552
|
+
return p.slice(0, idx);
|
|
7553
|
+
};
|
|
7554
|
+
|
|
7555
|
+
const TEXT_LIKE_MIME_PREFIXES = ["text/"];
|
|
7556
|
+
const TEXT_LIKE_MIME_EXACT = new Set([
|
|
7557
|
+
"application/json",
|
|
7558
|
+
"application/javascript",
|
|
7559
|
+
"application/xml",
|
|
7560
|
+
"application/x-sh",
|
|
7561
|
+
"application/x-yaml",
|
|
7562
|
+
"application/yaml",
|
|
7563
|
+
"application/toml",
|
|
7564
|
+
"application/x-www-form-urlencoded",
|
|
7565
|
+
]);
|
|
7566
|
+
const TEXT_LIKE_EXTENSIONS = new Set([
|
|
7567
|
+
"md","txt","log","csv","tsv","js","mjs","cjs","jsx","ts","tsx","json","yaml","yml","toml",
|
|
7568
|
+
"xml","html","htm","css","scss","sass","less","sh","bash","zsh","py","rb","go","rs","java",
|
|
7569
|
+
"kt","swift","c","cpp","h","hpp","sql","env","ini","conf","cfg","gitignore","editorconfig",
|
|
7570
|
+
]);
|
|
7571
|
+
|
|
7572
|
+
const categorizePreview = (mime, name) => {
|
|
7573
|
+
const m = (mime || "").toLowerCase();
|
|
7574
|
+
const ext = (name.split(".").pop() || "").toLowerCase();
|
|
7575
|
+
if (m === "text/html" || ext === "html" || ext === "htm") return "html";
|
|
7576
|
+
if (m.startsWith("image/")) return "image";
|
|
7577
|
+
if (m === "application/pdf") return "pdf";
|
|
7578
|
+
if (m.startsWith("audio/")) return "audio";
|
|
7579
|
+
if (m.startsWith("video/")) return "video";
|
|
7580
|
+
for (const p of TEXT_LIKE_MIME_PREFIXES) if (m.startsWith(p)) return "text";
|
|
7581
|
+
if (TEXT_LIKE_MIME_EXACT.has(m)) return "text";
|
|
7582
|
+
if (TEXT_LIKE_EXTENSIONS.has(ext)) return "text";
|
|
7583
|
+
if (!m && (ext === "" || /^[a-z0-9]+$/i.test(ext))) return "text-maybe";
|
|
7584
|
+
return "binary";
|
|
7585
|
+
};
|
|
7586
|
+
|
|
7587
|
+
const TEXT_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
7588
|
+
|
|
7589
|
+
const folderIconSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1.5 4.5a1 1 0 0 1 1-1h3l1.5 1.5h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-10.5a1 1 0 0 1-1-1v-7.5z"/></svg>';
|
|
7590
|
+
const fileIconSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 1.5H3.5a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1V6L9 1.5z"/><path d="M9 1.5V6h4.5"/></svg>';
|
|
7591
|
+
const downloadIconSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v8M5 7l3 3 3-3M2.5 12.5v.5a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-.5"/></svg>';
|
|
7592
|
+
const refreshIconSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8a6 6 0 0 1 10.5-4M14 8a6 6 0 0 1-10.5 4"/><path d="M12.5 1.5v3h-3M3.5 14.5v-3h3"/></svg>';
|
|
7593
|
+
const closeIconSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
|
7594
|
+
const caretIconSvg = '<svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M4.5 2.75L8 6L4.5 9.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
|
|
7595
|
+
const uploadIconSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 11V3M5 6l3-3 3 3M2.5 12.5v.5a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-.5"/></svg>';
|
|
7596
|
+
const fetchDirEntries = async (dirPath) => {
|
|
7597
|
+
const qs = "?path=" + encodeURIComponent(dirPath);
|
|
7598
|
+
const data = await api("/api/vfs-list" + qs);
|
|
7599
|
+
state.dirCache.set(dirPath, data.entries || []);
|
|
7600
|
+
if (data.usage) state.fileExplorerUsage = data.usage;
|
|
7601
|
+
return data.entries || [];
|
|
7602
|
+
};
|
|
7603
|
+
|
|
7604
|
+
const ensureDirLoaded = async (dirPath) => {
|
|
7605
|
+
if (state.dirCache.has(dirPath)) return;
|
|
7606
|
+
try {
|
|
7607
|
+
await fetchDirEntries(dirPath);
|
|
7608
|
+
state.fileExplorerError = null;
|
|
7609
|
+
} catch (err) {
|
|
7610
|
+
state.fileExplorerError = err.message || "Failed to load directory";
|
|
7611
|
+
}
|
|
7612
|
+
};
|
|
7613
|
+
|
|
7614
|
+
const renderFileExplorer = () => {
|
|
7615
|
+
const root = elements.fileExplorer;
|
|
7616
|
+
if (!root) return;
|
|
7617
|
+
root.innerHTML = "";
|
|
7618
|
+
|
|
7619
|
+
// Pending uploads spinner row
|
|
7620
|
+
if (state.pendingUploads > 0) {
|
|
7621
|
+
const row = document.createElement("div");
|
|
7622
|
+
row.className = "file-upload-row";
|
|
7623
|
+
row.innerHTML = '<span class="file-upload-spinner"></span><span>Uploading ' + state.pendingUploads + (state.pendingUploads === 1 ? ' file\u2026' : ' files\u2026') + '</span>';
|
|
7624
|
+
root.appendChild(row);
|
|
7625
|
+
}
|
|
7626
|
+
|
|
7627
|
+
// Error row
|
|
7628
|
+
if (state.fileExplorerError) {
|
|
7629
|
+
const err = document.createElement("div");
|
|
7630
|
+
err.className = "file-explorer-error";
|
|
7631
|
+
err.textContent = state.fileExplorerError;
|
|
7632
|
+
const retry = document.createElement("button");
|
|
7633
|
+
retry.textContent = "Retry";
|
|
7634
|
+
retry.onclick = async () => {
|
|
7635
|
+
state.fileExplorerError = null;
|
|
7636
|
+
state.dirCache.clear();
|
|
7637
|
+
for (const dir of Array.from(state.expandedDirs)) {
|
|
7638
|
+
try { await fetchDirEntries(dir); } catch {}
|
|
7639
|
+
}
|
|
7640
|
+
renderFileExplorer();
|
|
7641
|
+
};
|
|
7642
|
+
err.appendChild(document.createElement("br"));
|
|
7643
|
+
err.appendChild(retry);
|
|
7644
|
+
root.appendChild(err);
|
|
7645
|
+
}
|
|
7646
|
+
|
|
7647
|
+
// Tree
|
|
7648
|
+
const tree = document.createElement("div");
|
|
7649
|
+
tree.className = "file-children";
|
|
7650
|
+
tree.dataset.dir = "/";
|
|
7651
|
+
renderDirInto(tree, "/", 0);
|
|
7652
|
+
root.appendChild(tree);
|
|
7653
|
+
|
|
7654
|
+
// Footer (status bar with usage + upload)
|
|
7655
|
+
const footer = document.createElement("div");
|
|
7656
|
+
footer.className = "file-explorer-footer";
|
|
7657
|
+
const usageEl = document.createElement("div");
|
|
7658
|
+
usageEl.className = "file-explorer-usage";
|
|
7659
|
+
if (state.fileExplorerUsage) {
|
|
7660
|
+
const u = state.fileExplorerUsage;
|
|
7661
|
+
const countEl = document.createElement("span");
|
|
7662
|
+
countEl.textContent = u.fileCount + " file" + (u.fileCount === 1 ? "" : "s");
|
|
7663
|
+
const sizeEl = document.createElement("span");
|
|
7664
|
+
sizeEl.textContent = formatBytes(u.totalBytes);
|
|
7665
|
+
usageEl.append(countEl, sizeEl);
|
|
7666
|
+
}
|
|
7667
|
+
const refreshBtn = document.createElement("button");
|
|
7668
|
+
refreshBtn.className = "file-explorer-icon-btn";
|
|
7669
|
+
refreshBtn.title = "Refresh";
|
|
7670
|
+
refreshBtn.innerHTML = refreshIconSvg;
|
|
7671
|
+
refreshBtn.onclick = async () => {
|
|
7672
|
+
state.dirCache.clear();
|
|
7673
|
+
for (const dir of Array.from(state.expandedDirs)) {
|
|
7674
|
+
try { await fetchDirEntries(dir); } catch {}
|
|
7675
|
+
}
|
|
7676
|
+
renderFileExplorer();
|
|
7677
|
+
};
|
|
7678
|
+
const uploadBtn = document.createElement("button");
|
|
7679
|
+
uploadBtn.className = "file-explorer-upload";
|
|
7680
|
+
uploadBtn.title = "Upload files";
|
|
7681
|
+
uploadBtn.innerHTML = uploadIconSvg + '<span>Upload</span>';
|
|
7682
|
+
uploadBtn.onclick = () => triggerUploadPicker("/");
|
|
7683
|
+
footer.append(usageEl, refreshBtn, uploadBtn);
|
|
7684
|
+
root.appendChild(footer);
|
|
7685
|
+
};
|
|
7686
|
+
|
|
7687
|
+
const renderDirInto = (container, dirPath, depth) => {
|
|
7688
|
+
const entries = state.dirCache.get(dirPath);
|
|
7689
|
+
if (!entries) {
|
|
7690
|
+
// Lazy fetch for newly expanded dir
|
|
7691
|
+
ensureDirLoaded(dirPath).then(() => renderFileExplorer());
|
|
7692
|
+
if (dirPath !== "/") {
|
|
7693
|
+
const loading = document.createElement("div");
|
|
7694
|
+
loading.className = "file-explorer-empty";
|
|
7695
|
+
loading.style.paddingLeft = (16 + depth * 14) + "px";
|
|
7696
|
+
loading.textContent = "Loading\u2026";
|
|
7697
|
+
container.appendChild(loading);
|
|
7698
|
+
}
|
|
7699
|
+
return;
|
|
7700
|
+
}
|
|
7701
|
+
if (entries.length === 0 && dirPath === "/") {
|
|
7702
|
+
const empty = document.createElement("div");
|
|
7703
|
+
empty.className = "file-explorer-empty";
|
|
7704
|
+
empty.innerHTML = "No files yet. Tools like " + _TK + "write_file" + _TK + " and " + _TK + "bash" + _TK + " will populate this.";
|
|
7705
|
+
container.appendChild(empty);
|
|
7706
|
+
return;
|
|
7707
|
+
}
|
|
7708
|
+
for (const entry of entries) {
|
|
7709
|
+
const childPath = joinPath(dirPath, entry.name);
|
|
7710
|
+
const row = document.createElement("div");
|
|
7711
|
+
row.className = "file-row" + (entry.type === "directory" ? " is-dir" : "") + (state.activeFilePath === childPath ? " active" : "");
|
|
7712
|
+
row.style.paddingLeft = (8 + depth * 14) + "px";
|
|
7713
|
+
row.dataset.path = childPath;
|
|
7714
|
+
row.dataset.type = entry.type;
|
|
7715
|
+
|
|
7716
|
+
const caret = document.createElement("span");
|
|
7717
|
+
caret.className = "file-caret" + (entry.type === "directory" ? "" : " empty") + (state.expandedDirs.has(childPath) ? " open" : "");
|
|
7718
|
+
caret.innerHTML = caretIconSvg;
|
|
7719
|
+
row.appendChild(caret);
|
|
7720
|
+
|
|
7721
|
+
const icon = document.createElement("span");
|
|
7722
|
+
icon.className = "file-icon";
|
|
7723
|
+
icon.innerHTML = entry.type === "directory" ? folderIconSvg : fileIconSvg;
|
|
7724
|
+
row.appendChild(icon);
|
|
7725
|
+
|
|
7726
|
+
const name = document.createElement("span");
|
|
7727
|
+
name.className = "file-name";
|
|
7728
|
+
name.textContent = entry.name;
|
|
7729
|
+
row.appendChild(name);
|
|
7730
|
+
|
|
7731
|
+
if (entry.type === "directory") {
|
|
7732
|
+
row.onclick = (e) => {
|
|
7733
|
+
e.stopPropagation();
|
|
7734
|
+
if (state.confirmDeletePath) { state.confirmDeletePath = null; renderFileExplorer(); return; }
|
|
7735
|
+
if (state.expandedDirs.has(childPath)) {
|
|
7736
|
+
state.expandedDirs.delete(childPath);
|
|
7737
|
+
} else {
|
|
7738
|
+
state.expandedDirs.add(childPath);
|
|
7739
|
+
}
|
|
7740
|
+
renderFileExplorer();
|
|
7741
|
+
};
|
|
7742
|
+
} else {
|
|
7743
|
+
row.onclick = (e) => {
|
|
7744
|
+
e.stopPropagation();
|
|
7745
|
+
if (state.confirmDeletePath) { state.confirmDeletePath = null; renderFileExplorer(); return; }
|
|
7746
|
+
selectFile(childPath);
|
|
7747
|
+
};
|
|
7748
|
+
}
|
|
7749
|
+
|
|
7750
|
+
const isConfirming = state.confirmDeletePath === childPath;
|
|
7751
|
+
const actions = document.createElement("div");
|
|
7752
|
+
actions.className = "file-row-actions" + (isConfirming ? " confirming" : "");
|
|
7753
|
+
|
|
7754
|
+
const dl = document.createElement("a");
|
|
7755
|
+
dl.className = "file-row-action";
|
|
7756
|
+
if (entry.type === "directory") {
|
|
7757
|
+
dl.href = "/api/vfs-archive?path=" + encodeURIComponent(childPath);
|
|
7758
|
+
dl.title = "Download as zip";
|
|
7759
|
+
dl.setAttribute("download", entry.name + ".zip");
|
|
7760
|
+
} else {
|
|
7761
|
+
dl.href = "/api/vfs/" + encodeURI(childPath.replace(/^\\//, ""));
|
|
7762
|
+
dl.title = "Download";
|
|
7763
|
+
dl.setAttribute("download", entry.name);
|
|
7764
|
+
}
|
|
7765
|
+
dl.innerHTML = downloadIconSvg;
|
|
7766
|
+
dl.onclick = (e) => { e.stopPropagation(); };
|
|
7767
|
+
actions.appendChild(dl);
|
|
7768
|
+
|
|
7769
|
+
const del = document.createElement("button");
|
|
7770
|
+
del.className = "file-row-action file-delete" + (isConfirming ? " confirming" : "");
|
|
7771
|
+
if (isConfirming) del.textContent = "sure?";
|
|
7772
|
+
else del.innerHTML = closeIconSvg;
|
|
7773
|
+
del.title = "Delete";
|
|
7774
|
+
del.onclick = async (e) => {
|
|
7775
|
+
e.stopPropagation();
|
|
7776
|
+
if (!isConfirming) {
|
|
7777
|
+
state.confirmDeletePath = childPath;
|
|
7778
|
+
renderFileExplorer();
|
|
7779
|
+
return;
|
|
7780
|
+
}
|
|
7781
|
+
state.confirmDeletePath = null;
|
|
7782
|
+
await deleteEntry(childPath, entry.type, dirPath);
|
|
7783
|
+
};
|
|
7784
|
+
actions.appendChild(del);
|
|
7785
|
+
row.appendChild(actions);
|
|
7786
|
+
|
|
7787
|
+
container.appendChild(row);
|
|
7788
|
+
|
|
7789
|
+
if (entry.type === "directory" && state.expandedDirs.has(childPath)) {
|
|
7790
|
+
const childWrap = document.createElement("div");
|
|
7791
|
+
childWrap.className = "file-children";
|
|
7792
|
+
childWrap.dataset.dir = childPath;
|
|
7793
|
+
renderDirInto(childWrap, childPath, depth + 1);
|
|
7794
|
+
container.appendChild(childWrap);
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
7797
|
+
};
|
|
7798
|
+
|
|
7799
|
+
const selectFile = async (filePath) => {
|
|
7800
|
+
state.activeFilePath = filePath;
|
|
7801
|
+
state.activeConversationId = null;
|
|
7802
|
+
pushFileUrl(filePath);
|
|
7803
|
+
updateComposerVisibility();
|
|
7804
|
+
renderFileExplorer();
|
|
7805
|
+
await renderFilePreview(filePath);
|
|
7806
|
+
};
|
|
7807
|
+
|
|
7808
|
+
const renderFilePreview = async (filePath) => {
|
|
7809
|
+
const messages = elements.messages;
|
|
7810
|
+
if (!messages) return;
|
|
7811
|
+
const filename = filePath.split("/").pop() || filePath;
|
|
7812
|
+
elements.chatTitle.textContent = filename;
|
|
7813
|
+
messages.innerHTML = '<div class="file-preview"><div class="file-explorer-empty">Loading\u2026</div></div>';
|
|
7814
|
+
let response;
|
|
7815
|
+
try {
|
|
7816
|
+
response = await fetch("/api/vfs/" + encodeURI(filePath.replace(/^\\//, "")), {
|
|
7817
|
+
credentials: state.tenantToken ? "omit" : "include",
|
|
7818
|
+
headers: buildAuthHeaders(),
|
|
7819
|
+
});
|
|
7820
|
+
} catch (err) {
|
|
7821
|
+
renderPreviewError(filePath, "Network error");
|
|
7822
|
+
return;
|
|
7823
|
+
}
|
|
7824
|
+
if (!response.ok) {
|
|
7825
|
+
renderPreviewError(filePath, "HTTP " + response.status);
|
|
7826
|
+
return;
|
|
7827
|
+
}
|
|
7828
|
+
const mime = (response.headers.get("content-type") || "").split(";")[0].trim();
|
|
7829
|
+
const sizeHeader = response.headers.get("content-length");
|
|
7830
|
+
const size = sizeHeader ? parseInt(sizeHeader, 10) : 0;
|
|
7831
|
+
const category = categorizePreview(mime, filename);
|
|
7832
|
+
|
|
7833
|
+
if (category === "text" || category === "text-maybe") {
|
|
7834
|
+
if (size && size > TEXT_PREVIEW_MAX_BYTES) {
|
|
7835
|
+
renderPreviewPlaceholder(filePath, mime, size, "Text file too large to preview inline.");
|
|
7836
|
+
return;
|
|
7837
|
+
}
|
|
7838
|
+
const buf = await response.arrayBuffer();
|
|
7839
|
+
if (buf.byteLength > TEXT_PREVIEW_MAX_BYTES) {
|
|
7840
|
+
renderPreviewPlaceholder(filePath, mime, buf.byteLength, "Text file too large to preview inline.");
|
|
7841
|
+
return;
|
|
7842
|
+
}
|
|
7843
|
+
let text;
|
|
7844
|
+
try {
|
|
7845
|
+
text = new TextDecoder("utf-8", { fatal: category === "text-maybe" }).decode(buf);
|
|
7846
|
+
} catch {
|
|
7847
|
+
renderPreviewPlaceholder(filePath, mime, buf.byteLength, "Not a text file.");
|
|
7848
|
+
return;
|
|
7849
|
+
}
|
|
7850
|
+
renderTextView(filePath, text, mime || "text/plain");
|
|
7851
|
+
return;
|
|
7852
|
+
}
|
|
7853
|
+
if (category === "image") {
|
|
7854
|
+
const blob = await response.blob();
|
|
7855
|
+
const url = URL.createObjectURL(blob);
|
|
7856
|
+
const wrap = document.createElement("div");
|
|
7857
|
+
wrap.className = "file-preview";
|
|
7858
|
+
const inner = document.createElement("div");
|
|
7859
|
+
inner.className = "file-preview-image";
|
|
7860
|
+
const img = document.createElement("img");
|
|
7861
|
+
img.src = url;
|
|
7862
|
+
img.alt = filename;
|
|
7863
|
+
img.onload = () => URL.revokeObjectURL(url);
|
|
7864
|
+
inner.appendChild(img);
|
|
7865
|
+
wrap.append(buildPreviewActions(filePath), inner);
|
|
7866
|
+
messages.innerHTML = "";
|
|
7867
|
+
messages.appendChild(wrap);
|
|
7868
|
+
return;
|
|
7869
|
+
}
|
|
7870
|
+
if (category === "pdf") {
|
|
7871
|
+
const blob = await response.blob();
|
|
7872
|
+
const url = URL.createObjectURL(blob);
|
|
7873
|
+
const wrap = document.createElement("div");
|
|
7874
|
+
wrap.className = "file-preview";
|
|
7875
|
+
const inner = document.createElement("div");
|
|
7876
|
+
inner.className = "file-preview-pdf";
|
|
7877
|
+
const iframe = document.createElement("iframe");
|
|
7878
|
+
iframe.src = url;
|
|
7879
|
+
iframe.title = filename;
|
|
7880
|
+
inner.appendChild(iframe);
|
|
7881
|
+
wrap.append(buildPreviewActions(filePath), inner);
|
|
7882
|
+
messages.innerHTML = "";
|
|
7883
|
+
messages.appendChild(wrap);
|
|
7884
|
+
return;
|
|
7885
|
+
}
|
|
7886
|
+
if (category === "html") {
|
|
7887
|
+
const blob = await response.blob();
|
|
7888
|
+
const url = URL.createObjectURL(blob);
|
|
7889
|
+
const wrap = document.createElement("div");
|
|
7890
|
+
wrap.className = "file-preview";
|
|
7891
|
+
const inner = document.createElement("div");
|
|
7892
|
+
inner.className = "file-preview-pdf";
|
|
7893
|
+
const iframe = document.createElement("iframe");
|
|
7894
|
+
iframe.src = url;
|
|
7895
|
+
iframe.title = filename;
|
|
7896
|
+
iframe.setAttribute("sandbox", "");
|
|
7897
|
+
inner.appendChild(iframe);
|
|
7898
|
+
wrap.append(buildPreviewActions(filePath), inner);
|
|
7899
|
+
messages.innerHTML = "";
|
|
7900
|
+
messages.appendChild(wrap);
|
|
7901
|
+
return;
|
|
7902
|
+
}
|
|
7903
|
+
if (category === "audio" || category === "video") {
|
|
7904
|
+
const blob = await response.blob();
|
|
7905
|
+
const url = URL.createObjectURL(blob);
|
|
7906
|
+
const wrap = document.createElement("div");
|
|
7907
|
+
wrap.className = "file-preview";
|
|
7908
|
+
const inner = document.createElement("div");
|
|
7909
|
+
inner.className = "file-preview-media";
|
|
7910
|
+
const media = document.createElement(category);
|
|
7911
|
+
media.src = url;
|
|
7912
|
+
media.controls = true;
|
|
7913
|
+
media.style.maxWidth = "100%";
|
|
7914
|
+
media.style.maxHeight = "100%";
|
|
7915
|
+
inner.appendChild(media);
|
|
7916
|
+
wrap.append(buildPreviewActions(filePath), inner);
|
|
7917
|
+
messages.innerHTML = "";
|
|
7918
|
+
messages.appendChild(wrap);
|
|
7919
|
+
return;
|
|
7920
|
+
}
|
|
7921
|
+
renderPreviewPlaceholder(filePath, mime, size, "This file type can't be previewed.");
|
|
7922
|
+
};
|
|
7923
|
+
|
|
7924
|
+
const buildDownloadLink = (filePath) => {
|
|
7925
|
+
const filename = filePath.split("/").pop() || "download";
|
|
7926
|
+
const a = document.createElement("a");
|
|
7927
|
+
a.className = "file-preview-action-btn";
|
|
7928
|
+
a.href = "/api/vfs/" + encodeURI(filePath.replace(/^\\//, ""));
|
|
7929
|
+
a.textContent = "Download";
|
|
7930
|
+
a.setAttribute("download", filename);
|
|
7931
|
+
return a;
|
|
7932
|
+
};
|
|
7933
|
+
|
|
7934
|
+
const buildPreviewActions = (filePath, leftExtras = [], rightExtras = []) => {
|
|
7935
|
+
const actions = document.createElement("div");
|
|
7936
|
+
actions.className = "file-preview-actions";
|
|
7937
|
+
const left = document.createElement("div");
|
|
7938
|
+
left.className = "file-preview-actions-group";
|
|
7939
|
+
left.appendChild(buildDownloadLink(filePath));
|
|
7940
|
+
left.appendChild(buildCopyLinkButton(filePath));
|
|
7941
|
+
for (const el of leftExtras) left.appendChild(el);
|
|
7942
|
+
const right = document.createElement("div");
|
|
7943
|
+
right.className = "file-preview-actions-group";
|
|
7944
|
+
for (const el of rightExtras) right.appendChild(el);
|
|
7945
|
+
actions.append(left, right);
|
|
7946
|
+
return actions;
|
|
7947
|
+
};
|
|
7948
|
+
|
|
7949
|
+
const csvDelimiter = (mime, name) => {
|
|
7950
|
+
const m = (mime || "").toLowerCase();
|
|
7951
|
+
const ext = (name.split(".").pop() || "").toLowerCase();
|
|
7952
|
+
if (m === "text/csv" || ext === "csv") return ",";
|
|
7953
|
+
if (m === "text/tab-separated-values" || ext === "tsv") return "\\t";
|
|
7954
|
+
return null;
|
|
7955
|
+
};
|
|
7956
|
+
|
|
7957
|
+
const parseDelimited = (text, delimiter) => {
|
|
7958
|
+
const rows = [];
|
|
7959
|
+
let row = [];
|
|
7960
|
+
let field = "";
|
|
7961
|
+
let inQuotes = false;
|
|
7962
|
+
for (let i = 0; i < text.length; i++) {
|
|
7963
|
+
const c = text[i];
|
|
7964
|
+
if (inQuotes) {
|
|
7965
|
+
if (c === '"') {
|
|
7966
|
+
if (text[i + 1] === '"') { field += '"'; i++; }
|
|
7967
|
+
else inQuotes = false;
|
|
7968
|
+
} else {
|
|
7969
|
+
field += c;
|
|
7970
|
+
}
|
|
7971
|
+
continue;
|
|
7972
|
+
}
|
|
7973
|
+
if (c === '"') { inQuotes = true; continue; }
|
|
7974
|
+
if (c === delimiter) { row.push(field); field = ""; continue; }
|
|
7975
|
+
if (c === "\\n" || c === "\\r") {
|
|
7976
|
+
if (c === "\\r" && text[i + 1] === "\\n") i++;
|
|
7977
|
+
row.push(field);
|
|
7978
|
+
field = "";
|
|
7979
|
+
rows.push(row);
|
|
7980
|
+
row = [];
|
|
7981
|
+
continue;
|
|
7982
|
+
}
|
|
7983
|
+
field += c;
|
|
7984
|
+
}
|
|
7985
|
+
if (field.length > 0 || row.length > 0) {
|
|
7986
|
+
row.push(field);
|
|
7987
|
+
rows.push(row);
|
|
7988
|
+
}
|
|
7989
|
+
return rows;
|
|
7990
|
+
};
|
|
7991
|
+
|
|
7992
|
+
const TABLE_PREVIEW_MAX_ROWS = 5000;
|
|
7993
|
+
|
|
7994
|
+
const isMarkdownFile = (mime, name) => {
|
|
7995
|
+
const m = (mime || "").toLowerCase();
|
|
7996
|
+
if (m.startsWith("text/markdown") || m === "text/x-markdown") return true;
|
|
7997
|
+
const ext = (name.split(".").pop() || "").toLowerCase();
|
|
7998
|
+
return ext === "md" || ext === "markdown" || ext === "mdx";
|
|
7999
|
+
};
|
|
8000
|
+
|
|
8001
|
+
const flashLabel = (btn, label, durationMs) => {
|
|
8002
|
+
const original = btn.dataset.label || btn.textContent;
|
|
8003
|
+
btn.dataset.label = original;
|
|
8004
|
+
btn.textContent = label;
|
|
8005
|
+
if (btn._flashTimer) clearTimeout(btn._flashTimer);
|
|
8006
|
+
btn._flashTimer = setTimeout(() => { btn.textContent = original; }, durationMs);
|
|
8007
|
+
};
|
|
8008
|
+
|
|
8009
|
+
const buildCopyTextButton = (text) => {
|
|
8010
|
+
const btn = document.createElement("button");
|
|
8011
|
+
btn.className = "file-preview-action-btn";
|
|
8012
|
+
btn.textContent = "Copy";
|
|
8013
|
+
btn.onclick = async () => {
|
|
8014
|
+
try {
|
|
8015
|
+
await navigator.clipboard.writeText(text);
|
|
8016
|
+
flashLabel(btn, "Copied", 1500);
|
|
8017
|
+
} catch (err) {
|
|
8018
|
+
window.alert("Failed to copy: " + (err.message || "clipboard unavailable"));
|
|
8019
|
+
}
|
|
8020
|
+
};
|
|
8021
|
+
return btn;
|
|
8022
|
+
};
|
|
8023
|
+
|
|
8024
|
+
const buildCopyLinkButton = (filePath) => {
|
|
8025
|
+
const btn = document.createElement("button");
|
|
8026
|
+
btn.className = "file-preview-action-btn";
|
|
8027
|
+
btn.textContent = "Copy link";
|
|
8028
|
+
btn.onclick = async () => {
|
|
8029
|
+
const url = window.location.origin + "/api/vfs/" + encodeURI(filePath.replace(/^\\//, ""));
|
|
8030
|
+
try {
|
|
8031
|
+
await navigator.clipboard.writeText(url);
|
|
8032
|
+
flashLabel(btn, "Copied", 1500);
|
|
8033
|
+
} catch (err) {
|
|
8034
|
+
window.alert("Failed to copy: " + (err.message || "clipboard unavailable"));
|
|
8035
|
+
}
|
|
8036
|
+
};
|
|
8037
|
+
return btn;
|
|
8038
|
+
};
|
|
8039
|
+
|
|
8040
|
+
const renderTextView = (filePath, text, mime) => {
|
|
8041
|
+
const messages = elements.messages;
|
|
8042
|
+
const filename = filePath.split("/").pop() || filePath;
|
|
8043
|
+
const wrap = document.createElement("div");
|
|
8044
|
+
wrap.className = "file-preview";
|
|
8045
|
+
const copyBtn = buildCopyTextButton(text);
|
|
8046
|
+
const editBtn = document.createElement("button");
|
|
8047
|
+
editBtn.className = "file-preview-action-btn";
|
|
8048
|
+
editBtn.textContent = "Edit";
|
|
8049
|
+
editBtn.onclick = () => renderTextEditor(filePath, text, mime);
|
|
8050
|
+
|
|
8051
|
+
let body;
|
|
8052
|
+
const delimiter = csvDelimiter(mime, filename);
|
|
8053
|
+
if (delimiter) {
|
|
8054
|
+
body = document.createElement("div");
|
|
8055
|
+
body.className = "file-preview-table-wrap";
|
|
8056
|
+
const rows = parseDelimited(text, delimiter);
|
|
8057
|
+
if (rows.length === 0) {
|
|
8058
|
+
const empty = document.createElement("div");
|
|
8059
|
+
empty.className = "file-explorer-empty";
|
|
8060
|
+
empty.textContent = "Empty file";
|
|
8061
|
+
body.appendChild(empty);
|
|
8062
|
+
} else {
|
|
8063
|
+
const truncated = rows.length > TABLE_PREVIEW_MAX_ROWS;
|
|
8064
|
+
const visibleRows = truncated ? rows.slice(0, TABLE_PREVIEW_MAX_ROWS) : rows;
|
|
8065
|
+
const table = document.createElement("table");
|
|
8066
|
+
table.className = "file-preview-table";
|
|
8067
|
+
const thead = document.createElement("thead");
|
|
8068
|
+
const headTr = document.createElement("tr");
|
|
8069
|
+
for (const cell of visibleRows[0]) {
|
|
8070
|
+
const th = document.createElement("th");
|
|
8071
|
+
th.textContent = cell;
|
|
8072
|
+
headTr.appendChild(th);
|
|
8073
|
+
}
|
|
8074
|
+
thead.appendChild(headTr);
|
|
8075
|
+
table.appendChild(thead);
|
|
8076
|
+
const tbody = document.createElement("tbody");
|
|
8077
|
+
for (let r = 1; r < visibleRows.length; r++) {
|
|
8078
|
+
const tr = document.createElement("tr");
|
|
8079
|
+
for (const cell of visibleRows[r]) {
|
|
8080
|
+
const td = document.createElement("td");
|
|
8081
|
+
td.textContent = cell;
|
|
8082
|
+
tr.appendChild(td);
|
|
8083
|
+
}
|
|
8084
|
+
tbody.appendChild(tr);
|
|
8085
|
+
}
|
|
8086
|
+
table.appendChild(tbody);
|
|
8087
|
+
body.appendChild(table);
|
|
8088
|
+
if (truncated) {
|
|
8089
|
+
const note = document.createElement("div");
|
|
8090
|
+
note.className = "file-preview-table-truncated";
|
|
8091
|
+
note.textContent = "Showing first " + TABLE_PREVIEW_MAX_ROWS + " of " + rows.length + " rows. Click Edit to see the raw file.";
|
|
8092
|
+
body.appendChild(note);
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
} else if (isMarkdownFile(mime, filename)) {
|
|
8096
|
+
body = document.createElement("div");
|
|
8097
|
+
body.className = "file-preview-markdown";
|
|
8098
|
+
const inner = document.createElement("div");
|
|
8099
|
+
inner.className = "assistant-content";
|
|
8100
|
+
inner.innerHTML = renderAssistantMarkdown(text);
|
|
8101
|
+
body.appendChild(inner);
|
|
8102
|
+
} else {
|
|
8103
|
+
body = document.createElement("pre");
|
|
8104
|
+
body.className = "file-preview-text";
|
|
8105
|
+
body.textContent = text;
|
|
8106
|
+
}
|
|
8107
|
+
wrap.append(buildPreviewActions(filePath, [copyBtn], [editBtn]), body);
|
|
8108
|
+
messages.innerHTML = "";
|
|
8109
|
+
messages.appendChild(wrap);
|
|
8110
|
+
};
|
|
8111
|
+
|
|
8112
|
+
const renderTextEditor = (filePath, originalText, mime) => {
|
|
8113
|
+
const messages = elements.messages;
|
|
8114
|
+
const wrap = document.createElement("div");
|
|
8115
|
+
wrap.className = "file-preview";
|
|
8116
|
+
const actions = document.createElement("div");
|
|
8117
|
+
actions.className = "file-preview-actions";
|
|
8118
|
+
const cancelBtn = document.createElement("button");
|
|
8119
|
+
cancelBtn.className = "file-preview-action-btn";
|
|
8120
|
+
cancelBtn.textContent = "Cancel";
|
|
8121
|
+
const saveBtn = document.createElement("button");
|
|
8122
|
+
saveBtn.className = "file-preview-action-btn primary";
|
|
8123
|
+
saveBtn.textContent = "Save";
|
|
8124
|
+
saveBtn.disabled = true;
|
|
8125
|
+
const leftGroup = document.createElement("div");
|
|
8126
|
+
leftGroup.className = "file-preview-actions-group";
|
|
8127
|
+
const rightGroup = document.createElement("div");
|
|
8128
|
+
rightGroup.className = "file-preview-actions-group";
|
|
8129
|
+
rightGroup.append(cancelBtn, saveBtn);
|
|
8130
|
+
actions.append(leftGroup, rightGroup);
|
|
8131
|
+
const textarea = document.createElement("textarea");
|
|
8132
|
+
textarea.className = "file-edit-textarea";
|
|
8133
|
+
textarea.value = originalText;
|
|
8134
|
+
textarea.spellcheck = false;
|
|
8135
|
+
textarea.oninput = () => { saveBtn.disabled = textarea.value === originalText; };
|
|
8136
|
+
cancelBtn.onclick = () => renderTextView(filePath, originalText, mime);
|
|
8137
|
+
saveBtn.onclick = async () => {
|
|
8138
|
+
const newText = textarea.value;
|
|
8139
|
+
saveBtn.disabled = true;
|
|
8140
|
+
saveBtn.textContent = "Saving\u2026";
|
|
8141
|
+
cancelBtn.disabled = true;
|
|
8142
|
+
try {
|
|
8143
|
+
const url = "/api/vfs/" + encodeURI(filePath.replace(/^\\//, "")) + "?overwrite=1";
|
|
8144
|
+
const response = await fetch(url, {
|
|
8145
|
+
method: "PUT",
|
|
8146
|
+
credentials: state.tenantToken ? "omit" : "include",
|
|
8147
|
+
headers: { ...buildAuthHeaders(), "Content-Type": mime || "text/plain" },
|
|
8148
|
+
body: newText,
|
|
8149
|
+
});
|
|
8150
|
+
if (!response.ok) {
|
|
8151
|
+
let msg = "Save failed (" + response.status + ")";
|
|
8152
|
+
try { const p = await response.json(); if (p && p.message) msg = p.message; } catch {}
|
|
8153
|
+
throw new Error(msg);
|
|
8154
|
+
}
|
|
8155
|
+
} catch (err) {
|
|
8156
|
+
saveBtn.textContent = "Save";
|
|
8157
|
+
saveBtn.disabled = false;
|
|
8158
|
+
cancelBtn.disabled = false;
|
|
8159
|
+
window.alert("Failed to save: " + (err.message || "unknown error"));
|
|
8160
|
+
return;
|
|
8161
|
+
}
|
|
8162
|
+
// Invalidate parent dir cache so file size/mtime refresh on next view
|
|
8163
|
+
state.dirCache.delete(parentPath(filePath));
|
|
8164
|
+
renderTextView(filePath, newText, mime);
|
|
8165
|
+
};
|
|
8166
|
+
wrap.append(actions, textarea);
|
|
8167
|
+
messages.innerHTML = "";
|
|
8168
|
+
messages.appendChild(wrap);
|
|
8169
|
+
textarea.focus();
|
|
8170
|
+
};
|
|
8171
|
+
|
|
8172
|
+
const renderPreviewPlaceholder = (filePath, mime, size, reason) => {
|
|
8173
|
+
const messages = elements.messages;
|
|
8174
|
+
const filename = filePath.split("/").pop() || filePath;
|
|
8175
|
+
const downloadUrl = "/api/vfs/" + encodeURI(filePath.replace(/^\\//, ""));
|
|
8176
|
+
const wrap = document.createElement("div");
|
|
8177
|
+
wrap.className = "file-preview";
|
|
8178
|
+
const inner = document.createElement("div");
|
|
8179
|
+
inner.className = "file-preview-placeholder";
|
|
8180
|
+
const card = document.createElement("div");
|
|
8181
|
+
const nameEl = document.createElement("div");
|
|
8182
|
+
nameEl.className = "file-preview-name";
|
|
8183
|
+
nameEl.textContent = filename;
|
|
8184
|
+
const metaEl = document.createElement("div");
|
|
8185
|
+
metaEl.className = "file-preview-meta";
|
|
8186
|
+
const metaParts = [];
|
|
8187
|
+
if (mime) metaParts.push(mime);
|
|
8188
|
+
if (size) metaParts.push(formatBytes(size));
|
|
8189
|
+
metaEl.textContent = (reason ? reason + " \xB7 " : "") + metaParts.join(" \xB7 ");
|
|
8190
|
+
const a = document.createElement("a");
|
|
8191
|
+
a.className = "file-preview-download";
|
|
8192
|
+
a.href = downloadUrl;
|
|
8193
|
+
a.textContent = "Download";
|
|
8194
|
+
a.setAttribute("download", filename);
|
|
8195
|
+
card.append(nameEl, metaEl, a);
|
|
8196
|
+
inner.appendChild(card);
|
|
8197
|
+
wrap.appendChild(inner);
|
|
8198
|
+
messages.innerHTML = "";
|
|
8199
|
+
messages.appendChild(wrap);
|
|
8200
|
+
};
|
|
8201
|
+
|
|
8202
|
+
const renderPreviewError = (filePath, message) => {
|
|
8203
|
+
const messages = elements.messages;
|
|
8204
|
+
const filename = filePath.split("/").pop() || filePath;
|
|
8205
|
+
const wrap = document.createElement("div");
|
|
8206
|
+
wrap.className = "file-preview";
|
|
8207
|
+
const inner = document.createElement("div");
|
|
8208
|
+
inner.className = "file-preview-placeholder";
|
|
8209
|
+
const card = document.createElement("div");
|
|
8210
|
+
card.innerHTML = '<div class="file-preview-name">' + escapeHtml(filename) + '</div><div class="file-preview-meta">Failed to load file (' + escapeHtml(message) + ')</div>';
|
|
8211
|
+
inner.appendChild(card);
|
|
8212
|
+
wrap.appendChild(inner);
|
|
8213
|
+
messages.innerHTML = "";
|
|
8214
|
+
messages.appendChild(wrap);
|
|
8215
|
+
};
|
|
8216
|
+
|
|
8217
|
+
const updateComposerVisibility = () => {
|
|
8218
|
+
if (!elements.composer) return;
|
|
8219
|
+
if (state.activeFilePath) elements.composer.classList.add("hidden");
|
|
8220
|
+
else elements.composer.classList.remove("hidden");
|
|
8221
|
+
};
|
|
8222
|
+
|
|
8223
|
+
const deleteEntry = async (path, type, parentDir) => {
|
|
8224
|
+
try {
|
|
8225
|
+
const url = "/api/vfs/" + encodeURI(path.replace(/^\\//, ""));
|
|
8226
|
+
const response = await fetch(url, {
|
|
8227
|
+
method: "DELETE",
|
|
8228
|
+
credentials: state.tenantToken ? "omit" : "include",
|
|
8229
|
+
headers: buildAuthHeaders(),
|
|
8230
|
+
});
|
|
8231
|
+
if (!response.ok) {
|
|
8232
|
+
let msg = "Delete failed (" + response.status + ")";
|
|
8233
|
+
try { const p = await response.json(); if (p && p.message) msg = p.message; } catch {}
|
|
8234
|
+
throw new Error(msg);
|
|
8235
|
+
}
|
|
8236
|
+
} catch (err) {
|
|
8237
|
+
window.alert("Failed to delete: " + (err.message || "unknown error"));
|
|
8238
|
+
renderFileExplorer();
|
|
8239
|
+
return;
|
|
8240
|
+
}
|
|
8241
|
+
// Drop cache for parent and any descendants we cached
|
|
8242
|
+
state.dirCache.delete(parentDir);
|
|
8243
|
+
if (type === "directory") {
|
|
8244
|
+
const prefix = path === "/" ? "/" : path + "/";
|
|
8245
|
+
for (const k of Array.from(state.dirCache.keys())) {
|
|
8246
|
+
if (k === path || k.startsWith(prefix)) state.dirCache.delete(k);
|
|
8247
|
+
}
|
|
8248
|
+
for (const d of Array.from(state.expandedDirs)) {
|
|
8249
|
+
if (d === path || d.startsWith(prefix)) state.expandedDirs.delete(d);
|
|
8250
|
+
}
|
|
6880
8251
|
}
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
8252
|
+
if (state.activeFilePath === path || (type === "directory" && state.activeFilePath && state.activeFilePath.startsWith(path + "/"))) {
|
|
8253
|
+
state.activeFilePath = null;
|
|
8254
|
+
elements.chatTitle.textContent = "";
|
|
8255
|
+
elements.messages.innerHTML = "";
|
|
8256
|
+
updateComposerVisibility();
|
|
8257
|
+
replaceFileUrl(null);
|
|
6884
8258
|
}
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
8259
|
+
try { await fetchDirEntries(parentDir); } catch {}
|
|
8260
|
+
renderFileExplorer();
|
|
8261
|
+
};
|
|
8262
|
+
|
|
8263
|
+
const switchSidebarMode = (mode) => {
|
|
8264
|
+
if (mode !== "conversations" && mode !== "files") return;
|
|
8265
|
+
state.sidebarMode = mode;
|
|
8266
|
+
const buttons = document.querySelectorAll(".sidebar-segmented .seg-btn");
|
|
8267
|
+
buttons.forEach((b) => {
|
|
8268
|
+
if (b.dataset.mode === mode) b.classList.add("active");
|
|
8269
|
+
else b.classList.remove("active");
|
|
8270
|
+
});
|
|
8271
|
+
const list = elements.list;
|
|
8272
|
+
const explorer = elements.fileExplorer;
|
|
8273
|
+
if (mode === "files") {
|
|
8274
|
+
list.classList.add("hidden");
|
|
8275
|
+
explorer.classList.remove("hidden");
|
|
8276
|
+
if (!state.dirCache.has("/")) {
|
|
8277
|
+
ensureDirLoaded("/").then(() => renderFileExplorer());
|
|
6893
8278
|
}
|
|
8279
|
+
renderFileExplorer();
|
|
8280
|
+
} else {
|
|
8281
|
+
explorer.classList.add("hidden");
|
|
8282
|
+
list.classList.remove("hidden");
|
|
6894
8283
|
}
|
|
6895
|
-
}
|
|
8284
|
+
};
|
|
6896
8285
|
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
}
|
|
6908
|
-
|
|
8286
|
+
// ----- Uploads + mkdir + drag-drop -----
|
|
8287
|
+
|
|
8288
|
+
let _hiddenUploadInput = null;
|
|
8289
|
+
const triggerUploadPicker = (targetDir) => {
|
|
8290
|
+
if (!_hiddenUploadInput) {
|
|
8291
|
+
_hiddenUploadInput = document.createElement("input");
|
|
8292
|
+
_hiddenUploadInput.type = "file";
|
|
8293
|
+
_hiddenUploadInput.multiple = true;
|
|
8294
|
+
_hiddenUploadInput.style.display = "none";
|
|
8295
|
+
document.body.appendChild(_hiddenUploadInput);
|
|
8296
|
+
}
|
|
8297
|
+
_hiddenUploadInput.value = "";
|
|
8298
|
+
_hiddenUploadInput.onchange = () => {
|
|
8299
|
+
const files = Array.from(_hiddenUploadInput.files || []);
|
|
8300
|
+
if (files.length > 0) uploadFiles(files, targetDir);
|
|
8301
|
+
};
|
|
8302
|
+
_hiddenUploadInput.click();
|
|
8303
|
+
};
|
|
6909
8304
|
|
|
6910
|
-
|
|
6911
|
-
state.isMessagesPinnedToBottom = isNearBottom(elements.messages);
|
|
6912
|
-
}, { passive: true });
|
|
8305
|
+
const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
6913
8306
|
|
|
6914
|
-
|
|
6915
|
-
if (
|
|
6916
|
-
|
|
8307
|
+
const uploadOne = async (file, targetDir, overwrite) => {
|
|
8308
|
+
if (file.size > MAX_UPLOAD_BYTES) {
|
|
8309
|
+
throw new Error("File too large (limit " + formatBytes(MAX_UPLOAD_BYTES) + ")");
|
|
6917
8310
|
}
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
8311
|
+
const targetPath = joinPath(targetDir, file.name);
|
|
8312
|
+
const url = "/api/vfs/" + encodeURI(targetPath.replace(/^\\//, "")) + (overwrite ? "?overwrite=1" : "");
|
|
8313
|
+
const response = await fetch(url, {
|
|
8314
|
+
method: "PUT",
|
|
8315
|
+
credentials: state.tenantToken ? "omit" : "include",
|
|
8316
|
+
headers: { ...buildAuthHeaders(), "Content-Type": file.type || "application/octet-stream" },
|
|
8317
|
+
body: file,
|
|
8318
|
+
});
|
|
8319
|
+
if (response.status === 409) {
|
|
8320
|
+
const ok = window.confirm("A file named \\"" + file.name + "\\" already exists in " + targetDir + ". Overwrite?");
|
|
8321
|
+
if (!ok) return;
|
|
8322
|
+
return uploadOne(file, targetDir, true);
|
|
6921
8323
|
}
|
|
6922
|
-
if (!
|
|
6923
|
-
|
|
6924
|
-
|
|
8324
|
+
if (!response.ok) {
|
|
8325
|
+
let msg = "Upload failed (" + response.status + ")";
|
|
8326
|
+
try { const p = await response.json(); if (p && p.message) msg = p.message; } catch {}
|
|
8327
|
+
throw new Error(msg);
|
|
6925
8328
|
}
|
|
6926
|
-
}
|
|
6927
|
-
|
|
6928
|
-
window.addEventListener("resize", () => {
|
|
6929
|
-
setSidebarOpen(false);
|
|
6930
|
-
});
|
|
8329
|
+
};
|
|
6931
8330
|
|
|
6932
|
-
const
|
|
6933
|
-
|
|
6934
|
-
state.
|
|
6935
|
-
|
|
8331
|
+
const uploadFiles = async (files, targetDir) => {
|
|
8332
|
+
for (const file of files) {
|
|
8333
|
+
state.pendingUploads += 1;
|
|
8334
|
+
renderFileExplorer();
|
|
6936
8335
|
try {
|
|
6937
|
-
await
|
|
6938
|
-
} catch {
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
state.
|
|
6942
|
-
replaceConversationUrl(null);
|
|
6943
|
-
elements.chatTitle.textContent = "";
|
|
6944
|
-
renderMessages([]);
|
|
6945
|
-
renderConversationList();
|
|
8336
|
+
await uploadOne(file, targetDir, false);
|
|
8337
|
+
} catch (err) {
|
|
8338
|
+
window.alert("Failed to upload " + file.name + ": " + (err.message || "unknown error"));
|
|
8339
|
+
} finally {
|
|
8340
|
+
state.pendingUploads -= 1;
|
|
6946
8341
|
}
|
|
6947
|
-
} else {
|
|
6948
|
-
state.activeConversationId = null;
|
|
6949
|
-
state.activeMessages = [];
|
|
6950
|
-
state.contextTokens = 0;
|
|
6951
|
-
state.contextWindow = 0;
|
|
6952
|
-
updateContextRing();
|
|
6953
|
-
elements.chatTitle.textContent = "";
|
|
6954
|
-
renderMessages([]);
|
|
6955
|
-
renderConversationList();
|
|
6956
8342
|
}
|
|
8343
|
+
state.dirCache.delete(targetDir);
|
|
8344
|
+
try { await fetchDirEntries(targetDir); } catch {}
|
|
8345
|
+
state.expandedDirs.add(targetDir);
|
|
8346
|
+
renderFileExplorer();
|
|
8347
|
+
};
|
|
8348
|
+
|
|
8349
|
+
let _dropTargetEl = null;
|
|
8350
|
+
const _setDropTarget = (el) => {
|
|
8351
|
+
if (_dropTargetEl === el) return;
|
|
8352
|
+
if (_dropTargetEl) _dropTargetEl.classList.remove("drop-target");
|
|
8353
|
+
_dropTargetEl = el;
|
|
8354
|
+
if (_dropTargetEl) _dropTargetEl.classList.add("drop-target");
|
|
8355
|
+
};
|
|
8356
|
+
const _resolveDropTarget = (eventTarget) => {
|
|
8357
|
+
if (!(eventTarget instanceof Element)) return null;
|
|
8358
|
+
// A folder header row takes precedence \u2014 drop INTO that folder.
|
|
8359
|
+
const folderRow = eventTarget.closest(".file-row.is-dir");
|
|
8360
|
+
if (folderRow && elements.fileExplorer.contains(folderRow)) return folderRow;
|
|
8361
|
+
// Otherwise the deepest .file-children wrap \u2014 drop into its parent dir.
|
|
8362
|
+
const childrenWrap = eventTarget.closest(".file-children");
|
|
8363
|
+
if (childrenWrap && elements.fileExplorer.contains(childrenWrap)) return childrenWrap;
|
|
8364
|
+
return null;
|
|
6957
8365
|
};
|
|
8366
|
+
const _resolveDropPath = (el) => {
|
|
8367
|
+
if (!el) return "/";
|
|
8368
|
+
if (el.classList.contains("file-row")) return el.dataset.path || "/";
|
|
8369
|
+
if (el.classList.contains("file-children")) return el.dataset.dir || "/";
|
|
8370
|
+
return "/";
|
|
8371
|
+
};
|
|
8372
|
+
|
|
8373
|
+
const attachExplorerDropHandlers = () => {
|
|
8374
|
+
const root = elements.fileExplorer;
|
|
8375
|
+
if (!root) return;
|
|
8376
|
+
root.addEventListener("dragover", (e) => {
|
|
8377
|
+
if (!e.dataTransfer || ![...(e.dataTransfer.types || [])].includes("Files")) return;
|
|
8378
|
+
e.preventDefault();
|
|
8379
|
+
_setDropTarget(_resolveDropTarget(e.target));
|
|
8380
|
+
});
|
|
8381
|
+
root.addEventListener("dragleave", (e) => {
|
|
8382
|
+
if (!root.contains(e.relatedTarget)) _setDropTarget(null);
|
|
8383
|
+
});
|
|
8384
|
+
root.addEventListener("drop", (e) => {
|
|
8385
|
+
if (!e.dataTransfer || !e.dataTransfer.files || e.dataTransfer.files.length === 0) return;
|
|
8386
|
+
e.preventDefault();
|
|
8387
|
+
const target = _dropTargetEl;
|
|
8388
|
+
const dir = _resolveDropPath(target);
|
|
8389
|
+
_setDropTarget(null);
|
|
8390
|
+
const items = Array.from(e.dataTransfer.items || []);
|
|
8391
|
+
const hasDir = items.some((it) => {
|
|
8392
|
+
const fn = it.webkitGetAsEntry && it.webkitGetAsEntry();
|
|
8393
|
+
return fn && fn.isDirectory;
|
|
8394
|
+
});
|
|
8395
|
+
const files = Array.from(e.dataTransfer.files);
|
|
8396
|
+
if (hasDir) {
|
|
8397
|
+
window.alert("Folder uploads aren't supported yet \u2014 drop individual files.");
|
|
8398
|
+
}
|
|
8399
|
+
if (files.length > 0) uploadFiles(files, dir);
|
|
8400
|
+
});
|
|
8401
|
+
};
|
|
8402
|
+
|
|
8403
|
+
// Wire segmented control + drop-handlers once the DOM is ready
|
|
8404
|
+
(function wireFileExplorer() {
|
|
8405
|
+
const buttons = document.querySelectorAll(".sidebar-segmented .seg-btn");
|
|
8406
|
+
buttons.forEach((b) => {
|
|
8407
|
+
b.addEventListener("click", () => switchSidebarMode(b.dataset.mode));
|
|
8408
|
+
});
|
|
8409
|
+
attachExplorerDropHandlers();
|
|
8410
|
+
})();
|
|
6958
8411
|
|
|
6959
8412
|
window.addEventListener("popstate", async () => {
|
|
6960
8413
|
if (state.isStreaming) return;
|
|
8414
|
+
const filePath = getFilePathFromUrl();
|
|
8415
|
+
if (filePath) {
|
|
8416
|
+
switchSidebarMode("files");
|
|
8417
|
+
await selectFile(filePath);
|
|
8418
|
+
return;
|
|
8419
|
+
}
|
|
6961
8420
|
const conversationId = getConversationIdFromUrl();
|
|
6962
8421
|
await navigateToConversation(conversationId);
|
|
6963
8422
|
});
|
|
@@ -6968,6 +8427,26 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
6968
8427
|
return;
|
|
6969
8428
|
}
|
|
6970
8429
|
await loadConversations();
|
|
8430
|
+
const urlFilePath = getFilePathFromUrl();
|
|
8431
|
+
if (urlFilePath) {
|
|
8432
|
+
switchSidebarMode("files");
|
|
8433
|
+
// Expand ancestors so the file is visible in the tree
|
|
8434
|
+
let p = parentPath(urlFilePath);
|
|
8435
|
+
const ancestors = [];
|
|
8436
|
+
while (p && p !== "/") { ancestors.push(p); p = parentPath(p); }
|
|
8437
|
+
ancestors.push("/");
|
|
8438
|
+
for (const a of ancestors.reverse()) {
|
|
8439
|
+
state.expandedDirs.add(a);
|
|
8440
|
+
try { await fetchDirEntries(a); } catch {}
|
|
8441
|
+
}
|
|
8442
|
+
state.activeFilePath = urlFilePath;
|
|
8443
|
+
updateComposerVisibility();
|
|
8444
|
+
renderFileExplorer();
|
|
8445
|
+
await renderFilePreview(urlFilePath);
|
|
8446
|
+
autoResizePrompt();
|
|
8447
|
+
updateContextRing();
|
|
8448
|
+
return;
|
|
8449
|
+
}
|
|
6971
8450
|
const urlConversationId = getConversationIdFromUrl();
|
|
6972
8451
|
if (urlConversationId) {
|
|
6973
8452
|
state.activeConversationId = urlConversationId;
|
|
@@ -7768,7 +9247,12 @@ ${WEB_UI_STYLES}
|
|
|
7768
9247
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
7769
9248
|
</button>
|
|
7770
9249
|
</div>
|
|
9250
|
+
<div class="sidebar-segmented" role="tablist">
|
|
9251
|
+
<button class="seg-btn active" data-mode="conversations" role="tab">Chats</button>
|
|
9252
|
+
<button class="seg-btn" data-mode="files" role="tab">Files</button>
|
|
9253
|
+
</div>
|
|
7771
9254
|
<div id="conversation-list" class="conversation-list"></div>
|
|
9255
|
+
<div id="file-explorer" class="file-explorer hidden"></div>
|
|
7772
9256
|
<div class="sidebar-footer">
|
|
7773
9257
|
<div class="sidebar-footer-row">
|
|
7774
9258
|
<button id="logout" class="logout-btn">Log out</button>
|
|
@@ -7842,7 +9326,6 @@ ${WEB_UI_STYLES}
|
|
|
7842
9326
|
<span class="thread-panel-title">Thread</span>
|
|
7843
9327
|
<button id="thread-panel-close" class="thread-panel-close" title="Close thread">×</button>
|
|
7844
9328
|
</div>
|
|
7845
|
-
<div id="thread-panel-parent" class="thread-panel-parent"></div>
|
|
7846
9329
|
<div id="thread-panel-messages" class="thread-panel-messages messages"></div>
|
|
7847
9330
|
<form id="thread-composer" class="composer thread-composer">
|
|
7848
9331
|
<div class="composer-inner">
|
|
@@ -7865,6 +9348,7 @@ ${WEB_UI_STYLES}
|
|
|
7865
9348
|
</div>
|
|
7866
9349
|
<div id="drag-overlay" class="drag-overlay"><div class="drag-overlay-inner">Drop files to attach</div></div>
|
|
7867
9350
|
<div id="lightbox" class="lightbox" style="display:none"><img /></div>
|
|
9351
|
+
<button id="view-toggle" class="dev-view-toggle" hidden title="Toggle between user-facing messages and raw harness messages (dev -v only)">user view</button>
|
|
7868
9352
|
|
|
7869
9353
|
<script>
|
|
7870
9354
|
${getWebUiClientScript(markedSource)}
|
|
@@ -10461,19 +11945,22 @@ var normalizeMessageForClient = (message) => {
|
|
|
10461
11945
|
}
|
|
10462
11946
|
return message;
|
|
10463
11947
|
};
|
|
10464
|
-
var runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent) => {
|
|
11948
|
+
var runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent, parameters, tenantId) => {
|
|
10465
11949
|
const turnTimestamp = Date.now();
|
|
10466
11950
|
const userMessageId = randomUUID2();
|
|
10467
11951
|
const assistantId = randomUUID2();
|
|
11952
|
+
const finalParameters = {
|
|
11953
|
+
...parameters ?? {},
|
|
11954
|
+
__activeConversationId: conversationId,
|
|
11955
|
+
[TOOL_RESULT_ARCHIVE_PARAM]: parameters?.[TOOL_RESULT_ARCHIVE_PARAM] ?? toolResultArchive ?? {}
|
|
11956
|
+
};
|
|
10468
11957
|
const execution = await executeConversationTurn({
|
|
10469
11958
|
harness: harnessRef,
|
|
10470
11959
|
runInput: {
|
|
10471
11960
|
task,
|
|
10472
11961
|
conversationId,
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
[TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {}
|
|
10476
|
-
},
|
|
11962
|
+
tenantId: tenantId ?? void 0,
|
|
11963
|
+
parameters: finalParameters,
|
|
10477
11964
|
messages: historyMessages
|
|
10478
11965
|
},
|
|
10479
11966
|
onEvent
|
|
@@ -12718,7 +14205,7 @@ var runInteractive = async (workingDir, params) => {
|
|
|
12718
14205
|
await harness.initialize();
|
|
12719
14206
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
12720
14207
|
try {
|
|
12721
|
-
const { runInteractiveInk } = await import("./run-interactive-ink-
|
|
14208
|
+
const { runInteractiveInk } = await import("./run-interactive-ink-W5YJS7UH.js");
|
|
12722
14209
|
await runInteractiveInk({
|
|
12723
14210
|
harness,
|
|
12724
14211
|
params,
|
|
@@ -12794,6 +14281,19 @@ var collectToolCallIds = (msgs) => {
|
|
|
12794
14281
|
}
|
|
12795
14282
|
return ids;
|
|
12796
14283
|
};
|
|
14284
|
+
var MEMORY_VFILE_PATH = "/memory.md";
|
|
14285
|
+
var isSafeVfsPath = (p) => {
|
|
14286
|
+
if (typeof p !== "string" || p.length === 0) return false;
|
|
14287
|
+
if (!p.startsWith("/")) return false;
|
|
14288
|
+
if (p.includes("\0")) return false;
|
|
14289
|
+
const segments = p.split("/").slice(1);
|
|
14290
|
+
if (p !== "/" && segments[segments.length - 1] === "") return false;
|
|
14291
|
+
for (const seg of segments) {
|
|
14292
|
+
if (seg === "" && p !== "/") return false;
|
|
14293
|
+
if (seg === "." || seg === "..") return false;
|
|
14294
|
+
}
|
|
14295
|
+
return true;
|
|
14296
|
+
};
|
|
12797
14297
|
var serverlessLog = createLogger("serverless");
|
|
12798
14298
|
var __dirname3 = dirname7(fileURLToPath3(import.meta.url));
|
|
12799
14299
|
var createRequestHandler = async (options) => {
|
|
@@ -12830,6 +14330,22 @@ var createRequestHandler = async (options) => {
|
|
|
12830
14330
|
let activeConversationRuns = /* @__PURE__ */ new Map();
|
|
12831
14331
|
const conversationEventStreams = /* @__PURE__ */ new Map();
|
|
12832
14332
|
const conversationEventCallbacks = /* @__PURE__ */ new Map();
|
|
14333
|
+
const MAX_BUFFERED_EVENTS_PER_CONVERSATION = 1e3;
|
|
14334
|
+
const STRIP_LARGE_STRING_BYTES = 4096;
|
|
14335
|
+
const stripLargeStringsForBuffer = (value) => {
|
|
14336
|
+
if (typeof value === "string") {
|
|
14337
|
+
return value.length > STRIP_LARGE_STRING_BYTES ? `[stripped-for-replay len=${value.length}]` : value;
|
|
14338
|
+
}
|
|
14339
|
+
if (Array.isArray(value)) return value.map(stripLargeStringsForBuffer);
|
|
14340
|
+
if (value && typeof value === "object") {
|
|
14341
|
+
const out = {};
|
|
14342
|
+
for (const [k, v] of Object.entries(value)) {
|
|
14343
|
+
out[k] = stripLargeStringsForBuffer(v);
|
|
14344
|
+
}
|
|
14345
|
+
return out;
|
|
14346
|
+
}
|
|
14347
|
+
return value;
|
|
14348
|
+
};
|
|
12833
14349
|
const broadcastEvent = (conversationId, event) => {
|
|
12834
14350
|
let stream = conversationEventStreams.get(conversationId);
|
|
12835
14351
|
if (!stream) {
|
|
@@ -12837,7 +14353,10 @@ var createRequestHandler = async (options) => {
|
|
|
12837
14353
|
conversationEventStreams.set(conversationId, stream);
|
|
12838
14354
|
}
|
|
12839
14355
|
if (event.type !== "browser:frame") {
|
|
12840
|
-
stream.buffer.push(event);
|
|
14356
|
+
stream.buffer.push(stripLargeStringsForBuffer(event));
|
|
14357
|
+
if (stream.buffer.length > MAX_BUFFERED_EVENTS_PER_CONVERSATION) {
|
|
14358
|
+
stream.buffer.splice(0, stream.buffer.length - MAX_BUFFERED_EVENTS_PER_CONVERSATION);
|
|
14359
|
+
}
|
|
12841
14360
|
}
|
|
12842
14361
|
for (const subscriber of stream.subscribers) {
|
|
12843
14362
|
try {
|
|
@@ -13115,6 +14634,24 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
13115
14634
|
__conversationFetchFn: conversationFetchFn
|
|
13116
14635
|
};
|
|
13117
14636
|
};
|
|
14637
|
+
const buildTurnParameters = (conversation, opts = {}) => {
|
|
14638
|
+
return withToolResultArchiveParam({
|
|
14639
|
+
...opts.bodyParameters ?? {},
|
|
14640
|
+
...buildRecallParams({
|
|
14641
|
+
ownerId: conversation.ownerId,
|
|
14642
|
+
tenantId: conversation.tenantId,
|
|
14643
|
+
excludeConversationId: conversation.conversationId
|
|
14644
|
+
}),
|
|
14645
|
+
...opts.messagingMetadata ? {
|
|
14646
|
+
__messaging_platform: opts.messagingMetadata.platform,
|
|
14647
|
+
__messaging_sender_id: opts.messagingMetadata.sender.id,
|
|
14648
|
+
__messaging_sender_name: opts.messagingMetadata.sender.name ?? "",
|
|
14649
|
+
__messaging_thread_id: opts.messagingMetadata.threadId
|
|
14650
|
+
} : {},
|
|
14651
|
+
__activeConversationId: conversation.conversationId,
|
|
14652
|
+
__ownerId: conversation.ownerId
|
|
14653
|
+
}, conversation);
|
|
14654
|
+
};
|
|
13118
14655
|
const messagingRoutes = /* @__PURE__ */ new Map();
|
|
13119
14656
|
const messagingRouteRegistrar = (method, path, routeHandler) => {
|
|
13120
14657
|
let byMethod = messagingRoutes.get(path);
|
|
@@ -13196,6 +14733,7 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
13196
14733
|
let runContextWindow = 0;
|
|
13197
14734
|
let runContinuation2 = false;
|
|
13198
14735
|
let runContinuationMessages;
|
|
14736
|
+
let runHarnessMessages;
|
|
13199
14737
|
let runSteps = 0;
|
|
13200
14738
|
let runMaxSteps;
|
|
13201
14739
|
const buildMessages = () => {
|
|
@@ -13230,9 +14768,12 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
13230
14768
|
const runInput = {
|
|
13231
14769
|
task: input2.task,
|
|
13232
14770
|
conversationId,
|
|
14771
|
+
tenantId: latestConversation?.tenantId ?? void 0,
|
|
13233
14772
|
messages: historyMessages,
|
|
13234
14773
|
files: input2.files,
|
|
13235
|
-
parameters:
|
|
14774
|
+
parameters: latestConversation ? buildTurnParameters(latestConversation, {
|
|
14775
|
+
messagingMetadata: input2.metadata
|
|
14776
|
+
}) : withToolResultArchiveParam({
|
|
13236
14777
|
...input2.metadata ? {
|
|
13237
14778
|
__messaging_platform: input2.metadata.platform,
|
|
13238
14779
|
__messaging_sender_id: input2.metadata.sender.id,
|
|
@@ -13240,7 +14781,7 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
13240
14781
|
__messaging_thread_id: input2.metadata.threadId
|
|
13241
14782
|
} : {},
|
|
13242
14783
|
__activeConversationId: conversationId
|
|
13243
|
-
},
|
|
14784
|
+
}, { _toolResultArchive: {} })
|
|
13244
14785
|
};
|
|
13245
14786
|
try {
|
|
13246
14787
|
const execution = await executeConversationTurn2({
|
|
@@ -13334,6 +14875,7 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
13334
14875
|
});
|
|
13335
14876
|
runContinuation2 = execution.runContinuation;
|
|
13336
14877
|
runContinuationMessages = execution.runContinuationMessages;
|
|
14878
|
+
runHarnessMessages = execution.runHarnessMessages;
|
|
13337
14879
|
runSteps = execution.runSteps;
|
|
13338
14880
|
runMaxSteps = execution.runMaxSteps;
|
|
13339
14881
|
runContextTokens = execution.runContextTokens;
|
|
@@ -13355,7 +14897,11 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
13355
14897
|
contextWindow: runContextWindow,
|
|
13356
14898
|
continuation: runContinuation2,
|
|
13357
14899
|
continuationMessages: runContinuationMessages,
|
|
13358
|
-
|
|
14900
|
+
// Prefer the cancellation/end-of-run snapshot from the harness so
|
|
14901
|
+
// _harnessMessages stays in sync with what the model just saw,
|
|
14902
|
+
// even on aborted runs. Falls back to continuationMessages when
|
|
14903
|
+
// the run completed via continuation.
|
|
14904
|
+
harnessMessages: runHarnessMessages ?? runContinuationMessages,
|
|
13359
14905
|
toolResultArchive: harness.getToolResultArchive(conversationId)
|
|
13360
14906
|
}, { shouldRebuildCanonical: true });
|
|
13361
14907
|
});
|
|
@@ -13757,7 +15303,7 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
13757
15303
|
selfBaseUrl = `${proto}://${request.headers.host}`;
|
|
13758
15304
|
}
|
|
13759
15305
|
if (webUiEnabled) {
|
|
13760
|
-
if (request.method === "GET" && (pathname === "/" || pathname.startsWith("/c/"))) {
|
|
15306
|
+
if (request.method === "GET" && (pathname === "/" || pathname.startsWith("/c/") || pathname.startsWith("/f/"))) {
|
|
13761
15307
|
writeHtml(response, 200, renderWebUiHtml({ agentName, isDev: !isProduction }));
|
|
13762
15308
|
return;
|
|
13763
15309
|
}
|
|
@@ -14760,13 +16306,16 @@ data: ${JSON.stringify(frame)}
|
|
|
14760
16306
|
const hasPendingCallbackResults = Array.isArray(conversation.pendingSubagentResults) && conversation.pendingSubagentResults.length > 0;
|
|
14761
16307
|
const hasPendingApprovals = Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0;
|
|
14762
16308
|
const needsContinuation = !hasActiveRun && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0 && !hasPendingApprovals;
|
|
16309
|
+
const verboseDev = process.env.PONCHO_DEV_VERBOSE === "1";
|
|
14763
16310
|
writeJson(response, 200, {
|
|
14764
16311
|
conversation: {
|
|
14765
16312
|
...conversation,
|
|
14766
16313
|
messages: conversation.messages.map(normalizeMessageForClient).filter((m) => m !== null),
|
|
14767
16314
|
pendingApprovals: storedPending,
|
|
14768
16315
|
_continuationMessages: void 0,
|
|
14769
|
-
|
|
16316
|
+
// In verbose dev mode the web UI exposes a toggle to inspect the
|
|
16317
|
+
// raw harness messages sent to the model API. Strip it otherwise.
|
|
16318
|
+
_harnessMessages: verboseDev ? conversation._harnessMessages : void 0,
|
|
14770
16319
|
// The browser has no use for the archive; make sure we never ship
|
|
14771
16320
|
// it back even if the conversation was loaded via getWithArchive.
|
|
14772
16321
|
_toolResultArchive: void 0
|
|
@@ -14774,7 +16323,8 @@ data: ${JSON.stringify(frame)}
|
|
|
14774
16323
|
subagentPendingApprovals: subagentPending,
|
|
14775
16324
|
hasActiveRun: hasActiveRun || hasPendingCallbackResults,
|
|
14776
16325
|
hasRunningSubagents,
|
|
14777
|
-
needsContinuation
|
|
16326
|
+
needsContinuation,
|
|
16327
|
+
verboseDev
|
|
14778
16328
|
});
|
|
14779
16329
|
return;
|
|
14780
16330
|
}
|
|
@@ -14903,6 +16453,22 @@ data: ${JSON.stringify(frame)}
|
|
|
14903
16453
|
writeJson(response, 500, { code: "NO_ENGINE", message: "Storage engine not available" });
|
|
14904
16454
|
return;
|
|
14905
16455
|
}
|
|
16456
|
+
if (vfsPath === MEMORY_VFILE_PATH) {
|
|
16457
|
+
try {
|
|
16458
|
+
const memory = await engine.memory.get(tenantId);
|
|
16459
|
+
const data = Buffer.from(memory.content, "utf-8");
|
|
16460
|
+
response.writeHead(200, {
|
|
16461
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
16462
|
+
"Content-Length": data.length,
|
|
16463
|
+
"Content-Disposition": `inline; filename="memory.md"`,
|
|
16464
|
+
"Cache-Control": "no-cache"
|
|
16465
|
+
});
|
|
16466
|
+
response.end(data);
|
|
16467
|
+
} catch (err) {
|
|
16468
|
+
writeJson(response, 500, { code: "READ_FAILED", message: err?.message ?? "Failed to read memory" });
|
|
16469
|
+
}
|
|
16470
|
+
return;
|
|
16471
|
+
}
|
|
14906
16472
|
try {
|
|
14907
16473
|
const stat2 = await engine.vfs.stat(tenantId, vfsPath);
|
|
14908
16474
|
if (!stat2 || stat2.type !== "file") {
|
|
@@ -14949,6 +16515,260 @@ data: ${JSON.stringify(frame)}
|
|
|
14949
16515
|
}
|
|
14950
16516
|
return;
|
|
14951
16517
|
}
|
|
16518
|
+
if (vfsMatch && request.method === "PUT") {
|
|
16519
|
+
const rawPath = "/" + decodeURIComponent(vfsMatch[1] ?? "");
|
|
16520
|
+
const tenantId = ctx.tenantId ?? "__default__";
|
|
16521
|
+
const engine = harness.storageEngine;
|
|
16522
|
+
if (!engine) {
|
|
16523
|
+
writeJson(response, 500, { code: "NO_ENGINE", message: "Storage engine not available" });
|
|
16524
|
+
return;
|
|
16525
|
+
}
|
|
16526
|
+
if (!isSafeVfsPath(rawPath) || rawPath === "/") {
|
|
16527
|
+
writeJson(response, 400, { code: "BAD_PATH", message: "Invalid path" });
|
|
16528
|
+
return;
|
|
16529
|
+
}
|
|
16530
|
+
if (rawPath === MEMORY_VFILE_PATH) {
|
|
16531
|
+
try {
|
|
16532
|
+
const chunks = [];
|
|
16533
|
+
for await (const chunk of request) chunks.push(chunk);
|
|
16534
|
+
const body = Buffer.concat(chunks);
|
|
16535
|
+
const content = body.toString("utf-8").trim();
|
|
16536
|
+
const memory = await engine.memory.update(content, tenantId);
|
|
16537
|
+
writeJson(response, 200, {
|
|
16538
|
+
path: MEMORY_VFILE_PATH,
|
|
16539
|
+
size: Buffer.byteLength(memory.content, "utf-8"),
|
|
16540
|
+
mimeType: "text/markdown",
|
|
16541
|
+
updatedAt: memory.updatedAt
|
|
16542
|
+
});
|
|
16543
|
+
} catch (err) {
|
|
16544
|
+
writeJson(response, 500, { code: "WRITE_FAILED", message: err?.message ?? "Failed to write memory" });
|
|
16545
|
+
}
|
|
16546
|
+
return;
|
|
16547
|
+
}
|
|
16548
|
+
const allowOverwrite = requestUrl.searchParams.get("overwrite") === "1";
|
|
16549
|
+
try {
|
|
16550
|
+
const existing = await engine.vfs.stat(tenantId, rawPath);
|
|
16551
|
+
if (existing && !allowOverwrite) {
|
|
16552
|
+
writeJson(response, 409, { code: "EXISTS", message: "File already exists" });
|
|
16553
|
+
return;
|
|
16554
|
+
}
|
|
16555
|
+
if (existing && existing.type !== "file") {
|
|
16556
|
+
writeJson(response, 409, { code: "NOT_A_FILE", message: "Path exists and is not a file" });
|
|
16557
|
+
return;
|
|
16558
|
+
}
|
|
16559
|
+
const chunks = [];
|
|
16560
|
+
for await (const chunk of request) chunks.push(chunk);
|
|
16561
|
+
const body = Buffer.concat(chunks);
|
|
16562
|
+
const mimeType = request.headers["content-type"]?.split(";")[0]?.trim() || void 0;
|
|
16563
|
+
await engine.vfs.writeFile(tenantId, rawPath, new Uint8Array(body), mimeType);
|
|
16564
|
+
if (rawPath === "/skills" || rawPath.startsWith("/skills/")) {
|
|
16565
|
+
harness.invalidateSkillsForTenant(tenantId);
|
|
16566
|
+
}
|
|
16567
|
+
const stat2 = await engine.vfs.stat(tenantId, rawPath);
|
|
16568
|
+
writeJson(response, 200, {
|
|
16569
|
+
path: rawPath,
|
|
16570
|
+
size: stat2?.size ?? body.length,
|
|
16571
|
+
mimeType: stat2?.mimeType ?? mimeType ?? null,
|
|
16572
|
+
updatedAt: stat2?.updatedAt ?? Date.now()
|
|
16573
|
+
});
|
|
16574
|
+
} catch (err) {
|
|
16575
|
+
const message = err?.message ?? "Upload failed";
|
|
16576
|
+
const code = /quota|too large|exceed/i.test(message) ? 413 : 500;
|
|
16577
|
+
writeJson(response, code, { code: code === 413 ? "QUOTA" : "WRITE_FAILED", message });
|
|
16578
|
+
}
|
|
16579
|
+
return;
|
|
16580
|
+
}
|
|
16581
|
+
if (vfsMatch && request.method === "DELETE") {
|
|
16582
|
+
const rawPath = "/" + decodeURIComponent(vfsMatch[1] ?? "");
|
|
16583
|
+
const tenantId = ctx.tenantId ?? "__default__";
|
|
16584
|
+
const engine = harness.storageEngine;
|
|
16585
|
+
if (!engine) {
|
|
16586
|
+
writeJson(response, 500, { code: "NO_ENGINE", message: "Storage engine not available" });
|
|
16587
|
+
return;
|
|
16588
|
+
}
|
|
16589
|
+
if (!isSafeVfsPath(rawPath) || rawPath === "/") {
|
|
16590
|
+
writeJson(response, 400, { code: "BAD_PATH", message: "Invalid path" });
|
|
16591
|
+
return;
|
|
16592
|
+
}
|
|
16593
|
+
if (rawPath === MEMORY_VFILE_PATH) {
|
|
16594
|
+
writeJson(response, 400, {
|
|
16595
|
+
code: "RESERVED",
|
|
16596
|
+
message: "memory.md cannot be deleted; clear its contents instead."
|
|
16597
|
+
});
|
|
16598
|
+
return;
|
|
16599
|
+
}
|
|
16600
|
+
try {
|
|
16601
|
+
const stat2 = await engine.vfs.stat(tenantId, rawPath);
|
|
16602
|
+
if (!stat2) {
|
|
16603
|
+
writeJson(response, 404, { code: "NOT_FOUND", message: "Path not found" });
|
|
16604
|
+
return;
|
|
16605
|
+
}
|
|
16606
|
+
if (stat2.type === "directory") {
|
|
16607
|
+
await engine.vfs.deleteDir(tenantId, rawPath, true);
|
|
16608
|
+
} else {
|
|
16609
|
+
await engine.vfs.deleteFile(tenantId, rawPath);
|
|
16610
|
+
}
|
|
16611
|
+
if (rawPath === "/skills" || rawPath.startsWith("/skills/")) {
|
|
16612
|
+
harness.invalidateSkillsForTenant(tenantId);
|
|
16613
|
+
}
|
|
16614
|
+
writeJson(response, 200, { ok: true, path: rawPath });
|
|
16615
|
+
} catch (err) {
|
|
16616
|
+
writeJson(response, 500, { code: "DELETE_FAILED", message: err?.message ?? "Failed to delete" });
|
|
16617
|
+
}
|
|
16618
|
+
return;
|
|
16619
|
+
}
|
|
16620
|
+
if (pathname === "/api/vfs-archive" && request.method === "GET") {
|
|
16621
|
+
const dirPath = requestUrl.searchParams.get("path") ?? "/";
|
|
16622
|
+
const tenantId = ctx.tenantId ?? "__default__";
|
|
16623
|
+
const engine = harness.storageEngine;
|
|
16624
|
+
if (!engine) {
|
|
16625
|
+
writeJson(response, 500, { code: "NO_ENGINE", message: "Storage engine not available" });
|
|
16626
|
+
return;
|
|
16627
|
+
}
|
|
16628
|
+
if (!isSafeVfsPath(dirPath)) {
|
|
16629
|
+
writeJson(response, 400, { code: "BAD_PATH", message: "Invalid path" });
|
|
16630
|
+
return;
|
|
16631
|
+
}
|
|
16632
|
+
try {
|
|
16633
|
+
if (dirPath !== "/") {
|
|
16634
|
+
const stat2 = await engine.vfs.stat(tenantId, dirPath);
|
|
16635
|
+
if (!stat2 || stat2.type !== "directory") {
|
|
16636
|
+
writeJson(response, 404, { code: "NOT_FOUND", message: "Directory not found" });
|
|
16637
|
+
return;
|
|
16638
|
+
}
|
|
16639
|
+
}
|
|
16640
|
+
const entries = [];
|
|
16641
|
+
const walk = async (dir, prefix) => {
|
|
16642
|
+
const children = await engine.vfs.readdir(tenantId, dir);
|
|
16643
|
+
for (const child of children) {
|
|
16644
|
+
if (dir === "/" && child.name === "memory.md") continue;
|
|
16645
|
+
const childPath = dir === "/" ? "/" + child.name : dir + "/" + child.name;
|
|
16646
|
+
const relName = prefix === "" ? child.name : prefix + "/" + child.name;
|
|
16647
|
+
if (child.type === "directory") {
|
|
16648
|
+
await walk(childPath, relName);
|
|
16649
|
+
} else if (child.type === "file") {
|
|
16650
|
+
const content = await engine.vfs.readFile(tenantId, childPath);
|
|
16651
|
+
const stat2 = await engine.vfs.stat(tenantId, childPath);
|
|
16652
|
+
entries.push({
|
|
16653
|
+
name: relName,
|
|
16654
|
+
content,
|
|
16655
|
+
mtime: stat2?.updatedAt ? new Date(stat2.updatedAt) : void 0
|
|
16656
|
+
});
|
|
16657
|
+
}
|
|
16658
|
+
}
|
|
16659
|
+
};
|
|
16660
|
+
await walk(dirPath, "");
|
|
16661
|
+
if (dirPath === "/") {
|
|
16662
|
+
const memory = await engine.memory.get(tenantId);
|
|
16663
|
+
entries.push({
|
|
16664
|
+
name: "memory.md",
|
|
16665
|
+
content: new Uint8Array(Buffer.from(memory.content, "utf-8")),
|
|
16666
|
+
mtime: memory.updatedAt > 0 ? new Date(memory.updatedAt) : void 0
|
|
16667
|
+
});
|
|
16668
|
+
}
|
|
16669
|
+
const archiveName = (dirPath === "/" ? "vfs" : dirPath.split("/").pop() || "archive") + ".zip";
|
|
16670
|
+
const zip = buildZip(entries);
|
|
16671
|
+
response.writeHead(200, {
|
|
16672
|
+
"Content-Type": "application/zip",
|
|
16673
|
+
"Content-Length": zip.length,
|
|
16674
|
+
"Content-Disposition": `attachment; filename="${archiveName}"`,
|
|
16675
|
+
"Cache-Control": "no-cache"
|
|
16676
|
+
});
|
|
16677
|
+
response.end(zip);
|
|
16678
|
+
} catch (err) {
|
|
16679
|
+
writeJson(response, 500, { code: "ARCHIVE_FAILED", message: err?.message ?? "Failed to build archive" });
|
|
16680
|
+
}
|
|
16681
|
+
return;
|
|
16682
|
+
}
|
|
16683
|
+
if (pathname === "/api/vfs-list" && request.method === "GET") {
|
|
16684
|
+
const dirPath = requestUrl.searchParams.get("path") ?? "/";
|
|
16685
|
+
const tenantId = ctx.tenantId ?? "__default__";
|
|
16686
|
+
const engine = harness.storageEngine;
|
|
16687
|
+
if (!engine) {
|
|
16688
|
+
writeJson(response, 500, { code: "NO_ENGINE", message: "Storage engine not available" });
|
|
16689
|
+
return;
|
|
16690
|
+
}
|
|
16691
|
+
if (!isSafeVfsPath(dirPath)) {
|
|
16692
|
+
writeJson(response, 400, { code: "BAD_PATH", message: "Invalid path" });
|
|
16693
|
+
return;
|
|
16694
|
+
}
|
|
16695
|
+
try {
|
|
16696
|
+
if (dirPath !== "/") {
|
|
16697
|
+
const stat2 = await engine.vfs.stat(tenantId, dirPath);
|
|
16698
|
+
if (!stat2 || stat2.type !== "directory") {
|
|
16699
|
+
writeJson(response, 404, { code: "NOT_FOUND", message: "Directory not found" });
|
|
16700
|
+
return;
|
|
16701
|
+
}
|
|
16702
|
+
}
|
|
16703
|
+
const rawDirEntries = await engine.vfs.readdir(tenantId, dirPath);
|
|
16704
|
+
const dirEntries = dirPath === "/" ? rawDirEntries.filter((e) => e.name !== "memory.md") : rawDirEntries;
|
|
16705
|
+
const entries = await Promise.all(dirEntries.map(async (entry) => {
|
|
16706
|
+
const childPath = dirPath === "/" ? "/" + entry.name : dirPath + "/" + entry.name;
|
|
16707
|
+
const stat2 = await engine.vfs.stat(tenantId, childPath);
|
|
16708
|
+
return {
|
|
16709
|
+
name: entry.name,
|
|
16710
|
+
type: entry.type,
|
|
16711
|
+
size: stat2?.size ?? 0,
|
|
16712
|
+
mimeType: stat2?.mimeType ?? null,
|
|
16713
|
+
updatedAt: stat2?.updatedAt ?? null
|
|
16714
|
+
};
|
|
16715
|
+
}));
|
|
16716
|
+
if (dirPath === "/") {
|
|
16717
|
+
const memory = await engine.memory.get(tenantId);
|
|
16718
|
+
entries.push({
|
|
16719
|
+
name: "memory.md",
|
|
16720
|
+
type: "file",
|
|
16721
|
+
size: Buffer.byteLength(memory.content, "utf-8"),
|
|
16722
|
+
mimeType: "text/markdown",
|
|
16723
|
+
updatedAt: memory.updatedAt > 0 ? memory.updatedAt : null
|
|
16724
|
+
});
|
|
16725
|
+
}
|
|
16726
|
+
entries.sort((a, b) => {
|
|
16727
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
16728
|
+
return a.name.localeCompare(b.name);
|
|
16729
|
+
});
|
|
16730
|
+
const usage = await engine.vfs.getUsage(tenantId);
|
|
16731
|
+
writeJson(response, 200, { path: dirPath, entries, usage });
|
|
16732
|
+
} catch (err) {
|
|
16733
|
+
writeJson(response, 500, { code: "READDIR_FAILED", message: err?.message ?? "Failed to list directory" });
|
|
16734
|
+
}
|
|
16735
|
+
return;
|
|
16736
|
+
}
|
|
16737
|
+
if (pathname === "/api/vfs-mkdir" && request.method === "POST") {
|
|
16738
|
+
const tenantId = ctx.tenantId ?? "__default__";
|
|
16739
|
+
const engine = harness.storageEngine;
|
|
16740
|
+
if (!engine) {
|
|
16741
|
+
writeJson(response, 500, { code: "NO_ENGINE", message: "Storage engine not available" });
|
|
16742
|
+
return;
|
|
16743
|
+
}
|
|
16744
|
+
try {
|
|
16745
|
+
const chunks = [];
|
|
16746
|
+
for await (const chunk of request) chunks.push(chunk);
|
|
16747
|
+
const body = JSON.parse(Buffer.concat(chunks).toString() || "{}");
|
|
16748
|
+
const dirPath = body.path ?? "";
|
|
16749
|
+
if (!isSafeVfsPath(dirPath) || dirPath === "/") {
|
|
16750
|
+
writeJson(response, 400, { code: "BAD_PATH", message: "Invalid path" });
|
|
16751
|
+
return;
|
|
16752
|
+
}
|
|
16753
|
+
if (dirPath === MEMORY_VFILE_PATH) {
|
|
16754
|
+
writeJson(response, 400, { code: "RESERVED", message: "memory.md is reserved" });
|
|
16755
|
+
return;
|
|
16756
|
+
}
|
|
16757
|
+
const existing = await engine.vfs.stat(tenantId, dirPath);
|
|
16758
|
+
if (existing) {
|
|
16759
|
+
writeJson(response, 409, { code: "EXISTS", message: "Path already exists" });
|
|
16760
|
+
return;
|
|
16761
|
+
}
|
|
16762
|
+
await engine.vfs.mkdir(tenantId, dirPath, true);
|
|
16763
|
+
if (dirPath === "/skills" || dirPath.startsWith("/skills/")) {
|
|
16764
|
+
harness.invalidateSkillsForTenant(tenantId);
|
|
16765
|
+
}
|
|
16766
|
+
writeJson(response, 200, { path: dirPath });
|
|
16767
|
+
} catch (err) {
|
|
16768
|
+
writeJson(response, 500, { code: "MKDIR_FAILED", message: err?.message ?? "Failed to create directory" });
|
|
16769
|
+
}
|
|
16770
|
+
return;
|
|
16771
|
+
}
|
|
14952
16772
|
if (pathname === "/api/slash-commands" && request.method === "GET") {
|
|
14953
16773
|
const skills = harness.listSkills().map((s) => ({
|
|
14954
16774
|
command: "/" + s.name,
|
|
@@ -15215,6 +17035,7 @@ data: ${JSON.stringify(frame)}
|
|
|
15215
17035
|
let checkpointedRun = false;
|
|
15216
17036
|
let runCancelled = false;
|
|
15217
17037
|
let runContinuationMessages;
|
|
17038
|
+
let cancelHarnessMessages;
|
|
15218
17039
|
const turnTimestamp = Date.now();
|
|
15219
17040
|
const userMessage = userContent != null ? {
|
|
15220
17041
|
role: "user",
|
|
@@ -15270,12 +17091,7 @@ data: ${JSON.stringify(frame)}
|
|
|
15270
17091
|
task: messageText,
|
|
15271
17092
|
conversationId,
|
|
15272
17093
|
tenantId: ctx.tenantId ?? void 0,
|
|
15273
|
-
parameters:
|
|
15274
|
-
...bodyParameters ?? {},
|
|
15275
|
-
...buildRecallParams({ ownerId, tenantId: ctx.tenantId, excludeConversationId: conversationId }),
|
|
15276
|
-
__activeConversationId: conversationId,
|
|
15277
|
-
__ownerId: ownerId
|
|
15278
|
-
}, conversation),
|
|
17094
|
+
parameters: buildTurnParameters(conversation, { bodyParameters }),
|
|
15279
17095
|
messages: harnessMessages,
|
|
15280
17096
|
files: files.length > 0 ? files : void 0,
|
|
15281
17097
|
abortSignal: abortController.signal
|
|
@@ -15299,6 +17115,7 @@ data: ${JSON.stringify(frame)}
|
|
|
15299
17115
|
}
|
|
15300
17116
|
if (event.type === "run:cancelled") {
|
|
15301
17117
|
runCancelled = true;
|
|
17118
|
+
if (event.messages) cancelHarnessMessages = event.messages;
|
|
15302
17119
|
}
|
|
15303
17120
|
if (event.type === "compaction:completed") {
|
|
15304
17121
|
if (event.compactedMessages) {
|
|
@@ -15415,7 +17232,13 @@ data: ${JSON.stringify(frame)}
|
|
|
15415
17232
|
if (abortController.signal.aborted || runCancelled) {
|
|
15416
17233
|
if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
|
|
15417
17234
|
conversation.messages = buildMessages();
|
|
15418
|
-
conversation
|
|
17235
|
+
applyTurnMetadata(conversation, {
|
|
17236
|
+
latestRunId,
|
|
17237
|
+
contextTokens: 0,
|
|
17238
|
+
contextWindow: 0,
|
|
17239
|
+
harnessMessages: cancelHarnessMessages,
|
|
17240
|
+
toolResultArchive: harness.getToolResultArchive(conversationId)
|
|
17241
|
+
}, { shouldRebuildCanonical: true });
|
|
15419
17242
|
await conversationStore.update(conversation);
|
|
15420
17243
|
}
|
|
15421
17244
|
if (!checkpointedRun) {
|
|
@@ -15535,7 +17358,9 @@ ${cronJob.task}`;
|
|
|
15535
17358
|
conv._toolResultArchive,
|
|
15536
17359
|
async (event) => {
|
|
15537
17360
|
await telemetry.emit(event);
|
|
15538
|
-
}
|
|
17361
|
+
},
|
|
17362
|
+
buildTurnParameters(conv),
|
|
17363
|
+
conv.tenantId
|
|
15539
17364
|
);
|
|
15540
17365
|
const freshConv = await conversationStore.get(conv.conversationId);
|
|
15541
17366
|
if (freshConv) {
|
|
@@ -15603,7 +17428,9 @@ ${cronJob.task}`;
|
|
|
15603
17428
|
async (event) => {
|
|
15604
17429
|
broadcastEvent(convId, event);
|
|
15605
17430
|
await telemetry.emit(event);
|
|
15606
|
-
}
|
|
17431
|
+
},
|
|
17432
|
+
buildTurnParameters(conversation),
|
|
17433
|
+
conversation.tenantId
|
|
15607
17434
|
);
|
|
15608
17435
|
finishConversationStream(convId);
|
|
15609
17436
|
const freshConv = await conversationStore.get(convId);
|
|
@@ -15721,7 +17548,10 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` + recurrenceNote
|
|
|
15721
17548
|
framedMessage,
|
|
15722
17549
|
originConv.conversationId,
|
|
15723
17550
|
originConv.messages ?? [],
|
|
15724
|
-
originConv._toolResultArchive
|
|
17551
|
+
originConv._toolResultArchive,
|
|
17552
|
+
void 0,
|
|
17553
|
+
buildTurnParameters(originConv),
|
|
17554
|
+
originConv.tenantId
|
|
15725
17555
|
);
|
|
15726
17556
|
if (result.response) {
|
|
15727
17557
|
try {
|
|
@@ -15754,7 +17584,16 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` + recurrenceNote
|
|
|
15754
17584
|
`[reminder] ${reminder.task.slice(0, 80)} ${timestamp}`
|
|
15755
17585
|
);
|
|
15756
17586
|
const convId = conversation.conversationId;
|
|
15757
|
-
const result = await runCronAgent(
|
|
17587
|
+
const result = await runCronAgent(
|
|
17588
|
+
harness,
|
|
17589
|
+
framedMessage,
|
|
17590
|
+
convId,
|
|
17591
|
+
[],
|
|
17592
|
+
void 0,
|
|
17593
|
+
void 0,
|
|
17594
|
+
buildTurnParameters(conversation),
|
|
17595
|
+
conversation.tenantId
|
|
17596
|
+
);
|
|
15758
17597
|
const freshConv = await conversationStore.get(convId);
|
|
15759
17598
|
if (freshConv) {
|
|
15760
17599
|
freshConv.messages = buildCronMessages(framedMessage, [], result);
|
|
@@ -15787,6 +17626,7 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` + recurrenceNote
|
|
|
15787
17626
|
handler._finishConversationStream = finishConversationStream;
|
|
15788
17627
|
handler._checkAndFireReminders = checkAndFireReminders;
|
|
15789
17628
|
handler._reminderPollIntervalMs = reminderPollWindowMs;
|
|
17629
|
+
handler._buildTurnParameters = buildTurnParameters;
|
|
15790
17630
|
orchestrator.recoverStaleSubagents().catch(
|
|
15791
17631
|
(err) => subagentLog.warn(`failed to recover stale subagent runs: ${formatError(err)}`)
|
|
15792
17632
|
);
|
|
@@ -15817,6 +17657,7 @@ var startDevServer = async (port, options) => {
|
|
|
15817
17657
|
const activeRuns = handler._activeConversationRuns;
|
|
15818
17658
|
const deferredCallbacks = handler._pendingCallbackNeeded;
|
|
15819
17659
|
const runCallback = handler._processSubagentCallback;
|
|
17660
|
+
const buildParams = handler._buildTurnParameters;
|
|
15820
17661
|
if (!harnessRef || !store) return;
|
|
15821
17662
|
for (const [jobName, config] of entries) {
|
|
15822
17663
|
const job = new Cron(
|
|
@@ -15873,7 +17714,9 @@ ${config.task}`;
|
|
|
15873
17714
|
convId,
|
|
15874
17715
|
historyMessages,
|
|
15875
17716
|
conversation._toolResultArchive,
|
|
15876
|
-
broadcastCh ? (ev) => broadcastCh(convId, ev) : void 0
|
|
17717
|
+
broadcastCh ? (ev) => broadcastCh(convId, ev) : void 0,
|
|
17718
|
+
buildParams?.(conversation),
|
|
17719
|
+
conversation.tenantId
|
|
15877
17720
|
);
|
|
15878
17721
|
handler._finishConversationStream?.(convId);
|
|
15879
17722
|
const freshConv = await store.get(convId);
|
|
@@ -15941,7 +17784,9 @@ ${config.task}`;
|
|
|
15941
17784
|
cronConvId,
|
|
15942
17785
|
[],
|
|
15943
17786
|
conversation._toolResultArchive,
|
|
15944
|
-
broadcast ? (ev) => broadcast(cronConvId, ev) : void 0
|
|
17787
|
+
broadcast ? (ev) => broadcast(cronConvId, ev) : void 0,
|
|
17788
|
+
buildParams?.(conversation),
|
|
17789
|
+
conversation.tenantId
|
|
15945
17790
|
);
|
|
15946
17791
|
handler._finishConversationStream?.(cronConvId);
|
|
15947
17792
|
const freshConv = await store.get(cronConvId);
|
|
@@ -16054,6 +17899,9 @@ var buildCli = () => {
|
|
|
16054
17899
|
}
|
|
16055
17900
|
setLogLevel(level);
|
|
16056
17901
|
}
|
|
17902
|
+
if (options.verbose) {
|
|
17903
|
+
process.env.PONCHO_DEV_VERBOSE = "1";
|
|
17904
|
+
}
|
|
16057
17905
|
if (process.stdout.isTTY && !process.env.NO_COLOR) {
|
|
16058
17906
|
process.stdout.write("\n");
|
|
16059
17907
|
for (const line of getMascotLines()) process.stdout.write(`${line}
|