@oh-my-pi/pi-coding-agent 15.11.8 → 15.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -2
- package/dist/cli.js +8095 -7704
- package/dist/types/collab/crypto.d.ts +1 -6
- package/dist/types/collab/guest.d.ts +2 -0
- package/dist/types/collab/host.d.ts +16 -0
- package/dist/types/collab/protocol.d.ts +14 -1
- package/dist/types/config/settings-schema.d.ts +52 -6
- package/dist/types/export/custom-share.d.ts +1 -2
- package/dist/types/export/html/index.d.ts +39 -1
- package/dist/types/export/share.d.ts +43 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/agent-hub.d.ts +19 -1
- package/dist/types/modes/components/status-line/component.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/controllers/event-controller.d.ts +7 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/session-focus-controller.d.ts +31 -0
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/session-observer-registry.d.ts +7 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/codex-auto-reset.d.ts +8 -4
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/task/types.d.ts +9 -0
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/package.json +13 -14
- package/scripts/build-binary.ts +4 -0
- package/scripts/bundle-dist.ts +4 -0
- package/scripts/generate-share-viewer.ts +34 -0
- package/src/collab/crypto.ts +10 -4
- package/src/collab/guest.ts +31 -2
- package/src/collab/host.ts +73 -11
- package/src/collab/protocol.ts +48 -7
- package/src/commands/join.ts +1 -1
- package/src/config/settings-schema.ts +54 -5
- package/src/config/settings.ts +12 -0
- package/src/export/custom-share.ts +1 -1
- package/src/export/html/index.ts +122 -17
- package/src/export/html/share-loader.js +102 -0
- package/src/export/html/template.css +745 -459
- package/src/export/html/template.html +6 -3
- package/src/export/html/template.js +240 -915
- package/src/export/html/tool-views.generated.js +38 -0
- package/src/export/share.ts +268 -0
- package/src/internal-urls/docs-index.generated.ts +73 -73
- package/src/lsp/index.ts +11 -0
- package/src/main.ts +22 -9
- package/src/modes/components/agent-hub.ts +541 -410
- package/src/modes/components/status-line/component.ts +38 -5
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tips.txt +3 -1
- package/src/modes/controllers/command-controller.ts +55 -96
- package/src/modes/controllers/event-controller.ts +45 -16
- package/src/modes/controllers/input-controller.ts +104 -4
- package/src/modes/controllers/selector-controller.ts +11 -15
- package/src/modes/controllers/session-focus-controller.ts +112 -0
- package/src/modes/interactive-mode.ts +44 -2
- package/src/modes/session-observer-registry.ts +11 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/modes/types.ts +12 -0
- package/src/modes/utils/ui-helpers.ts +16 -13
- package/src/prompts/tools/job.md +1 -1
- package/src/session/agent-session.ts +87 -19
- package/src/session/codex-auto-reset.ts +23 -11
- package/src/slash-commands/builtin-registry.ts +62 -35
- package/src/task/executor.ts +14 -0
- package/src/task/index.ts +5 -1
- package/src/task/render.ts +76 -5
- package/src/task/types.ts +9 -0
- package/src/tiny/worker.ts +17 -95
- package/src/tools/ast-grep.ts +3 -1
- package/src/tools/find.ts +3 -1
- package/src/tools/gh.ts +20 -6
- package/src/tools/irc.ts +4 -0
- package/src/tools/job.ts +18 -13
- package/src/tools/memory-recall.ts +2 -0
- package/src/tools/search.ts +3 -1
- package/src/tools/tool-result.ts +8 -0
- package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
- package/dist/types/export/html/template.generated.d.ts +0 -1
- package/dist/types/export/html/template.macro.d.ts +0 -5
- package/dist/types/tiny/compiled-runtime.d.ts +0 -35
- package/scripts/generate-template.ts +0 -33
- package/src/bun-imports.d.ts +0 -28
- package/src/export/html/template.generated.ts +0 -2
- package/src/export/html/template.macro.ts +0 -25
- package/src/tiny/compiled-runtime.ts +0 -179
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
// ============================================================
|
|
5
|
-
//
|
|
5
|
+
// BOOT
|
|
6
6
|
// ============================================================
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const { header, entries, leafId: defaultLeafId, systemPrompt, tools } = data;
|
|
7
|
+
//
|
|
8
|
+
// Two boot paths share this template:
|
|
9
|
+
// - Static export: session JSON rides base64-embedded in #session-data.
|
|
10
|
+
// - Share viewer: share-loader.js sets `window.__OMP_SESSION_DATA__` to
|
|
11
|
+
// a promise resolving to the session JSON (fetched + decrypted).
|
|
12
|
+
// The entire app lives in bootSession(); its body keeps the original
|
|
13
|
+
// one-level indentation to avoid a whole-file reindent.
|
|
14
|
+
function bootSession(data) {
|
|
15
|
+
const { header, entries, leafId: defaultLeafId, systemPrompt, tools, subSessions } = data;
|
|
16
|
+
|
|
17
|
+
// Session render context: scopes entry lookups and tool-view host
|
|
18
|
+
// wiring to one transcript (main session or an embedded subagent).
|
|
19
|
+
const mainSctx = { entries, prefix: '', idPrefix: 'entry-' };
|
|
16
20
|
|
|
17
21
|
// ============================================================
|
|
18
22
|
// URL PARAMETER HANDLING
|
|
@@ -630,29 +634,8 @@
|
|
|
630
634
|
return text.replace(/\t/g, ' ');
|
|
631
635
|
}
|
|
632
636
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (typeof value === 'string') return value;
|
|
636
|
-
if (value == null) return '';
|
|
637
|
-
return null;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function getLanguageFromPath(filePath) {
|
|
641
|
-
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
642
|
-
const extToLang = {
|
|
643
|
-
ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
|
|
644
|
-
py: 'python', rb: 'ruby', rs: 'rust', go: 'go', java: 'java',
|
|
645
|
-
c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp', cs: 'csharp',
|
|
646
|
-
php: 'php', sh: 'bash', bash: 'bash', zsh: 'bash',
|
|
647
|
-
sql: 'sql', html: 'html', css: 'css', scss: 'scss',
|
|
648
|
-
json: 'json', yaml: 'yaml', yml: 'yaml', xml: 'xml',
|
|
649
|
-
md: 'markdown', dockerfile: 'dockerfile'
|
|
650
|
-
};
|
|
651
|
-
return extToLang[ext];
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function findToolResult(toolCallId) {
|
|
655
|
-
for (const entry of entries) {
|
|
637
|
+
function findToolResult(toolCallId, entryList) {
|
|
638
|
+
for (const entry of entryList) {
|
|
656
639
|
if (entry.type === 'message' && entry.message.role === 'toolResult') {
|
|
657
640
|
if (entry.message.toolCallId === toolCallId) {
|
|
658
641
|
return entry.message;
|
|
@@ -721,911 +704,222 @@
|
|
|
721
704
|
// ============================================================
|
|
722
705
|
// TOOL CALL RENDERING
|
|
723
706
|
// ============================================================
|
|
724
|
-
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const start = offset == null ? 1 : offset;
|
|
749
|
-
const end = limit !== undefined ? start + limit - 1 : '';
|
|
750
|
-
html += '<span class="line-numbers">:' + start + (end ? '-' + end : '') + '</span>';
|
|
751
|
-
}
|
|
752
|
-
return html;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
function codeBlock(code, lang) {
|
|
756
|
-
if (code == null || code === '') return '';
|
|
757
|
-
const text = String(code);
|
|
758
|
-
let highlighted;
|
|
759
|
-
try {
|
|
760
|
-
highlighted = lang ? hljs.highlight(text, { language: lang }).value : escapeHtml(text);
|
|
761
|
-
} catch {
|
|
762
|
-
highlighted = escapeHtml(text);
|
|
763
|
-
}
|
|
764
|
-
return '<div class="tool-output"><pre><code class="hljs">' + highlighted + '</code></pre></div>';
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Per-tool renderers. Each accepts (name, args, result, ctx) and returns the inner HTML.
|
|
768
|
-
function renderBash(name, args, result, ctx) {
|
|
769
|
-
const command = str(args.command);
|
|
770
|
-
const cwd = str(args.cwd);
|
|
771
|
-
const env = args.env && typeof args.env === 'object' ? args.env : null;
|
|
772
|
-
const cmdDisplay = command === null ? invalidArgHtml() : escapeHtml(command || '...');
|
|
773
|
-
let prefix = '';
|
|
774
|
-
if (env) {
|
|
775
|
-
for (const [k, v] of Object.entries(env)) {
|
|
776
|
-
prefix += escapeHtml(k) + '=' + escapeHtml(String(v)) + ' ';
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
let html = '<div class="tool-command">$ ' + prefix + cmdDisplay + '</div>';
|
|
780
|
-
const badges = [];
|
|
781
|
-
if (cwd) badges.push('cwd=' + shortenPath(cwd));
|
|
782
|
-
if (args.timeout) badges.push('timeout=' + args.timeout + 's');
|
|
783
|
-
if (args.pty) badges.push('pty');
|
|
784
|
-
if (args.head) badges.push('head=' + args.head);
|
|
785
|
-
if (args.tail) badges.push('tail=' + args.tail);
|
|
786
|
-
if (badges.length) {
|
|
787
|
-
html += '<div class="tool-meta">' + badges.map(b => '<span class="tool-badge">' + escapeHtml(b) + '</span>').join(' ') + '</div>';
|
|
788
|
-
}
|
|
789
|
-
if (result) {
|
|
790
|
-
html += ctx.renderResultImages();
|
|
791
|
-
const output = ctx.getResultText().trim();
|
|
792
|
-
if (output) html += formatExpandableOutput(output, 5);
|
|
793
|
-
}
|
|
794
|
-
return html;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
function renderJsLike(name, args, result, ctx) {
|
|
798
|
-
let html = toolHead(name, '');
|
|
799
|
-
const cells = result && result.details && Array.isArray(result.details.cells) ? result.details.cells : null;
|
|
800
|
-
if (cells) {
|
|
801
|
-
for (const cell of cells) {
|
|
802
|
-
html += '<div class="tool-cell">';
|
|
803
|
-
if (cell && cell.title) html += '<div class="tool-cell-title">' + escapeHtml(String(cell.title)) + '</div>';
|
|
804
|
-
const code = cell && typeof cell.code === 'string' ? cell.code : '';
|
|
805
|
-
const lang = cell && cell.language === 'js' ? 'javascript' : 'python';
|
|
806
|
-
html += codeBlock(code, lang);
|
|
807
|
-
html += '</div>';
|
|
808
|
-
}
|
|
809
|
-
} else if (typeof args.input === 'string') {
|
|
810
|
-
html += codeBlock(args.input, null);
|
|
811
|
-
} else {
|
|
812
|
-
html += '<div class="tool-error">[missing input]</div>';
|
|
813
|
-
}
|
|
814
|
-
if (result) {
|
|
815
|
-
html += ctx.renderResultImages();
|
|
816
|
-
const output = ctx.getResultText();
|
|
817
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
818
|
-
}
|
|
819
|
-
return html;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
function renderRead(name, args, result, ctx) {
|
|
823
|
-
const filePath = str(args.file_path == null ? args.path : args.file_path);
|
|
824
|
-
let pathHtml = pathDisplay(filePath, args.offset, args.limit);
|
|
825
|
-
if (args.sel) pathHtml += '<span class="line-numbers">:' + escapeHtml(String(args.sel)) + '</span>';
|
|
826
|
-
let html = toolHead('read', pathHtml);
|
|
827
|
-
if (result) {
|
|
828
|
-
html += ctx.renderResultImages();
|
|
829
|
-
const output = ctx.getResultText();
|
|
830
|
-
const lang = filePath ? getLanguageFromPath(filePath) : null;
|
|
831
|
-
if (output) html += formatExpandableOutput(output, 10, lang);
|
|
832
|
-
}
|
|
833
|
-
return html;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
function renderWrite(name, args, result, ctx) {
|
|
837
|
-
const filePath = str(args.file_path == null ? args.path : args.file_path);
|
|
838
|
-
const content = str(args.content);
|
|
839
|
-
const pathHtml = filePath === null ? invalidArgHtml() : escapeHtml(shortenPath(filePath || ''));
|
|
840
|
-
const lineCount = (content != null && content !== '') ? content.split('\n').length : 0;
|
|
841
|
-
const badges = lineCount > 10 ? ['(' + lineCount + ' lines)'] : null;
|
|
842
|
-
let html = toolHead('write', pathHtml, badges);
|
|
843
|
-
if (content === null) {
|
|
844
|
-
html += '<div class="tool-error">[invalid content arg - expected string]</div>';
|
|
845
|
-
} else if (content) {
|
|
846
|
-
const lang = filePath ? getLanguageFromPath(filePath) : null;
|
|
847
|
-
html += formatExpandableOutput(content, 10, lang);
|
|
848
|
-
}
|
|
849
|
-
if (result) {
|
|
850
|
-
const output = ctx.getResultText().trim();
|
|
851
|
-
if (output) html += '<div class="tool-output"><div>' + escapeHtml(output) + '</div></div>';
|
|
852
|
-
}
|
|
853
|
-
return html;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
function renderEdit(name, args, result, ctx) {
|
|
857
|
-
const filePath = str(args.file_path == null ? args.path : args.file_path);
|
|
858
|
-
const pathHtml = filePath ? escapeHtml(shortenPath(filePath)) : '';
|
|
859
|
-
let html = toolHead('edit', pathHtml);
|
|
860
|
-
if (typeof args.input === 'string' && args.input.length) {
|
|
861
|
-
html += codeBlock(args.input, null);
|
|
862
|
-
} else if (Array.isArray(args.edits)) {
|
|
863
|
-
html += '<div class="tool-args">';
|
|
864
|
-
for (const e of args.edits) {
|
|
865
|
-
const op = e && typeof e.op === 'string' ? e.op : '?';
|
|
866
|
-
const sel = e && typeof e.sel === 'string' ? e.sel : '?';
|
|
867
|
-
html += '<div class="tool-arg"><span class="tool-arg-key">' + escapeHtml(op) + '</span> <span class="tool-arg-val">' + escapeHtml(sel) + '</span></div>';
|
|
868
|
-
}
|
|
869
|
-
html += '</div>';
|
|
870
|
-
}
|
|
871
|
-
if (result?.details?.diff) {
|
|
872
|
-
const diffLines = String(result.details.diff).split('\n');
|
|
873
|
-
html += '<div class="tool-diff">';
|
|
874
|
-
for (const line of diffLines) {
|
|
875
|
-
const cls = line.match(/^\+/) ? 'diff-added' : line.match(/^-/) ? 'diff-removed' : 'diff-context';
|
|
876
|
-
// Blank gap rows mark non-contiguous regions; show a unicode ellipsis.
|
|
877
|
-
const display = line.trim().length === 0 ? '\u2026' : replaceTabs(line);
|
|
878
|
-
html += '<div class="' + cls + '">' + escapeHtml(display) + '</div>';
|
|
879
|
-
}
|
|
880
|
-
html += '</div>';
|
|
881
|
-
} else if (result) {
|
|
882
|
-
const output = ctx.getResultText().trim();
|
|
883
|
-
if (output) html += '<div class="tool-output"><pre>' + escapeHtml(output) + '</pre></div>';
|
|
884
|
-
}
|
|
885
|
-
return html;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
function renderAstEdit(name, args, result, ctx) {
|
|
889
|
-
const lang = args.lang || null;
|
|
890
|
-
const paths = Array.isArray(args.paths) ? args.paths.map(p => shortenPath(String(p))).join(', ') : (args.path ? shortenPath(String(args.path)) : '');
|
|
891
|
-
const pathHtml = paths ? escapeHtml(paths) : '';
|
|
892
|
-
const badges = [];
|
|
893
|
-
if (lang) badges.push(lang);
|
|
894
|
-
if (args.glob) badges.push('glob=' + args.glob);
|
|
895
|
-
if (args.sel) badges.push('sel=' + args.sel);
|
|
896
|
-
let html = toolHead('ast_edit', pathHtml, badges);
|
|
897
|
-
if (Array.isArray(args.ops)) {
|
|
898
|
-
for (const op of args.ops) {
|
|
899
|
-
html += '<div class="tool-cell">';
|
|
900
|
-
html += '<div class="tool-cell-title">pattern</div>';
|
|
901
|
-
html += codeBlock(String(op?.pat == null ? '' : op.pat), lang);
|
|
902
|
-
html += '<div class="tool-cell-title">replacement</div>';
|
|
903
|
-
html += codeBlock(String(op?.out == null ? '' : op.out), lang);
|
|
904
|
-
html += '</div>';
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
if (result) {
|
|
908
|
-
const output = ctx.getResultText();
|
|
909
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
910
|
-
}
|
|
911
|
-
return html;
|
|
707
|
+
//
|
|
708
|
+
// Tool calls render through the bundled <omp-tool-view> web component
|
|
709
|
+
// (tool-views.generated.js — the same React renderers collab-web uses).
|
|
710
|
+
// Payloads are handed over via a global store keyed by data-key, which
|
|
711
|
+
// survives innerHTML serialization and cloneNode round trips.
|
|
712
|
+
|
|
713
|
+
const TOOL_VIEW_DATA = new Map();
|
|
714
|
+
globalThis.__OMP_TOOL_VIEW_DATA = TOOL_VIEW_DATA;
|
|
715
|
+
let toolViewSeq = 0;
|
|
716
|
+
|
|
717
|
+
function renderToolCall(call, sctx) {
|
|
718
|
+
const result = findToolResult(call.id, sctx.entries);
|
|
719
|
+
const statusClass = result ? (result.isError ? 'error' : 'success') : 'pending';
|
|
720
|
+
const key = 'tv' + (++toolViewSeq);
|
|
721
|
+
TOOL_VIEW_DATA.set(key, {
|
|
722
|
+
name: call.name,
|
|
723
|
+
args: call.arguments || {},
|
|
724
|
+
result: result || undefined,
|
|
725
|
+
host: {
|
|
726
|
+
hasAgent: (id) => !!lookupSubSession(sctx.prefix, id),
|
|
727
|
+
openAgent: (id) => openSubSession(joinKey(sctx.prefix, id)),
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
return '<omp-tool-view class="tool-execution ' + statusClass + '" data-key="' + key + '" open></omp-tool-view>';
|
|
912
731
|
}
|
|
913
732
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
if (Array.isArray(args.pat)) {
|
|
923
|
-
for (const pat of args.pat) {
|
|
924
|
-
html += '<div class="tool-cell">' + codeBlock(String(pat == null ? '' : pat), lang) + '</div>';
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
if (result) {
|
|
928
|
-
const output = ctx.getResultText();
|
|
929
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
930
|
-
}
|
|
931
|
-
return html;
|
|
932
|
-
}
|
|
733
|
+
// ============================================================
|
|
734
|
+
// SUB-SESSION OVERLAY
|
|
735
|
+
// ============================================================
|
|
736
|
+
//
|
|
737
|
+
// Task tool cards expose agent chips (wired through the payload `host`
|
|
738
|
+
// above); clicking one opens that subagent's transcript in a stacked
|
|
739
|
+
// overlay. Keys are slash-joined agent ids relative to the main
|
|
740
|
+
// session: top-level agent 'ToolAsk', its child 'ToolAsk/Helper'.
|
|
933
741
|
|
|
934
|
-
function
|
|
935
|
-
|
|
936
|
-
const pathHtml = args.path ? escapeHtml(shortenPath(String(args.path))) : escapeHtml('.');
|
|
937
|
-
const patHtml = pattern === null ? invalidArgHtml() : escapeHtml(pattern);
|
|
938
|
-
let head = '<span class="tool-name">grep</span> <span class="tool-pattern">/' + patHtml + '/</span>';
|
|
939
|
-
head += ' <span class="tool-arg-key">in</span> <span class="tool-path">' + pathHtml + '</span>';
|
|
940
|
-
const badges = [];
|
|
941
|
-
if (args.glob) badges.push('glob=' + args.glob);
|
|
942
|
-
if (args.type) badges.push('type=' + args.type);
|
|
943
|
-
if (args.i) badges.push('i');
|
|
944
|
-
if (args.multiline) badges.push('multiline');
|
|
945
|
-
for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(b) + '</span>';
|
|
946
|
-
let html = '<div class="tool-header">' + head + '</div>';
|
|
947
|
-
if (result) {
|
|
948
|
-
const output = ctx.getResultText();
|
|
949
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
950
|
-
}
|
|
951
|
-
return html;
|
|
742
|
+
function joinKey(prefix, id) {
|
|
743
|
+
return prefix ? prefix + '/' + id : id;
|
|
952
744
|
}
|
|
953
745
|
|
|
954
|
-
function
|
|
955
|
-
|
|
956
|
-
const patHtml = paths ? escapeHtml(paths) : invalidArgHtml();
|
|
957
|
-
const badges = [];
|
|
958
|
-
if (args.limit) badges.push('limit=' + args.limit);
|
|
959
|
-
if (args.hidden === false) badges.push('no-hidden');
|
|
960
|
-
let html = toolHead('find', '<span class="tool-pattern">' + patHtml + '</span>', badges.length ? badges : null);
|
|
961
|
-
if (result) {
|
|
962
|
-
const output = ctx.getResultText();
|
|
963
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
964
|
-
}
|
|
965
|
-
return html;
|
|
746
|
+
function lookupSubSession(prefix, id) {
|
|
747
|
+
return subSessions ? subSessions[joinKey(prefix, id)] : undefined;
|
|
966
748
|
}
|
|
967
749
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
if (args.new_name) head += ' <span class="tool-arg-key">→</span> <span class="tool-arg-val">' + escapeHtml(String(args.new_name)) + '</span>';
|
|
980
|
-
let html = '<div class="tool-header">' + head + '</div>';
|
|
981
|
-
if (result) {
|
|
982
|
-
const output = ctx.getResultText();
|
|
983
|
-
if (output) html += formatExpandableOutput(output, 12);
|
|
750
|
+
// Render context per sub-session (entries scoped to that transcript).
|
|
751
|
+
const subSctxCache = new Map();
|
|
752
|
+
function getSubSctx(key) {
|
|
753
|
+
let sctx = subSctxCache.get(key);
|
|
754
|
+
if (!sctx) {
|
|
755
|
+
sctx = {
|
|
756
|
+
entries: subSessions[key].entries,
|
|
757
|
+
prefix: key,
|
|
758
|
+
idPrefix: 'sub-' + key.replace(/[^A-Za-z0-9_-]/g, '_') + '-entry-',
|
|
759
|
+
};
|
|
760
|
+
subSctxCache.set(key, sctx);
|
|
984
761
|
}
|
|
985
|
-
return
|
|
762
|
+
return sctx;
|
|
986
763
|
}
|
|
987
764
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
765
|
+
/**
|
|
766
|
+
* Root-to-leaf path through an arbitrary entry list (subagent
|
|
767
|
+
* transcripts are linear chains; same parent-walk as getPath).
|
|
768
|
+
*/
|
|
769
|
+
function getPathIn(entryList, targetId) {
|
|
770
|
+
const map = new Map();
|
|
771
|
+
for (const e of entryList) map.set(e.id, e);
|
|
772
|
+
let current = targetId ? map.get(targetId) : undefined;
|
|
773
|
+
if (!current && entryList.length > 0) current = entryList[entryList.length - 1];
|
|
774
|
+
const path = [];
|
|
775
|
+
while (current) {
|
|
776
|
+
path.unshift(current);
|
|
777
|
+
if (!current.parentId || current.parentId === current.id) break;
|
|
778
|
+
current = map.get(current.parentId);
|
|
994
779
|
}
|
|
995
|
-
return
|
|
780
|
+
return path;
|
|
996
781
|
}
|
|
997
782
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
783
|
+
const overlayStack = []; // slash-joined keys, root-first chain
|
|
784
|
+
const subSessionBodyCache = new Map(); // key -> rendered body element
|
|
785
|
+
let subOverlayEl = null;
|
|
786
|
+
let subOverlayLastFocus = null;
|
|
787
|
+
|
|
788
|
+
function ensureSubOverlay() {
|
|
789
|
+
if (subOverlayEl) return subOverlayEl;
|
|
790
|
+
subOverlayEl = document.createElement('div');
|
|
791
|
+
subOverlayEl.id = 'subsession-overlay';
|
|
792
|
+
subOverlayEl.innerHTML = `
|
|
793
|
+
<div class="subsession-backdrop"></div>
|
|
794
|
+
<div class="subsession-panel" role="dialog" aria-modal="true" aria-label="Subagent session" tabindex="-1">
|
|
795
|
+
<div class="subsession-header">
|
|
796
|
+
<nav class="subsession-breadcrumb" aria-label="Subagent breadcrumb"></nav>
|
|
797
|
+
<button type="button" class="subsession-close" title="Close (Esc)" aria-label="Close subagent view">×</button>
|
|
798
|
+
</div>
|
|
799
|
+
<div class="subsession-meta"></div>
|
|
800
|
+
<div class="subsession-body"></div>
|
|
801
|
+
</div>`;
|
|
802
|
+
subOverlayEl.querySelector('.subsession-backdrop').addEventListener('click', popSubSession);
|
|
803
|
+
subOverlayEl.querySelector('.subsession-close').addEventListener('click', closeAllSubSessions);
|
|
804
|
+
document.body.appendChild(subOverlayEl);
|
|
805
|
+
return subOverlayEl;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function buildSubSessionBody(key) {
|
|
809
|
+
let body = subSessionBodyCache.get(key);
|
|
810
|
+
if (body) return body;
|
|
811
|
+
const sub = subSessions[key];
|
|
812
|
+
const sctx = getSubSctx(key);
|
|
813
|
+
body = document.createElement('div');
|
|
814
|
+
body.className = 'subsession-messages';
|
|
815
|
+
for (const entry of getPathIn(sub.entries, sub.leafId)) {
|
|
816
|
+
const node = renderEntryToNode(entry, sctx);
|
|
817
|
+
if (node) body.appendChild(node);
|
|
818
|
+
}
|
|
819
|
+
if (!body.firstChild) {
|
|
820
|
+
const empty = document.createElement('div');
|
|
821
|
+
empty.className = 'subsession-empty';
|
|
822
|
+
empty.textContent = '(no renderable entries)';
|
|
823
|
+
body.appendChild(empty);
|
|
824
|
+
}
|
|
825
|
+
subSessionBodyCache.set(key, body);
|
|
826
|
+
return body;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function subSessionMetaText(key) {
|
|
830
|
+
const sub = subSessions[key];
|
|
831
|
+
const stats = computeStats(sub.entries);
|
|
832
|
+
const parts = [];
|
|
833
|
+
if (stats.models.length > 0) parts.push(stats.models.join(', '));
|
|
834
|
+
parts.push(sub.entries.length + (sub.entries.length === 1 ? ' entry' : ' entries'));
|
|
835
|
+
return parts.join(' · ');
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function renderSubOverlay() {
|
|
839
|
+
const key = overlayStack[overlayStack.length - 1];
|
|
840
|
+
const el = ensureSubOverlay();
|
|
841
|
+
|
|
842
|
+
const crumbs = el.querySelector('.subsession-breadcrumb');
|
|
843
|
+
crumbs.innerHTML = '';
|
|
844
|
+
const segments = key.split('/');
|
|
845
|
+
for (let i = 0; i < segments.length; i++) {
|
|
846
|
+
if (i > 0) {
|
|
847
|
+
const sep = document.createElement('span');
|
|
848
|
+
sep.className = 'subsession-crumb-sep';
|
|
849
|
+
sep.textContent = '›';
|
|
850
|
+
crumbs.appendChild(sep);
|
|
1028
851
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
if (args.isolated) badges.push('isolated');
|
|
1043
|
-
let html = toolHead('task', '', badges);
|
|
1044
|
-
const description = str(args.description);
|
|
1045
|
-
const assignment = str(args.assignment);
|
|
1046
|
-
if (description || assignment) {
|
|
1047
|
-
html += '<div class="tool-args">';
|
|
1048
|
-
if (description) html += '<div class="tool-arg"><span class="tool-arg-key">' + escapeHtml(description) + '</span></div>';
|
|
1049
|
-
if (assignment) html += '<div class="tool-arg">' + escapeHtml(assignment) + '</div>';
|
|
1050
|
-
html += '</div>';
|
|
1051
|
-
}
|
|
1052
|
-
if (result) {
|
|
1053
|
-
const output = ctx.getResultText();
|
|
1054
|
-
if (output) html += formatExpandableOutput(output, 12);
|
|
1055
|
-
}
|
|
1056
|
-
return html;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
function renderWebSearch(name, args, result, ctx) {
|
|
1060
|
-
const query = str(args.query);
|
|
1061
|
-
const queryHtml = query === null ? invalidArgHtml() : escapeHtml(query);
|
|
1062
|
-
const badges = [];
|
|
1063
|
-
if (args.recency) badges.push('recency=' + args.recency);
|
|
1064
|
-
if (args.limit) badges.push('limit=' + args.limit);
|
|
1065
|
-
let html = toolHead('web_search', '<span class="tool-pattern">' + queryHtml + '</span>', badges);
|
|
1066
|
-
if (result) {
|
|
1067
|
-
const output = ctx.getResultText();
|
|
1068
|
-
if (output) html += formatExpandableOutput(output, 12, 'markdown');
|
|
1069
|
-
}
|
|
1070
|
-
return html;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
function renderFetch(name, args, result, ctx) {
|
|
1074
|
-
const url = str(args.url) || '';
|
|
1075
|
-
const badges = args.method ? [String(args.method)] : null;
|
|
1076
|
-
let html = toolHead('fetch', '<span class="tool-path">' + escapeHtml(url) + '</span>', badges);
|
|
1077
|
-
if (result) {
|
|
1078
|
-
const output = ctx.getResultText();
|
|
1079
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
1080
|
-
}
|
|
1081
|
-
return html;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
function renderDebug(name, args, result, ctx) {
|
|
1085
|
-
const action = str(args.action) || '?';
|
|
1086
|
-
const badges = [];
|
|
1087
|
-
if (args.adapter) badges.push(args.adapter);
|
|
1088
|
-
if (args.program) badges.push('program=' + shortenPath(String(args.program)));
|
|
1089
|
-
if (args.file) badges.push('file=' + shortenPath(String(args.file)));
|
|
1090
|
-
if (args.line) badges.push('line=' + args.line);
|
|
1091
|
-
let head = '<span class="tool-name">debug</span> <span class="tool-badge">' + escapeHtml(action) + '</span>';
|
|
1092
|
-
for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(String(b)) + '</span>';
|
|
1093
|
-
let html = '<div class="tool-header">' + head + '</div>';
|
|
1094
|
-
if (args.expression) html += codeBlock(String(args.expression));
|
|
1095
|
-
if (result) {
|
|
1096
|
-
const output = ctx.getResultText();
|
|
1097
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
1098
|
-
}
|
|
1099
|
-
return html;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
function renderBrowser(name, args, result, ctx) {
|
|
1103
|
-
const action = str(args.action) || '?';
|
|
1104
|
-
const tabName = str(args.name);
|
|
1105
|
-
const badges = [];
|
|
1106
|
-
if (tabName) badges.push('name=' + tabName);
|
|
1107
|
-
if (args.url) badges.push(String(args.url));
|
|
1108
|
-
if (args.app && typeof args.app === 'object') {
|
|
1109
|
-
if (args.app.path) badges.push('app=' + shortenPath(String(args.app.path)));
|
|
1110
|
-
else if (args.app.cdp_url) badges.push('cdp=' + String(args.app.cdp_url));
|
|
1111
|
-
}
|
|
1112
|
-
if (args.all) badges.push('all');
|
|
1113
|
-
if (args.kill) badges.push('kill');
|
|
1114
|
-
let head = '<span class="tool-name">browser</span> <span class="tool-badge">' + escapeHtml(action) + '</span>';
|
|
1115
|
-
for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(String(b)) + '</span>';
|
|
1116
|
-
let html = '<div class="tool-header">' + head + '</div>';
|
|
1117
|
-
if (action === 'run' && args.code) {
|
|
1118
|
-
html += codeBlock(String(args.code), 'javascript');
|
|
1119
|
-
}
|
|
1120
|
-
if (result) {
|
|
1121
|
-
html += ctx.renderResultImages();
|
|
1122
|
-
const output = ctx.getResultText();
|
|
1123
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
1124
|
-
}
|
|
1125
|
-
return html;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
function renderInspectImage(name, args, result, ctx) {
|
|
1129
|
-
const p = str(args.path == null ? args.url : args.path) || '';
|
|
1130
|
-
let html = toolHead('inspect_image', escapeHtml(shortenPath(p)));
|
|
1131
|
-
if (result) {
|
|
1132
|
-
html += ctx.renderResultImages();
|
|
1133
|
-
const output = ctx.getResultText();
|
|
1134
|
-
if (output) html += formatExpandableOutput(output, 8);
|
|
1135
|
-
}
|
|
1136
|
-
return html;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
function renderGenerateImage(name, args, result, ctx) {
|
|
1140
|
-
const subject = str(args.subject) || '';
|
|
1141
|
-
const badges = args.aspect_ratio ? [String(args.aspect_ratio)] : null;
|
|
1142
|
-
let html = toolHead('generate_image', '', badges);
|
|
1143
|
-
if (subject) html += '<div class="tool-output"><div>' + escapeHtml(subject) + '</div></div>';
|
|
1144
|
-
if (result) {
|
|
1145
|
-
html += ctx.renderResultImages();
|
|
1146
|
-
}
|
|
1147
|
-
return html;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function renderAsk(name, args, result, ctx) {
|
|
1151
|
-
let html = toolHead('ask');
|
|
1152
|
-
const questions = Array.isArray(args.questions) ? args.questions : null;
|
|
1153
|
-
if (questions) {
|
|
1154
|
-
html += '<div class="tool-args">';
|
|
1155
|
-
for (const q of questions) {
|
|
1156
|
-
html += '<div class="tool-arg"><span class="tool-arg-key">Q:</span> ' + escapeHtml(String(q?.question || '')) + '</div>';
|
|
1157
|
-
if (Array.isArray(q?.options)) {
|
|
1158
|
-
for (const opt of q.options) {
|
|
1159
|
-
html += '<div class="tool-arg"><span class="tool-arg-key"> -</span> ' + escapeHtml(String(opt?.label || '')) + '</div>';
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
852
|
+
if (i === segments.length - 1) {
|
|
853
|
+
const span = document.createElement('span');
|
|
854
|
+
span.className = 'subsession-crumb current';
|
|
855
|
+
span.textContent = segments[i];
|
|
856
|
+
crumbs.appendChild(span);
|
|
857
|
+
} else {
|
|
858
|
+
const btn = document.createElement('button');
|
|
859
|
+
btn.type = 'button';
|
|
860
|
+
btn.className = 'subsession-crumb';
|
|
861
|
+
btn.textContent = segments[i];
|
|
862
|
+
const ancestorKey = segments.slice(0, i + 1).join('/');
|
|
863
|
+
btn.addEventListener('click', () => popSubSessionTo(ancestorKey));
|
|
864
|
+
crumbs.appendChild(btn);
|
|
1162
865
|
}
|
|
1163
|
-
html += '</div>';
|
|
1164
|
-
}
|
|
1165
|
-
if (result) {
|
|
1166
|
-
const output = ctx.getResultText();
|
|
1167
|
-
if (output) html += formatExpandableOutput(output, 8);
|
|
1168
866
|
}
|
|
1169
|
-
return html;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
function renderResolve(name, args, result, ctx) {
|
|
1173
|
-
const action = str(args.action) || '?';
|
|
1174
|
-
let html = toolHead('resolve', '', [action]);
|
|
1175
|
-
if (args.reason) html += '<div class="tool-output"><div>' + escapeHtml(String(args.reason)) + '</div></div>';
|
|
1176
|
-
if (result) {
|
|
1177
|
-
const output = ctx.getResultText();
|
|
1178
|
-
if (output) html += formatExpandableOutput(output, 6);
|
|
1179
|
-
}
|
|
1180
|
-
return html;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
function renderGh(name, args, result, ctx) {
|
|
1184
|
-
const op = str(args.op);
|
|
1185
|
-
const badges = [];
|
|
1186
|
-
if (op) badges.push(op);
|
|
1187
|
-
if (args.repo) badges.push(String(args.repo));
|
|
1188
|
-
if (args.issue) badges.push('#' + args.issue);
|
|
1189
|
-
if (args.pr) badges.push(Array.isArray(args.pr) ? 'PRs ' + args.pr.join(',') : 'PR ' + args.pr);
|
|
1190
|
-
if (args.branch) badges.push('branch=' + args.branch);
|
|
1191
|
-
if (args.query) badges.push('query=' + truncate(String(args.query), 60));
|
|
1192
|
-
if (args.run) badges.push('run=' + args.run);
|
|
1193
|
-
if (args.title) badges.push('title=' + truncate(String(args.title), 40));
|
|
1194
|
-
let html = toolHead(name, '', badges);
|
|
1195
|
-
if (args.body) html += '<div class="tool-output"><div>' + escapeHtml(truncate(String(args.body), 400)) + '</div></div>';
|
|
1196
|
-
if (result) {
|
|
1197
|
-
const output = ctx.getResultText();
|
|
1198
|
-
if (output) html += formatExpandableOutput(output, 12, 'markdown');
|
|
1199
|
-
}
|
|
1200
|
-
return html;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
function renderMermaid(name, args, result, ctx) {
|
|
1204
|
-
let html = toolHead('render_mermaid');
|
|
1205
|
-
const code = args.code || args.source;
|
|
1206
|
-
if (code) html += codeBlock(String(code), 'mermaid');
|
|
1207
|
-
if (result) {
|
|
1208
|
-
html += ctx.renderResultImages();
|
|
1209
|
-
const output = ctx.getResultText();
|
|
1210
|
-
if (output) html += formatExpandableOutput(output, 6);
|
|
1211
|
-
}
|
|
1212
|
-
return html;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
function renderYield(name, args, result, ctx) {
|
|
1216
|
-
let html = toolHead('yield');
|
|
1217
|
-
if (args.data !== undefined) {
|
|
1218
|
-
html += '<div class="tool-output"><pre>' + escapeHtml(JSON.stringify(args.data, null, 2)) + '</pre></div>';
|
|
1219
|
-
}
|
|
1220
|
-
if (result) {
|
|
1221
|
-
const output = ctx.getResultText();
|
|
1222
|
-
if (output) html += formatExpandableOutput(output, 6);
|
|
1223
|
-
}
|
|
1224
|
-
return html;
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
function renderReportFinding(name, args, result, ctx) {
|
|
1228
|
-
const badges = [];
|
|
1229
|
-
if (args.priority) badges.push('priority=' + args.priority);
|
|
1230
|
-
if (args.confidence != null) badges.push('confidence=' + args.confidence);
|
|
1231
|
-
if (args.file_path) badges.push(shortenPath(String(args.file_path)));
|
|
1232
|
-
let html = toolHead('report_finding', args.title ? escapeHtml(String(args.title)) : '', badges);
|
|
1233
|
-
if (args.body) html += '<div class="tool-output"><div>' + escapeHtml(String(args.body)) + '</div></div>';
|
|
1234
|
-
return html;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
function renderReportToolIssue(name, args, result, ctx) {
|
|
1238
|
-
const pathHtml = args.tool ? '<span class="tool-badge">' + escapeHtml(String(args.tool)) + '</span>' : '';
|
|
1239
|
-
let html = toolHead('report_tool_issue', pathHtml);
|
|
1240
|
-
if (args.report) html += '<div class="tool-output"><div>' + escapeHtml(String(args.report)) + '</div></div>';
|
|
1241
|
-
return html;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
function renderJob(name, args, result, ctx) {
|
|
1245
|
-
const badges = [];
|
|
1246
|
-
const pollIds = Array.isArray(args.poll) ? args.poll : Array.isArray(args.jobs) ? args.jobs : Array.isArray(args.jobIds) ? args.jobIds : [];
|
|
1247
|
-
const cancelIds = Array.isArray(args.cancel) ? args.cancel : args.jobId ? [String(args.jobId)] : [];
|
|
1248
|
-
if (cancelIds.length > 0) badges.push('cancel ' + cancelIds.length);
|
|
1249
|
-
if (pollIds.length > 0) badges.push('poll ' + pollIds.length);
|
|
1250
|
-
let html = toolHead('job', '', badges);
|
|
1251
|
-
if (result) {
|
|
1252
|
-
const output = ctx.getResultText();
|
|
1253
|
-
if (output) html += formatExpandableOutput(output, 8);
|
|
1254
|
-
}
|
|
1255
|
-
return html;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// Parse `*** Cell <attrs>` headers (canonical), plus legacy
|
|
1259
|
-
// `*** Begin <LANG>` headers and `===== <info> =====` bars used in
|
|
1260
|
-
// older transcripts. Cells emitted before each format cutover still
|
|
1261
|
-
// need to render in HTML exports.
|
|
1262
|
-
function parseEvalCells(input) {
|
|
1263
|
-
const text = String(input);
|
|
1264
|
-
if (/^[*]{2,}\s*Cell\b/im.test(text)) return parseEvalCellsCell(text);
|
|
1265
|
-
if (/^[*]{2,}\s*Begin\b/im.test(text)) return parseEvalCellsBegin(text);
|
|
1266
|
-
return parseEvalCellsLegacy(text);
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
function evalLangAlias(token) {
|
|
1270
|
-
const t = String(token || '').toUpperCase();
|
|
1271
|
-
if (t === 'PY' || t === 'PYTHON' || t === 'IPY' || t === 'IPYTHON') return 'py';
|
|
1272
|
-
if (t === 'JS' || t === 'JAVASCRIPT') return 'js';
|
|
1273
|
-
if (t === 'TS' || t === 'TYPESCRIPT') return 'ts';
|
|
1274
|
-
return null;
|
|
1275
|
-
}
|
|
1276
867
|
|
|
1277
|
-
|
|
1278
|
-
// segments. Mirrors `tokenizeCellAttrs` in src/eval/parse.ts.
|
|
1279
|
-
function tokenizeCellAttrsHtml(input) {
|
|
1280
|
-
const tokens = [];
|
|
1281
|
-
let i = 0;
|
|
1282
|
-
while (i < input.length) {
|
|
1283
|
-
while (i < input.length && /\s/.test(input[i])) i++;
|
|
1284
|
-
if (i >= input.length) break;
|
|
1285
|
-
let tok = '';
|
|
1286
|
-
while (i < input.length && !/\s/.test(input[i])) {
|
|
1287
|
-
const ch = input[i];
|
|
1288
|
-
if (ch === '"' || ch === "'") {
|
|
1289
|
-
tok += ch; i++;
|
|
1290
|
-
while (i < input.length && input[i] !== ch) { tok += input[i]; i++; }
|
|
1291
|
-
if (i < input.length) { tok += input[i]; i++; }
|
|
1292
|
-
} else { tok += ch; i++; }
|
|
1293
|
-
}
|
|
1294
|
-
tokens.push(tok);
|
|
1295
|
-
}
|
|
1296
|
-
return tokens;
|
|
1297
|
-
}
|
|
868
|
+
el.querySelector('.subsession-meta').textContent = subSessionMetaText(key);
|
|
1298
869
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
const ATTR = /^([a-zA-Z][\w-]*)(?::(?:"([^"]*)"|'([^']*)'|(.*)))?$/;
|
|
1304
|
-
const DUR = /^\d+(?:ms|s|m)?$/;
|
|
1305
|
-
const ID_KEYS = ['id', 'title', 'name', 'cell', 'file', 'label'];
|
|
1306
|
-
const T_KEYS = ['t', 'timeout', 'duration', 'time'];
|
|
1307
|
-
const RST_KEYS = ['rst', 'reset'];
|
|
1308
|
-
const lines = text.split('\n');
|
|
1309
|
-
if (lines.length && lines[lines.length - 1] === '') lines.pop();
|
|
1310
|
-
const cells = [];
|
|
1311
|
-
let i = 0;
|
|
1312
|
-
while (i < lines.length && lines[i].trim() === '') i++;
|
|
1313
|
-
while (i < lines.length) {
|
|
1314
|
-
const m = CELL.exec(lines[i]);
|
|
1315
|
-
if (!m) { i++; continue; }
|
|
1316
|
-
const tokens = tokenizeCellAttrsHtml(m[1] || '');
|
|
1317
|
-
let lang = null;
|
|
1318
|
-
let title = '';
|
|
1319
|
-
const attrs = [];
|
|
1320
|
-
let bareReset = false;
|
|
1321
|
-
const titleParts = [];
|
|
1322
|
-
for (const tok of tokens) {
|
|
1323
|
-
const lower = tok.toLowerCase();
|
|
1324
|
-
if (RST_KEYS.indexOf(lower) >= 0) { bareReset = true; continue; }
|
|
1325
|
-
const am = ATTR.exec(tok);
|
|
1326
|
-
if (am && tok.indexOf(':') >= 0) {
|
|
1327
|
-
const key = am[1].toLowerCase();
|
|
1328
|
-
const value = am[2] !== undefined ? am[2] : am[3] !== undefined ? am[3] : (am[4] || '');
|
|
1329
|
-
const lc = evalLangAlias(key);
|
|
1330
|
-
if (lc) {
|
|
1331
|
-
if (!lang) lang = lc;
|
|
1332
|
-
if (!title && value) title = value;
|
|
1333
|
-
continue;
|
|
1334
|
-
}
|
|
1335
|
-
if (ID_KEYS.indexOf(key) >= 0) { if (!title) title = value; continue; }
|
|
1336
|
-
if (T_KEYS.indexOf(key) >= 0) { attrs.push('t=' + value); continue; }
|
|
1337
|
-
if (RST_KEYS.indexOf(key) >= 0) { attrs.push('rst'); continue; }
|
|
1338
|
-
continue;
|
|
1339
|
-
}
|
|
1340
|
-
const lc = evalLangAlias(tok);
|
|
1341
|
-
if (lc && !lang) { lang = lc; continue; }
|
|
1342
|
-
if (DUR.test(tok)) { attrs.push('t=' + tok); continue; }
|
|
1343
|
-
titleParts.push(tok);
|
|
1344
|
-
}
|
|
1345
|
-
if (!title && titleParts.length) title = titleParts.join(' ');
|
|
1346
|
-
if (bareReset) attrs.push('rst');
|
|
1347
|
-
lang = lang || 'py';
|
|
1348
|
-
i++;
|
|
1349
|
-
const codeLines = [];
|
|
1350
|
-
while (i < lines.length) {
|
|
1351
|
-
if (END.test(lines[i])) { i++; break; }
|
|
1352
|
-
if (CELL.test(lines[i])) break;
|
|
1353
|
-
codeLines.push(lines[i]);
|
|
1354
|
-
i++;
|
|
1355
|
-
}
|
|
1356
|
-
while (codeLines.length && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
|
|
1357
|
-
cells.push({ lang, title, attrs, code: codeLines.join('\n') });
|
|
1358
|
-
while (i < lines.length && lines[i].trim() === '') i++;
|
|
1359
|
-
}
|
|
1360
|
-
return cells;
|
|
1361
|
-
}
|
|
870
|
+
const bodyHost = el.querySelector('.subsession-body');
|
|
871
|
+
bodyHost.innerHTML = '';
|
|
872
|
+
bodyHost.appendChild(buildSubSessionBody(key));
|
|
873
|
+
bodyHost.scrollTop = 0;
|
|
1362
874
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
const BEGIN = new RegExp('^' + STARS + '\\s*Begin\\b\\s*(\\S+)?\\s*$', 'i');
|
|
1366
|
-
const END = new RegExp('^' + STARS + '\\s*End\\b.*$', 'i');
|
|
1367
|
-
const TITLE = new RegExp('^' + STARS + '\\s*Title\\s*:\\s*(.+?)\\s*$', 'i');
|
|
1368
|
-
const TIMEOUT = new RegExp('^' + STARS + '\\s*Timeout\\s*:\\s*(\\S+)\\s*$', 'i');
|
|
1369
|
-
const RESET = new RegExp('^' + STARS + '\\s*Reset\\s*$', 'i');
|
|
1370
|
-
const lines = text.split('\n');
|
|
1371
|
-
if (lines.length && lines[lines.length - 1] === '') lines.pop();
|
|
1372
|
-
const cells = [];
|
|
1373
|
-
let i = 0;
|
|
1374
|
-
while (i < lines.length && lines[i].trim() === '') i++;
|
|
1375
|
-
while (i < lines.length) {
|
|
1376
|
-
const beginMatch = BEGIN.exec(lines[i]);
|
|
1377
|
-
if (!beginMatch) { i++; continue; }
|
|
1378
|
-
const lang = evalLangAlias(beginMatch[1]) || 'py';
|
|
1379
|
-
i++;
|
|
1380
|
-
let title = '';
|
|
1381
|
-
const attrs = [];
|
|
1382
|
-
while (i < lines.length) {
|
|
1383
|
-
const tm = TITLE.exec(lines[i]);
|
|
1384
|
-
if (tm) { if (!title) title = tm[1]; i++; continue; }
|
|
1385
|
-
const to = TIMEOUT.exec(lines[i]);
|
|
1386
|
-
if (to) { attrs.push('t=' + to[1]); i++; continue; }
|
|
1387
|
-
if (RESET.test(lines[i])) { attrs.push('rst'); i++; continue; }
|
|
1388
|
-
break;
|
|
1389
|
-
}
|
|
1390
|
-
const codeLines = [];
|
|
1391
|
-
while (i < lines.length) {
|
|
1392
|
-
if (END.test(lines[i])) { i++; break; }
|
|
1393
|
-
if (BEGIN.test(lines[i])) break;
|
|
1394
|
-
codeLines.push(lines[i]);
|
|
1395
|
-
i++;
|
|
1396
|
-
}
|
|
1397
|
-
while (codeLines.length && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
|
|
1398
|
-
cells.push({ lang, title, attrs, code: codeLines.join('\n') });
|
|
1399
|
-
while (i < lines.length && lines[i].trim() === '') i++;
|
|
1400
|
-
}
|
|
1401
|
-
return cells;
|
|
875
|
+
el.classList.add('open');
|
|
876
|
+
el.querySelector('.subsession-panel').focus();
|
|
1402
877
|
}
|
|
1403
878
|
|
|
1404
|
-
function
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
let inheritedLang = 'py';
|
|
1409
|
-
let current = null;
|
|
1410
|
-
for (const line of lines) {
|
|
1411
|
-
const m = line.match(HEADER);
|
|
1412
|
-
if (m) {
|
|
1413
|
-
if (current) cells.push(current);
|
|
1414
|
-
const info = m[1] || '';
|
|
1415
|
-
let lang = inheritedLang;
|
|
1416
|
-
let title = '';
|
|
1417
|
-
const langMatch = info.match(/^(py|js|ts)(?::"([^"]*)")?/);
|
|
1418
|
-
if (langMatch) {
|
|
1419
|
-
lang = langMatch[1];
|
|
1420
|
-
if (langMatch[2]) title = langMatch[2];
|
|
1421
|
-
}
|
|
1422
|
-
if (!title) {
|
|
1423
|
-
const idMatch = info.match(/id:"([^"]*)"/);
|
|
1424
|
-
if (idMatch) title = idMatch[1];
|
|
1425
|
-
}
|
|
1426
|
-
inheritedLang = lang;
|
|
1427
|
-
const attrs = [];
|
|
1428
|
-
const tMatch = info.match(/(?:^|\s)t:(\S+)/);
|
|
1429
|
-
if (tMatch) attrs.push('t=' + tMatch[1]);
|
|
1430
|
-
if (/(?:^|\s)rst(?:\s|$)/.test(info)) attrs.push('rst');
|
|
1431
|
-
current = { lang, title, attrs, code: '' };
|
|
1432
|
-
} else {
|
|
1433
|
-
if (!current) current = { lang: inheritedLang, title: '', attrs: [], code: '' };
|
|
1434
|
-
current.code += (current.code ? '\n' : '') + line;
|
|
1435
|
-
}
|
|
879
|
+
function openSubSession(key) {
|
|
880
|
+
if (!subSessions || !subSessions[key]) return;
|
|
881
|
+
if (overlayStack.length === 0) {
|
|
882
|
+
subOverlayLastFocus = document.activeElement;
|
|
1436
883
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
884
|
+
overlayStack.push(key);
|
|
885
|
+
renderSubOverlay();
|
|
1439
886
|
}
|
|
1440
887
|
|
|
1441
|
-
function
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
let html = toolHead('eval');
|
|
1447
|
-
if (typeof args.input !== 'string') {
|
|
1448
|
-
html += '<div class="tool-error">[missing input]</div>';
|
|
888
|
+
function popSubSession() {
|
|
889
|
+
if (overlayStack.length === 0) return;
|
|
890
|
+
overlayStack.pop();
|
|
891
|
+
if (overlayStack.length === 0) {
|
|
892
|
+
hideSubOverlay();
|
|
1449
893
|
} else {
|
|
1450
|
-
|
|
1451
|
-
if (cells.length === 0) {
|
|
1452
|
-
html += codeBlock(args.input, 'python');
|
|
1453
|
-
} else {
|
|
1454
|
-
for (const cell of cells) {
|
|
1455
|
-
html += '<div class="tool-cell">';
|
|
1456
|
-
const titleParts = [];
|
|
1457
|
-
if (cell.title) titleParts.push(cell.title);
|
|
1458
|
-
titleParts.push(cell.lang);
|
|
1459
|
-
if (cell.attrs && cell.attrs.length) titleParts.push(...cell.attrs);
|
|
1460
|
-
html += '<div class="tool-cell-title">' + escapeHtml(titleParts.join(' · ')) + '</div>';
|
|
1461
|
-
html += codeBlock(cell.code, evalLangToHljs(cell.lang));
|
|
1462
|
-
html += '</div>';
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
if (result) {
|
|
1467
|
-
html += ctx.renderResultImages();
|
|
1468
|
-
const output = ctx.getResultText();
|
|
1469
|
-
if (output) html += formatExpandableOutput(output, 12);
|
|
894
|
+
renderSubOverlay();
|
|
1470
895
|
}
|
|
1471
|
-
return html;
|
|
1472
896
|
}
|
|
1473
897
|
|
|
1474
|
-
function
|
|
1475
|
-
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1478
|
-
let
|
|
1479
|
-
|
|
1480
|
-
const badges = [];
|
|
1481
|
-
if (args.i) badges.push('i');
|
|
1482
|
-
if (args.skip) badges.push('skip=' + args.skip);
|
|
1483
|
-
if (args.gitignore === false) badges.push('no-gitignore');
|
|
1484
|
-
for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(b) + '</span>';
|
|
1485
|
-
let html = '<div class="tool-header">' + head + '</div>';
|
|
1486
|
-
if (result) {
|
|
1487
|
-
const output = ctx.getResultText();
|
|
1488
|
-
if (output) html += formatExpandableOutput(output, 12);
|
|
898
|
+
function popSubSessionTo(key) {
|
|
899
|
+
// Rebuild the chain root..key (the stack is always a prefix chain).
|
|
900
|
+
const segments = key.split('/');
|
|
901
|
+
overlayStack.length = 0;
|
|
902
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
903
|
+
overlayStack.push(segments.slice(0, i).join('/'));
|
|
1489
904
|
}
|
|
1490
|
-
|
|
905
|
+
renderSubOverlay();
|
|
1491
906
|
}
|
|
1492
907
|
|
|
1493
|
-
function
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
if (args.to) badges.push('to=' + str(args.to));
|
|
1498
|
-
if (op === 'wait' && args.from) badges.push('from=' + str(args.from));
|
|
1499
|
-
if (args.await) badges.push('await');
|
|
1500
|
-
if (args.peek) badges.push('peek');
|
|
1501
|
-
let html = toolHead('irc', '', badges);
|
|
1502
|
-
if (args.message) html += '<div class="tool-output"><div>' + escapeHtml(String(args.message)) + '</div></div>';
|
|
1503
|
-
let renderedDetails = false;
|
|
1504
|
-
if (details && Array.isArray(details.receipts) && details.receipts.length) {
|
|
1505
|
-
html += '<div class="tool-args">';
|
|
1506
|
-
for (const receipt of details.receipts) {
|
|
1507
|
-
const outcome = escapeHtml(String(receipt.outcome)) + (receipt.error ? ' — ' + escapeHtml(String(receipt.error)) : '');
|
|
1508
|
-
html += '<div class="tool-arg"><span class="tool-arg-key">' + escapeHtml(String(receipt.to)) + '</span> ' + outcome + '</div>';
|
|
1509
|
-
}
|
|
1510
|
-
html += '</div>';
|
|
1511
|
-
renderedDetails = true;
|
|
1512
|
-
}
|
|
1513
|
-
if (details && details.waited) {
|
|
1514
|
-
html += '<div class="tool-output"><div>' + escapeHtml(String(details.waited.from)) + ': ' + escapeHtml(String(details.waited.body)) + '</div></div>';
|
|
1515
|
-
renderedDetails = true;
|
|
1516
|
-
}
|
|
1517
|
-
if (details && Array.isArray(details.inbox) && details.inbox.length) {
|
|
1518
|
-
html += '<div class="tool-args">';
|
|
1519
|
-
for (const msg of details.inbox) {
|
|
1520
|
-
html += '<div class="tool-arg"><span class="tool-arg-key">' + escapeHtml(String(msg.from)) + '</span> ' + escapeHtml(String(msg.body)) + '</div>';
|
|
1521
|
-
}
|
|
1522
|
-
html += '</div>';
|
|
1523
|
-
renderedDetails = true;
|
|
1524
|
-
}
|
|
1525
|
-
if (!renderedDetails && result) {
|
|
1526
|
-
const output = ctx.getResultText();
|
|
1527
|
-
if (output) html += formatExpandableOutput(output, 8);
|
|
1528
|
-
}
|
|
1529
|
-
return html;
|
|
908
|
+
function closeAllSubSessions() {
|
|
909
|
+
if (overlayStack.length === 0) return;
|
|
910
|
+
overlayStack.length = 0;
|
|
911
|
+
hideSubOverlay();
|
|
1530
912
|
}
|
|
1531
913
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
if (argText && argText !== '{}') {
|
|
1537
|
-
html += '<div class="tool-output"><pre>' + escapeHtml(argText) + '</pre></div>';
|
|
914
|
+
function hideSubOverlay() {
|
|
915
|
+
if (subOverlayEl) {
|
|
916
|
+
subOverlayEl.classList.remove('open');
|
|
917
|
+
subOverlayEl.querySelector('.subsession-body').innerHTML = '';
|
|
1538
918
|
}
|
|
1539
|
-
if (
|
|
1540
|
-
|
|
1541
|
-
const output = ctx.getResultText();
|
|
1542
|
-
if (output) html += formatExpandableOutput(output, 10);
|
|
919
|
+
if (subOverlayLastFocus && typeof subOverlayLastFocus.focus === 'function') {
|
|
920
|
+
subOverlayLastFocus.focus();
|
|
1543
921
|
}
|
|
1544
|
-
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
const TOOL_RENDERERS = {
|
|
1548
|
-
bash: renderBash,
|
|
1549
|
-
eval: renderEval,
|
|
1550
|
-
js: renderJsLike,
|
|
1551
|
-
python: renderJsLike,
|
|
1552
|
-
notebook: renderJsLike,
|
|
1553
|
-
read: renderRead,
|
|
1554
|
-
write: renderWrite,
|
|
1555
|
-
edit: renderEdit,
|
|
1556
|
-
ast_edit: renderAstEdit,
|
|
1557
|
-
ast_grep: renderAstGrep,
|
|
1558
|
-
grep: renderGrep,
|
|
1559
|
-
search: renderSearch,
|
|
1560
|
-
find: renderFind,
|
|
1561
|
-
lsp: renderLsp,
|
|
1562
|
-
todo: renderTodo,
|
|
1563
|
-
task: renderTask,
|
|
1564
|
-
web_search: renderWebSearch,
|
|
1565
|
-
fetch: renderFetch,
|
|
1566
|
-
debug: renderDebug,
|
|
1567
|
-
puppeteer: renderBrowser,
|
|
1568
|
-
browser: renderBrowser,
|
|
1569
|
-
inspect_image: renderInspectImage,
|
|
1570
|
-
generate_image: renderGenerateImage,
|
|
1571
|
-
ask: renderAsk,
|
|
1572
|
-
resolve: renderResolve,
|
|
1573
|
-
github: renderGh,
|
|
1574
|
-
render_mermaid: renderMermaid,
|
|
1575
|
-
yield: renderYield,
|
|
1576
|
-
report_finding: renderReportFinding,
|
|
1577
|
-
report_tool_issue: renderReportToolIssue,
|
|
1578
|
-
await: renderJob,
|
|
1579
|
-
poll: renderJob,
|
|
1580
|
-
cancel_job: renderJob,
|
|
1581
|
-
job: renderJob,
|
|
1582
|
-
irc: renderIrc,
|
|
1583
|
-
};
|
|
1584
|
-
|
|
1585
|
-
function renderToolCall(call) {
|
|
1586
|
-
const result = findToolResult(call.id);
|
|
1587
|
-
const isError = result?.isError || false;
|
|
1588
|
-
const statusClass = result ? (isError ? 'error' : 'success') : 'pending';
|
|
1589
|
-
const rawArgs = call.arguments || {};
|
|
1590
|
-
const intent = typeof rawArgs._i === 'string' ? rawArgs._i.trim() : '';
|
|
1591
|
-
// Strip internal _i intent so renderers don't dump it as JSON.
|
|
1592
|
-
const args = {};
|
|
1593
|
-
for (const k of Object.keys(rawArgs)) {
|
|
1594
|
-
if (k !== '_i') args[k] = rawArgs[k];
|
|
1595
|
-
}
|
|
1596
|
-
const name = call.name;
|
|
1597
|
-
|
|
1598
|
-
const ctx = {
|
|
1599
|
-
intent,
|
|
1600
|
-
getResultText: () => {
|
|
1601
|
-
if (!result) return '';
|
|
1602
|
-
const textBlocks = result.content.filter(c => c.type === 'text');
|
|
1603
|
-
return textBlocks.map(c => c.text).join('\n');
|
|
1604
|
-
},
|
|
1605
|
-
getResultImages: () => {
|
|
1606
|
-
if (!result) return [];
|
|
1607
|
-
return result.content.filter(c => c.type === 'image');
|
|
1608
|
-
},
|
|
1609
|
-
renderResultImages: () => {
|
|
1610
|
-
if (!result) return '';
|
|
1611
|
-
const images = result.content.filter(c => c.type === 'image');
|
|
1612
|
-
if (images.length === 0) return '';
|
|
1613
|
-
return '<div class="tool-images">' +
|
|
1614
|
-
images.map(img => '<img src="data:' + img.mimeType + ';base64,' + img.data + '" class="tool-image" />').join('') +
|
|
1615
|
-
'</div>';
|
|
1616
|
-
},
|
|
1617
|
-
};
|
|
1618
|
-
|
|
1619
|
-
const renderer = TOOL_RENDERERS[name] || renderGenericTool;
|
|
1620
|
-
let html = '<div class="tool-execution ' + statusClass + '">';
|
|
1621
|
-
if (intent) html += '<div class="tool-intent">' + escapeHtml(intent) + '</div>';
|
|
1622
|
-
try {
|
|
1623
|
-
html += renderer(name, args, result, ctx);
|
|
1624
|
-
} catch (err) {
|
|
1625
|
-
html += renderGenericTool(name, args, result, ctx);
|
|
1626
|
-
}
|
|
1627
|
-
html += '</div>';
|
|
1628
|
-
return html;
|
|
922
|
+
subOverlayLastFocus = null;
|
|
1629
923
|
}
|
|
1630
924
|
|
|
1631
925
|
|
|
@@ -1710,11 +1004,12 @@
|
|
|
1710
1004
|
</button>`;
|
|
1711
1005
|
}
|
|
1712
1006
|
|
|
1713
|
-
function renderEntry(entry) {
|
|
1007
|
+
function renderEntry(entry, sctx) {
|
|
1714
1008
|
const ts = formatTimestamp(entry.timestamp);
|
|
1715
1009
|
const tsHtml = ts ? `<div class="message-timestamp">${ts}</div>` : '';
|
|
1716
|
-
const entryId =
|
|
1717
|
-
|
|
1010
|
+
const entryId = `${sctx.idPrefix}${entry.id}`;
|
|
1011
|
+
// Share links target main-session entries only; overlays skip them.
|
|
1012
|
+
const copyBtnHtml = sctx.prefix === '' ? renderCopyLinkButton(entry.id) : '';
|
|
1718
1013
|
|
|
1719
1014
|
if (entry.type === 'message') {
|
|
1720
1015
|
const msg = entry.message;
|
|
@@ -1771,7 +1066,7 @@
|
|
|
1771
1066
|
|
|
1772
1067
|
for (const block of msg.content) {
|
|
1773
1068
|
if (block.type === 'toolCall') {
|
|
1774
|
-
html += renderToolCall(block);
|
|
1069
|
+
html += renderToolCall(block, sctx);
|
|
1775
1070
|
}
|
|
1776
1071
|
}
|
|
1777
1072
|
|
|
@@ -1959,14 +1254,16 @@
|
|
|
1959
1254
|
// Cache for rendered entry DOM nodes
|
|
1960
1255
|
const entryCache = new Map();
|
|
1961
1256
|
|
|
1962
|
-
function renderEntryToNode(entry) {
|
|
1963
|
-
//
|
|
1964
|
-
|
|
1257
|
+
function renderEntryToNode(entry, sctx) {
|
|
1258
|
+
// Cache main-session nodes only; sub-session bodies are cached whole
|
|
1259
|
+
// per key in subSessionBodyCache, so each entry renders once anyway.
|
|
1260
|
+
const cacheable = sctx.prefix === '';
|
|
1261
|
+
if (cacheable && entryCache.has(entry.id)) {
|
|
1965
1262
|
return entryCache.get(entry.id).cloneNode(true);
|
|
1966
1263
|
}
|
|
1967
1264
|
|
|
1968
1265
|
// Render to HTML string, then parse to node
|
|
1969
|
-
const html = renderEntry(entry);
|
|
1266
|
+
const html = renderEntry(entry, sctx);
|
|
1970
1267
|
if (!html) return null;
|
|
1971
1268
|
|
|
1972
1269
|
const template = document.createElement('template');
|
|
@@ -1974,7 +1271,7 @@
|
|
|
1974
1271
|
const node = template.content.firstElementChild;
|
|
1975
1272
|
|
|
1976
1273
|
// Cache the node
|
|
1977
|
-
if (node) {
|
|
1274
|
+
if (cacheable && node) {
|
|
1978
1275
|
entryCache.set(entry.id, node.cloneNode(true));
|
|
1979
1276
|
}
|
|
1980
1277
|
return node;
|
|
@@ -1994,7 +1291,7 @@
|
|
|
1994
1291
|
const fragment = document.createDocumentFragment();
|
|
1995
1292
|
|
|
1996
1293
|
for (const entry of path) {
|
|
1997
|
-
const node = renderEntryToNode(entry);
|
|
1294
|
+
const node = renderEntryToNode(entry, mainSctx);
|
|
1998
1295
|
if (node) {
|
|
1999
1296
|
fragment.appendChild(node);
|
|
2000
1297
|
}
|
|
@@ -2228,13 +1525,13 @@
|
|
|
2228
1525
|
document.getElementById('sidebar-close').addEventListener('click', closeSidebar);
|
|
2229
1526
|
|
|
2230
1527
|
// Toggle states
|
|
2231
|
-
let thinkingExpanded =
|
|
1528
|
+
let thinkingExpanded = false;
|
|
2232
1529
|
let toolOutputsExpanded = false;
|
|
2233
1530
|
|
|
2234
1531
|
const toggleThinking = () => {
|
|
2235
1532
|
thinkingExpanded = !thinkingExpanded;
|
|
2236
1533
|
document.querySelectorAll('.thinking-text').forEach(el => {
|
|
2237
|
-
el.style.display = thinkingExpanded ? '' : 'none';
|
|
1534
|
+
el.style.display = thinkingExpanded ? 'block' : 'none';
|
|
2238
1535
|
});
|
|
2239
1536
|
document.querySelectorAll('.thinking-collapsed').forEach(el => {
|
|
2240
1537
|
el.style.display = thinkingExpanded ? 'none' : 'block';
|
|
@@ -2254,6 +1551,11 @@
|
|
|
2254
1551
|
// Keyboard shortcuts
|
|
2255
1552
|
document.addEventListener('keydown', (e) => {
|
|
2256
1553
|
if (e.key === 'Escape') {
|
|
1554
|
+
if (overlayStack.length > 0) {
|
|
1555
|
+
e.preventDefault();
|
|
1556
|
+
popSubSession();
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
2257
1559
|
searchInput.value = '';
|
|
2258
1560
|
searchQuery = '';
|
|
2259
1561
|
navigateTo(leafId, 'bottom');
|
|
@@ -2280,4 +1582,27 @@
|
|
|
2280
1582
|
// Fallback: use last entry if no leafId
|
|
2281
1583
|
navigateTo(entries[entries.length - 1].id, 'none');
|
|
2282
1584
|
}
|
|
1585
|
+
} // end bootSession
|
|
1586
|
+
|
|
1587
|
+
function showLoadError(err) {
|
|
1588
|
+
const messages = document.getElementById('messages');
|
|
1589
|
+
if (!messages) return;
|
|
1590
|
+
const div = document.createElement('div');
|
|
1591
|
+
div.className = 'share-load-error';
|
|
1592
|
+
div.textContent = 'Failed to load session: ' + (err && err.message ? err.message : String(err));
|
|
1593
|
+
messages.appendChild(div);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
const pending = window.__OMP_SESSION_DATA__;
|
|
1597
|
+
if (pending && typeof pending.then === 'function') {
|
|
1598
|
+
pending.then(bootSession, showLoadError);
|
|
1599
|
+
} else {
|
|
1600
|
+
const base64 = document.getElementById('session-data').textContent;
|
|
1601
|
+
const binary = atob(base64);
|
|
1602
|
+
const bytes = new Uint8Array(binary.length);
|
|
1603
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1604
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1605
|
+
}
|
|
1606
|
+
bootSession(JSON.parse(new TextDecoder('utf-8').decode(bytes)));
|
|
1607
|
+
}
|
|
2283
1608
|
})();
|