@oh-my-pi/pi-coding-agent 14.0.4 → 14.1.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/package.json +11 -8
  3. package/src/async/index.ts +1 -0
  4. package/src/async/support.ts +5 -0
  5. package/src/cli/list-models.ts +96 -57
  6. package/src/commit/model-selection.ts +16 -13
  7. package/src/config/model-equivalence.ts +674 -0
  8. package/src/config/model-registry.ts +182 -13
  9. package/src/config/model-resolver.ts +203 -74
  10. package/src/config/settings-schema.ts +23 -0
  11. package/src/config/settings.ts +9 -2
  12. package/src/dap/session.ts +31 -39
  13. package/src/debug/log-formatting.ts +2 -2
  14. package/src/edit/modes/chunk.ts +8 -3
  15. package/src/export/html/template.css +82 -0
  16. package/src/export/html/template.generated.ts +1 -1
  17. package/src/export/html/template.js +612 -97
  18. package/src/internal-urls/docs-index.generated.ts +1 -1
  19. package/src/internal-urls/jobs-protocol.ts +2 -1
  20. package/src/lsp/client.ts +5 -3
  21. package/src/lsp/index.ts +4 -9
  22. package/src/lsp/utils.ts +26 -0
  23. package/src/main.ts +6 -1
  24. package/src/memories/index.ts +7 -6
  25. package/src/modes/components/diff.ts +1 -1
  26. package/src/modes/components/model-selector.ts +221 -64
  27. package/src/modes/controllers/command-controller.ts +18 -0
  28. package/src/modes/controllers/event-controller.ts +438 -426
  29. package/src/modes/controllers/selector-controller.ts +13 -5
  30. package/src/modes/theme/mermaid-cache.ts +5 -7
  31. package/src/priority.json +8 -0
  32. package/src/prompts/agents/designer.md +1 -2
  33. package/src/prompts/system/system-prompt.md +5 -1
  34. package/src/prompts/tools/bash.md +15 -0
  35. package/src/prompts/tools/cancel-job.md +1 -1
  36. package/src/prompts/tools/chunk-edit.md +39 -40
  37. package/src/prompts/tools/read-chunk.md +13 -1
  38. package/src/prompts/tools/read.md +9 -0
  39. package/src/prompts/tools/write.md +1 -0
  40. package/src/sdk.ts +7 -4
  41. package/src/session/agent-session.ts +33 -6
  42. package/src/session/compaction/compaction.ts +1 -1
  43. package/src/task/executor.ts +5 -1
  44. package/src/tools/await-tool.ts +2 -1
  45. package/src/tools/bash.ts +221 -56
  46. package/src/tools/browser.ts +84 -21
  47. package/src/tools/cancel-job.ts +2 -1
  48. package/src/tools/fetch.ts +1 -1
  49. package/src/tools/find.ts +40 -94
  50. package/src/tools/gemini-image.ts +1 -0
  51. package/src/tools/inspect-image.ts +1 -1
  52. package/src/tools/read.ts +218 -1
  53. package/src/tools/render-utils.ts +1 -1
  54. package/src/tools/sqlite-reader.ts +623 -0
  55. package/src/tools/write.ts +187 -1
  56. package/src/utils/commit-message-generator.ts +1 -0
  57. package/src/utils/git.ts +24 -1
  58. package/src/utils/image-resize.ts +73 -37
  59. package/src/utils/title-generator.ts +1 -1
  60. package/src/web/scrapers/types.ts +50 -32
  61. package/src/web/search/providers/codex.ts +21 -2
@@ -292,7 +292,7 @@
292
292
  parts.push(msg.role);
293
293
  if (msg.content) parts.push(extractContent(msg.content));
294
294
  if (msg.role === 'bashExecution' && msg.command) parts.push(msg.command);
295
- if (msg.role === 'pythonExecution' && msg.code) parts.push(msg.code);
295
+ if (msg.role === 'jsExecution' && msg.code) parts.push(msg.code);
296
296
  break;
297
297
  }
298
298
  case 'custom_message':
@@ -480,9 +480,9 @@
480
480
  const cmd = truncate(normalize(msg.command || ''));
481
481
  return labelHtml + `<span class="tree-role-tool">[bash]:</span> ${escapeHtml(cmd)}`;
482
482
  }
483
- if (msg.role === 'pythonExecution') {
483
+ if (msg.role === 'jsExecution') {
484
484
  const code = truncate(normalize(msg.code || ''));
485
- return labelHtml + `<span class="tree-role-tool">[python]:</span> ${escapeHtml(code)}`;
485
+ return labelHtml + `<span class="tree-role-tool">[js]:</span> ${escapeHtml(code)}`;
486
486
  }
487
487
  return labelHtml + `<span class="tree-muted">[${msg.role}]</span>`;
488
488
  }
@@ -702,123 +702,638 @@
702
702
  return out;
703
703
  }
704
704
 
705
- function renderToolCall(call) {
706
- const result = findToolResult(call.id);
707
- const isError = result?.isError || false;
708
- const statusClass = result ? (isError ? 'error' : 'success') : 'pending';
705
+ // ============================================================
706
+ // TOOL CALL RENDERING
707
+ // ============================================================
709
708
 
710
- const getResultText = () => {
711
- if (!result) return '';
712
- const textBlocks = result.content.filter(c => c.type === 'text');
713
- return textBlocks.map(c => c.text).join('\n');
714
- };
709
+ // Shared helpers for per-tool renderers.
710
+ function toolHead(label, pathHtml, badges) {
711
+ let html = '<div class="tool-header"><span class="tool-name">' + escapeHtml(label) + '</span>';
712
+ if (pathHtml) html += ' <span class="tool-path">' + pathHtml + '</span>';
713
+ if (badges) {
714
+ for (const badge of badges) {
715
+ if (badge != null && badge !== '') {
716
+ html += ' <span class="tool-badge">' + escapeHtml(String(badge)) + '</span>';
717
+ }
718
+ }
719
+ }
720
+ html += '</div>';
721
+ return html;
722
+ }
715
723
 
716
- const getResultImages = () => {
717
- if (!result) return [];
718
- return result.content.filter(c => c.type === 'image');
719
- };
724
+ function invalidArgHtml() {
725
+ return '<span class="tool-error">[invalid arg]</span>';
726
+ }
720
727
 
721
- const renderResultImages = () => {
722
- const images = getResultImages();
723
- if (images.length === 0) return '';
724
- return '<div class="tool-images">' +
725
- images.map(img => `<img src="data:${img.mimeType};base64,${img.data}" class="tool-image" />`).join('') +
726
- '</div>';
727
- };
728
+ function pathDisplay(filePath, offset, limit) {
729
+ if (filePath == null) return invalidArgHtml();
730
+ let html = escapeHtml(shortenPath(filePath || ''));
731
+ if (offset !== undefined || limit !== undefined) {
732
+ const start = offset == null ? 1 : offset;
733
+ const end = limit !== undefined ? start + limit - 1 : '';
734
+ html += '<span class="line-numbers">:' + start + (end ? '-' + end : '') + '</span>';
735
+ }
736
+ return html;
737
+ }
728
738
 
729
- let html = `<div class="tool-execution ${statusClass}">`;
730
- const args = call.arguments || {};
731
- const name = call.name;
739
+ function codeBlock(code, lang) {
740
+ if (code == null || code === '') return '';
741
+ const text = String(code);
742
+ let highlighted;
743
+ try {
744
+ highlighted = lang ? hljs.highlight(text, { language: lang }).value : escapeHtml(text);
745
+ } catch {
746
+ highlighted = escapeHtml(text);
747
+ }
748
+ return '<div class="tool-output"><pre><code class="hljs">' + highlighted + '</code></pre></div>';
749
+ }
732
750
 
733
- const invalidArg = '<span class="tool-error">[invalid arg]</span>';
751
+ // Per-tool renderers. Each accepts (name, args, result, ctx) and returns the inner HTML.
752
+ function renderBash(name, args, result, ctx) {
753
+ const command = str(args.command);
754
+ const cwd = str(args.cwd);
755
+ const env = args.env && typeof args.env === 'object' ? args.env : null;
756
+ const cmdDisplay = command === null ? invalidArgHtml() : escapeHtml(command || '...');
757
+ let prefix = '';
758
+ if (env) {
759
+ for (const [k, v] of Object.entries(env)) {
760
+ prefix += escapeHtml(k) + '=' + escapeHtml(String(v)) + ' ';
761
+ }
762
+ }
763
+ let html = '<div class="tool-command">$ ' + prefix + cmdDisplay + '</div>';
764
+ const badges = [];
765
+ if (cwd) badges.push('cwd=' + shortenPath(cwd));
766
+ if (args.timeout) badges.push('timeout=' + args.timeout + 's');
767
+ if (args.pty) badges.push('pty');
768
+ if (args.head) badges.push('head=' + args.head);
769
+ if (args.tail) badges.push('tail=' + args.tail);
770
+ if (badges.length) {
771
+ html += '<div class="tool-meta">' + badges.map(b => '<span class="tool-badge">' + escapeHtml(b) + '</span>').join(' ') + '</div>';
772
+ }
773
+ if (result) {
774
+ html += ctx.renderResultImages();
775
+ const output = ctx.getResultText().trim();
776
+ if (output) html += formatExpandableOutput(output, 5);
777
+ }
778
+ return html;
779
+ }
734
780
 
735
- switch (name) {
736
- case 'bash': {
737
- const command = str(args.command);
738
- const cmdDisplay = command === null ? invalidArg : escapeHtml(command || '...');
739
- html += `<div class="tool-command">$ ${cmdDisplay}</div>`;
740
- if (result) {
741
- const output = getResultText().trim();
742
- if (output) html += formatExpandableOutput(output, 5);
743
- }
744
- break;
781
+ function renderJsLike(name, args, result, ctx) {
782
+ const lang = name === 'python' ? 'python' : 'javascript';
783
+ const badges = [];
784
+ if (args.cwd) badges.push('cwd=' + shortenPath(String(args.cwd)));
785
+ if (args.timeout) badges.push('timeout=' + args.timeout + 's');
786
+ if (args.reset) badges.push('reset');
787
+ let html = toolHead(name, '', badges);
788
+ const cells = Array.isArray(args.cells) ? args.cells : null;
789
+ if (!cells) {
790
+ html += '<div class="tool-error">[missing cells]</div>';
791
+ } else {
792
+ for (const cell of cells) {
793
+ html += '<div class="tool-cell">';
794
+ if (cell && cell.title) html += '<div class="tool-cell-title">' + escapeHtml(String(cell.title)) + '</div>';
795
+ const code = cell && typeof cell.code === 'string' ? cell.code : '';
796
+ html += codeBlock(code, lang);
797
+ html += '</div>';
745
798
  }
746
- case 'read': {
747
- const filePath = str(args.file_path ?? args.path);
748
- const offset = args.offset;
749
- const limit = args.limit;
799
+ }
800
+ if (result) {
801
+ html += ctx.renderResultImages();
802
+ const output = ctx.getResultText();
803
+ if (output) html += formatExpandableOutput(output, 10);
804
+ }
805
+ return html;
806
+ }
750
807
 
751
- let pathHtml = filePath === null ? invalidArg : escapeHtml(shortenPath(filePath || ''));
752
- if (filePath !== null && (offset !== undefined || limit !== undefined)) {
753
- const startLine = offset ?? 1;
754
- const endLine = limit !== undefined ? startLine + limit - 1 : '';
755
- pathHtml += `<span class="line-numbers">:${startLine}${endLine ? '-' + endLine : ''}</span>`;
756
- }
808
+ function renderRead(name, args, result, ctx) {
809
+ const filePath = str(args.file_path == null ? args.path : args.file_path);
810
+ let pathHtml = pathDisplay(filePath, args.offset, args.limit);
811
+ if (args.sel) pathHtml += '<span class="line-numbers">:' + escapeHtml(String(args.sel)) + '</span>';
812
+ let html = toolHead('read', pathHtml);
813
+ if (result) {
814
+ html += ctx.renderResultImages();
815
+ const output = ctx.getResultText();
816
+ const lang = filePath ? getLanguageFromPath(filePath) : null;
817
+ if (output) html += formatExpandableOutput(output, 10, lang);
818
+ }
819
+ return html;
820
+ }
757
821
 
758
- html += `<div class="tool-header"><span class="tool-name">read</span> <span class="tool-path">${pathHtml}</span></div>`;
759
- if (result) {
760
- html += renderResultImages();
761
- const output = getResultText();
762
- const lang = filePath ? getLanguageFromPath(filePath) : null;
763
- if (output) html += formatExpandableOutput(output, 10, lang);
764
- }
765
- break;
822
+ function renderWrite(name, args, result, ctx) {
823
+ const filePath = str(args.file_path == null ? args.path : args.file_path);
824
+ const content = str(args.content);
825
+ const pathHtml = filePath === null ? invalidArgHtml() : escapeHtml(shortenPath(filePath || ''));
826
+ const lineCount = (content != null && content !== '') ? content.split('\n').length : 0;
827
+ const badges = lineCount > 10 ? ['(' + lineCount + ' lines)'] : null;
828
+ let html = toolHead('write', pathHtml, badges);
829
+ if (content === null) {
830
+ html += '<div class="tool-error">[invalid content arg - expected string]</div>';
831
+ } else if (content) {
832
+ const lang = filePath ? getLanguageFromPath(filePath) : null;
833
+ html += formatExpandableOutput(content, 10, lang);
834
+ }
835
+ if (result) {
836
+ const output = ctx.getResultText().trim();
837
+ if (output) html += '<div class="tool-output"><div>' + escapeHtml(output) + '</div></div>';
838
+ }
839
+ return html;
840
+ }
841
+
842
+ function renderEdit(name, args, result, ctx) {
843
+ const filePath = str(args.file_path == null ? args.path : args.file_path);
844
+ const pathHtml = filePath === null ? invalidArgHtml() : escapeHtml(shortenPath(filePath || ''));
845
+ let html = toolHead('edit', pathHtml);
846
+ if (Array.isArray(args.edits)) {
847
+ html += '<div class="tool-args">';
848
+ for (const e of args.edits) {
849
+ const op = e && typeof e.op === 'string' ? e.op : '?';
850
+ const sel = e && typeof e.sel === 'string' ? e.sel : '?';
851
+ html += '<div class="tool-arg"><span class="tool-arg-key">' + escapeHtml(op) + '</span> <span class="tool-arg-val">' + escapeHtml(sel) + '</span></div>';
766
852
  }
767
- case 'write': {
768
- const filePath = str(args.file_path ?? args.path);
769
- const content = str(args.content);
770
-
771
- html += `<div class="tool-header"><span class="tool-name">write</span> <span class="tool-path">${filePath === null ? invalidArg : escapeHtml(shortenPath(filePath || ''))}</span>`;
772
- if (content !== null && content) {
773
- const lines = content.split('\n');
774
- if (lines.length > 10) html += ` <span class="line-count">(${lines.length} lines)</span>`;
775
- }
853
+ html += '</div>';
854
+ }
855
+ if (result?.details?.diff) {
856
+ const diffLines = String(result.details.diff).split('\n');
857
+ html += '<div class="tool-diff">';
858
+ for (const line of diffLines) {
859
+ const cls = line.match(/^\+/) ? 'diff-added' : line.match(/^-/) ? 'diff-removed' : 'diff-context';
860
+ html += '<div class="' + cls + '">' + escapeHtml(replaceTabs(line)) + '</div>';
861
+ }
862
+ html += '</div>';
863
+ } else if (result) {
864
+ const output = ctx.getResultText().trim();
865
+ if (output) html += '<div class="tool-output"><pre>' + escapeHtml(output) + '</pre></div>';
866
+ }
867
+ return html;
868
+ }
869
+
870
+ function renderAstEdit(name, args, result, ctx) {
871
+ const lang = args.lang || null;
872
+ const pathHtml = args.path ? escapeHtml(shortenPath(String(args.path))) : '';
873
+ const badges = [];
874
+ if (lang) badges.push(lang);
875
+ if (args.glob) badges.push('glob=' + args.glob);
876
+ if (args.sel) badges.push('sel=' + args.sel);
877
+ let html = toolHead('ast_edit', pathHtml, badges);
878
+ if (Array.isArray(args.ops)) {
879
+ for (const op of args.ops) {
880
+ html += '<div class="tool-cell">';
881
+ html += '<div class="tool-cell-title">pattern</div>';
882
+ html += codeBlock(String(op?.pat == null ? '' : op.pat), lang);
883
+ html += '<div class="tool-cell-title">replacement</div>';
884
+ html += codeBlock(String(op?.out == null ? '' : op.out), lang);
776
885
  html += '</div>';
886
+ }
887
+ }
888
+ if (result) {
889
+ const output = ctx.getResultText();
890
+ if (output) html += formatExpandableOutput(output, 10);
891
+ }
892
+ return html;
893
+ }
777
894
 
778
- if (content === null) {
779
- html += `<div class="tool-error">[invalid content arg - expected string]</div>`;
780
- } else if (content) {
781
- const lang = filePath ? getLanguageFromPath(filePath) : null;
782
- html += formatExpandableOutput(content, 10, lang);
783
- }
784
- if (result) {
785
- const output = getResultText().trim();
786
- if (output) html += `<div class="tool-output"><div>${escapeHtml(output)}</div></div>`;
787
- }
788
- break;
895
+ function renderAstGrep(name, args, result, ctx) {
896
+ const lang = args.lang || null;
897
+ const pathHtml = args.path ? escapeHtml(shortenPath(String(args.path))) : '';
898
+ const badges = [];
899
+ if (lang) badges.push(lang);
900
+ if (args.glob) badges.push('glob=' + args.glob);
901
+ if (args.sel) badges.push('sel=' + args.sel);
902
+ let html = toolHead('ast_grep', pathHtml, badges);
903
+ if (Array.isArray(args.pat)) {
904
+ for (const pat of args.pat) {
905
+ html += '<div class="tool-cell">' + codeBlock(String(pat == null ? '' : pat), lang) + '</div>';
906
+ }
907
+ }
908
+ if (result) {
909
+ const output = ctx.getResultText();
910
+ if (output) html += formatExpandableOutput(output, 10);
911
+ }
912
+ return html;
913
+ }
914
+
915
+ function renderGrep(name, args, result, ctx) {
916
+ const pattern = str(args.pattern);
917
+ const pathHtml = args.path ? escapeHtml(shortenPath(String(args.path))) : escapeHtml('.');
918
+ const patHtml = pattern === null ? invalidArgHtml() : escapeHtml(pattern);
919
+ let head = '<span class="tool-name">grep</span> <span class="tool-pattern">/' + patHtml + '/</span>';
920
+ head += ' <span class="tool-arg-key">in</span> <span class="tool-path">' + pathHtml + '</span>';
921
+ const badges = [];
922
+ if (args.glob) badges.push('glob=' + args.glob);
923
+ if (args.type) badges.push('type=' + args.type);
924
+ if (args.i) badges.push('i');
925
+ if (args.multiline) badges.push('multiline');
926
+ for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(b) + '</span>';
927
+ let html = '<div class="tool-header">' + head + '</div>';
928
+ if (result) {
929
+ const output = ctx.getResultText();
930
+ if (output) html += formatExpandableOutput(output, 10);
931
+ }
932
+ return html;
933
+ }
934
+
935
+ function renderFind(name, args, result, ctx) {
936
+ const pattern = str(args.pattern);
937
+ const patHtml = pattern === null ? invalidArgHtml() : escapeHtml(pattern);
938
+ const badges = args.limit ? ['limit=' + args.limit] : null;
939
+ let html = toolHead('find', '<span class="tool-pattern">' + patHtml + '</span>', badges);
940
+ if (result) {
941
+ const output = ctx.getResultText();
942
+ if (output) html += formatExpandableOutput(output, 10);
943
+ }
944
+ return html;
945
+ }
946
+
947
+ function renderLsp(name, args, result, ctx) {
948
+ const action = str(args.action) || '?';
949
+ let head = '<span class="tool-name">lsp</span> <span class="tool-badge">' + escapeHtml(action) + '</span>';
950
+ if (args.file && args.file !== '*') {
951
+ head += ' <span class="tool-path">' + escapeHtml(shortenPath(String(args.file))) + '</span>';
952
+ } else if (args.file === '*') {
953
+ head += ' <span class="tool-badge">workspace</span>';
954
+ }
955
+ if (args.line) head += '<span class="line-numbers">:' + args.line + '</span>';
956
+ if (args.symbol) head += ' <span class="tool-arg-val">' + escapeHtml(String(args.symbol)) + '</span>';
957
+ if (args.query) head += ' <span class="tool-arg-key">query=</span><span class="tool-arg-val">' + escapeHtml(String(args.query)) + '</span>';
958
+ if (args.new_name) head += ' <span class="tool-arg-key">→</span> <span class="tool-arg-val">' + escapeHtml(String(args.new_name)) + '</span>';
959
+ let html = '<div class="tool-header">' + head + '</div>';
960
+ if (result) {
961
+ const output = ctx.getResultText();
962
+ if (output) html += formatExpandableOutput(output, 12);
963
+ }
964
+ return html;
965
+ }
966
+
967
+ function renderTodoWrite(name, args, result, ctx) {
968
+ let html = toolHead('todo_write');
969
+ const ops = Array.isArray(args.ops) ? args.ops : null;
970
+ if (ops) {
971
+ html += '<div class="tool-args">';
972
+ for (const op of ops) {
973
+ const t = op && op.op ? op.op : '?';
974
+ let line = '<span class="tool-arg-key">' + escapeHtml(t) + '</span>';
975
+ if (op?.id) line += ' <span class="tool-arg-val">' + escapeHtml(String(op.id)) + '</span>';
976
+ if (op?.status) line += ' <span class="tool-badge">' + escapeHtml(String(op.status)) + '</span>';
977
+ if (op?.content) line += ' ' + escapeHtml(truncate(String(op.content), 80));
978
+ if (op?.task && typeof op.task === 'object' && op.task.content) line += ' ' + escapeHtml(truncate(String(op.task.content), 80));
979
+ html += '<div class="tool-arg">' + line + '</div>';
789
980
  }
790
- case 'edit': {
791
- const filePath = str(args.file_path ?? args.path);
792
- html += `<div class="tool-header"><span class="tool-name">edit</span> <span class="tool-path">${filePath === null ? invalidArg : escapeHtml(shortenPath(filePath || ''))}</span></div>`;
793
-
794
- if (result?.details?.diff) {
795
- const diffLines = result.details.diff.split('\n');
796
- html += '<div class="tool-diff">';
797
- for (const line of diffLines) {
798
- const cls = line.match(/^\+/) ? 'diff-added' : line.match(/^-/) ? 'diff-removed' : 'diff-context';
799
- html += `<div class="${cls}">${escapeHtml(replaceTabs(line))}</div>`;
981
+ html += '</div>';
982
+ }
983
+ const phases = result?.details?.phases;
984
+ if (Array.isArray(phases)) {
985
+ html += '<div class="todo-tree">';
986
+ for (const phase of phases) {
987
+ html += '<div class="todo-phase">' + escapeHtml(String(phase.name || '')) + '</div>';
988
+ if (Array.isArray(phase.tasks)) {
989
+ for (const task of phase.tasks) {
990
+ const status = task.status || 'pending';
991
+ const icon = status === 'completed' ? '✓' : status === 'in_progress' ? '→' : status === 'abandoned' ? '✕' : '○';
992
+ html += '<div class="todo-task todo-' + status + '"><span class="todo-icon">' + icon + '</span> ' + escapeHtml(String(task.content || '')) + '</div>';
800
993
  }
801
- html += '</div>';
802
- } else if (result) {
803
- const output = getResultText().trim();
804
- if (output) html += `<div class="tool-output"><pre>${escapeHtml(output)}</pre></div>`;
805
994
  }
806
- break;
807
995
  }
808
- default: {
809
- html += `<div class="tool-header"><span class="tool-name">${escapeHtml(name)}</span></div>`;
810
- html += `<div class="tool-output"><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></div>`;
811
- if (result) {
812
- const output = getResultText();
813
- if (output) html += formatExpandableOutput(output, 10);
996
+ html += '</div>';
997
+ } else if (result) {
998
+ const output = ctx.getResultText();
999
+ if (output) html += formatExpandableOutput(output, 8);
1000
+ }
1001
+ return html;
1002
+ }
1003
+
1004
+ function renderTask(name, args, result, ctx) {
1005
+ const agent = str(args.agent) || '?';
1006
+ const tasks = Array.isArray(args.tasks) ? args.tasks : [];
1007
+ const badges = ['agent=' + agent, tasks.length + ' subtask' + (tasks.length === 1 ? '' : 's')];
1008
+ if (args.isolated) badges.push('isolated');
1009
+ let html = toolHead('task', '', badges);
1010
+ if (tasks.length) {
1011
+ html += '<div class="tool-args">';
1012
+ for (const t of tasks) {
1013
+ const id = t?.id ? escapeHtml(String(t.id)) : '?';
1014
+ const desc = t?.description ? escapeHtml(String(t.description)) : '';
1015
+ html += '<div class="tool-arg"><span class="tool-arg-key">' + id + '</span> ' + desc + '</div>';
1016
+ }
1017
+ html += '</div>';
1018
+ }
1019
+ if (result) {
1020
+ const output = ctx.getResultText();
1021
+ if (output) html += formatExpandableOutput(output, 12);
1022
+ }
1023
+ return html;
1024
+ }
1025
+
1026
+ function renderWebSearch(name, args, result, ctx) {
1027
+ const query = str(args.query);
1028
+ const queryHtml = query === null ? invalidArgHtml() : escapeHtml(query);
1029
+ const badges = [];
1030
+ if (args.recency) badges.push('recency=' + args.recency);
1031
+ if (args.limit) badges.push('limit=' + args.limit);
1032
+ let html = toolHead('web_search', '<span class="tool-pattern">' + queryHtml + '</span>', badges);
1033
+ if (result) {
1034
+ const output = ctx.getResultText();
1035
+ if (output) html += formatExpandableOutput(output, 12, 'markdown');
1036
+ }
1037
+ return html;
1038
+ }
1039
+
1040
+ function renderFetch(name, args, result, ctx) {
1041
+ const url = str(args.url) || '';
1042
+ const badges = args.method ? [String(args.method)] : null;
1043
+ let html = toolHead('fetch', '<span class="tool-path">' + escapeHtml(url) + '</span>', badges);
1044
+ if (result) {
1045
+ const output = ctx.getResultText();
1046
+ if (output) html += formatExpandableOutput(output, 10);
1047
+ }
1048
+ return html;
1049
+ }
1050
+
1051
+ function renderDebug(name, args, result, ctx) {
1052
+ const action = str(args.action) || '?';
1053
+ const badges = [];
1054
+ if (args.adapter) badges.push(args.adapter);
1055
+ if (args.program) badges.push('program=' + shortenPath(String(args.program)));
1056
+ if (args.file) badges.push('file=' + shortenPath(String(args.file)));
1057
+ if (args.line) badges.push('line=' + args.line);
1058
+ let head = '<span class="tool-name">debug</span> <span class="tool-badge">' + escapeHtml(action) + '</span>';
1059
+ for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(String(b)) + '</span>';
1060
+ let html = '<div class="tool-header">' + head + '</div>';
1061
+ if (args.expression) html += codeBlock(String(args.expression));
1062
+ if (result) {
1063
+ const output = ctx.getResultText();
1064
+ if (output) html += formatExpandableOutput(output, 10);
1065
+ }
1066
+ return html;
1067
+ }
1068
+
1069
+ function renderPuppeteer(name, args, result, ctx) {
1070
+ const action = str(args.action) || '?';
1071
+ const badges = [];
1072
+ if (args.url) badges.push(String(args.url));
1073
+ if (args.selector) badges.push('selector=' + args.selector);
1074
+ if (args.element_id != null) badges.push('id=' + args.element_id);
1075
+ let head = '<span class="tool-name">puppeteer</span> <span class="tool-badge">' + escapeHtml(action) + '</span>';
1076
+ for (const b of badges) head += ' <span class="tool-badge">' + escapeHtml(String(b)) + '</span>';
1077
+ let html = '<div class="tool-header">' + head + '</div>';
1078
+ if (args.script) html += codeBlock(String(args.script), 'javascript');
1079
+ if (args.text) html += '<div class="tool-output"><div>' + escapeHtml(String(args.text)) + '</div></div>';
1080
+ if (result) {
1081
+ html += ctx.renderResultImages();
1082
+ const output = ctx.getResultText();
1083
+ if (output) html += formatExpandableOutput(output, 10);
1084
+ }
1085
+ return html;
1086
+ }
1087
+
1088
+ function renderInspectImage(name, args, result, ctx) {
1089
+ const p = str(args.path == null ? args.url : args.path) || '';
1090
+ let html = toolHead('inspect_image', escapeHtml(shortenPath(p)));
1091
+ if (result) {
1092
+ html += ctx.renderResultImages();
1093
+ const output = ctx.getResultText();
1094
+ if (output) html += formatExpandableOutput(output, 8);
1095
+ }
1096
+ return html;
1097
+ }
1098
+
1099
+ function renderGenerateImage(name, args, result, ctx) {
1100
+ const subject = str(args.subject) || '';
1101
+ const badges = args.aspect_ratio ? [String(args.aspect_ratio)] : null;
1102
+ let html = toolHead('generate_image', '', badges);
1103
+ if (subject) html += '<div class="tool-output"><div>' + escapeHtml(subject) + '</div></div>';
1104
+ if (result) {
1105
+ html += ctx.renderResultImages();
1106
+ }
1107
+ return html;
1108
+ }
1109
+
1110
+ function renderAsk(name, args, result, ctx) {
1111
+ let html = toolHead('ask');
1112
+ const questions = Array.isArray(args.questions) ? args.questions : null;
1113
+ if (questions) {
1114
+ html += '<div class="tool-args">';
1115
+ for (const q of questions) {
1116
+ html += '<div class="tool-arg"><span class="tool-arg-key">Q:</span> ' + escapeHtml(String(q?.question || '')) + '</div>';
1117
+ if (Array.isArray(q?.options)) {
1118
+ for (const opt of q.options) {
1119
+ html += '<div class="tool-arg"><span class="tool-arg-key"> -</span> ' + escapeHtml(String(opt?.label || '')) + '</div>';
1120
+ }
814
1121
  }
815
1122
  }
1123
+ html += '</div>';
1124
+ }
1125
+ if (result) {
1126
+ const output = ctx.getResultText();
1127
+ if (output) html += formatExpandableOutput(output, 8);
1128
+ }
1129
+ return html;
1130
+ }
1131
+
1132
+ function renderExitPlanMode(name, args, result, ctx) {
1133
+ const badges = args.title ? [String(args.title)] : null;
1134
+ let html = toolHead('exit_plan_mode', '', badges);
1135
+ if (result) {
1136
+ const output = ctx.getResultText();
1137
+ if (output) html += formatExpandableOutput(output, 8);
816
1138
  }
1139
+ return html;
1140
+ }
1141
+
1142
+ function renderResolve(name, args, result, ctx) {
1143
+ const action = str(args.action) || '?';
1144
+ let html = toolHead('resolve', '', [action]);
1145
+ if (args.reason) html += '<div class="tool-output"><div>' + escapeHtml(String(args.reason)) + '</div></div>';
1146
+ if (result) {
1147
+ const output = ctx.getResultText();
1148
+ if (output) html += formatExpandableOutput(output, 6);
1149
+ }
1150
+ return html;
1151
+ }
817
1152
 
1153
+ function renderGh(name, args, result, ctx) {
1154
+ const badges = [];
1155
+ if (args.repo) badges.push(String(args.repo));
1156
+ if (args.issue) badges.push('#' + args.issue);
1157
+ if (args.pr) badges.push('PR ' + args.pr);
1158
+ if (args.branch) badges.push('branch=' + args.branch);
1159
+ if (args.query) badges.push('query=' + args.query);
1160
+ if (args.run) badges.push('run=' + args.run);
1161
+ let html = toolHead(name, '', badges);
1162
+ if (result) {
1163
+ const output = ctx.getResultText();
1164
+ if (output) html += formatExpandableOutput(output, 12, 'markdown');
1165
+ }
1166
+ return html;
1167
+ }
1168
+
1169
+ function renderMermaid(name, args, result, ctx) {
1170
+ let html = toolHead('render_mermaid');
1171
+ const code = args.code || args.source;
1172
+ if (code) html += codeBlock(String(code), 'mermaid');
1173
+ if (result) {
1174
+ html += ctx.renderResultImages();
1175
+ const output = ctx.getResultText();
1176
+ if (output) html += formatExpandableOutput(output, 6);
1177
+ }
1178
+ return html;
1179
+ }
1180
+
1181
+ function renderSubmitResult(name, args, result, ctx) {
1182
+ let html = toolHead('submit_result');
1183
+ if (args.data !== undefined) {
1184
+ html += '<div class="tool-output"><pre>' + escapeHtml(JSON.stringify(args.data, null, 2)) + '</pre></div>';
1185
+ }
1186
+ if (result) {
1187
+ const output = ctx.getResultText();
1188
+ if (output) html += formatExpandableOutput(output, 6);
1189
+ }
1190
+ return html;
1191
+ }
1192
+
1193
+ function renderReportFinding(name, args, result, ctx) {
1194
+ const badges = [];
1195
+ if (args.priority) badges.push('priority=' + args.priority);
1196
+ if (args.confidence != null) badges.push('confidence=' + args.confidence);
1197
+ if (args.file_path) badges.push(shortenPath(String(args.file_path)));
1198
+ let html = toolHead('report_finding', args.title ? escapeHtml(String(args.title)) : '', badges);
1199
+ if (args.body) html += '<div class="tool-output"><div>' + escapeHtml(String(args.body)) + '</div></div>';
1200
+ return html;
1201
+ }
1202
+
1203
+ function renderReportToolIssue(name, args, result, ctx) {
1204
+ const pathHtml = args.tool ? '<span class="tool-badge">' + escapeHtml(String(args.tool)) + '</span>' : '';
1205
+ let html = toolHead('report_tool_issue', pathHtml);
1206
+ if (args.report) html += '<div class="tool-output"><div>' + escapeHtml(String(args.report)) + '</div></div>';
1207
+ return html;
1208
+ }
1209
+
1210
+ function renderCalc(name, args, result, ctx) {
1211
+ let html = toolHead('calc');
1212
+ const exprs = args.expressions || (args.expression ? [args.expression] : []);
1213
+ for (const e of exprs) html += codeBlock(String(e), 'plaintext');
1214
+ if (result) {
1215
+ const output = ctx.getResultText();
1216
+ if (output) html += formatExpandableOutput(output, 6);
1217
+ }
1218
+ return html;
1219
+ }
1220
+
1221
+ function renderAwait(name, args, result, ctx) {
1222
+ const badges = [];
1223
+ if (Array.isArray(args.jobIds)) badges.push(args.jobIds.length + ' job' + (args.jobIds.length === 1 ? '' : 's'));
1224
+ let html = toolHead('await', '', badges);
1225
+ if (result) {
1226
+ const output = ctx.getResultText();
1227
+ if (output) html += formatExpandableOutput(output, 8);
1228
+ }
1229
+ return html;
1230
+ }
1231
+
1232
+ function renderCancelJob(name, args, result, ctx) {
1233
+ let html = toolHead('cancel_job', args.jobId ? escapeHtml(String(args.jobId)) : '');
1234
+ if (result) {
1235
+ const output = ctx.getResultText();
1236
+ if (output) html += formatExpandableOutput(output, 4);
1237
+ }
1238
+ return html;
1239
+ }
1240
+
1241
+ function renderGenericTool(name, args, result, ctx) {
1242
+ let html = toolHead(name);
1243
+ const argText = JSON.stringify(args, null, 2);
1244
+ if (argText && argText !== '{}') {
1245
+ html += '<div class="tool-output"><pre>' + escapeHtml(argText) + '</pre></div>';
1246
+ }
1247
+ if (result) {
1248
+ html += ctx.renderResultImages();
1249
+ const output = ctx.getResultText();
1250
+ if (output) html += formatExpandableOutput(output, 10);
1251
+ }
1252
+ return html;
1253
+ }
1254
+
1255
+ const TOOL_RENDERERS = {
1256
+ bash: renderBash,
1257
+ js: renderJsLike,
1258
+ python: renderJsLike,
1259
+ notebook: renderJsLike,
1260
+ read: renderRead,
1261
+ write: renderWrite,
1262
+ edit: renderEdit,
1263
+ ast_edit: renderAstEdit,
1264
+ ast_grep: renderAstGrep,
1265
+ grep: renderGrep,
1266
+ find: renderFind,
1267
+ lsp: renderLsp,
1268
+ todo_write: renderTodoWrite,
1269
+ task: renderTask,
1270
+ web_search: renderWebSearch,
1271
+ fetch: renderFetch,
1272
+ debug: renderDebug,
1273
+ puppeteer: renderPuppeteer,
1274
+ inspect_image: renderInspectImage,
1275
+ generate_image: renderGenerateImage,
1276
+ ask: renderAsk,
1277
+ exit_plan_mode: renderExitPlanMode,
1278
+ resolve: renderResolve,
1279
+ gh_repo_view: renderGh,
1280
+ gh_issue_view: renderGh,
1281
+ gh_pr_view: renderGh,
1282
+ gh_pr_diff: renderGh,
1283
+ gh_pr_checkout: renderGh,
1284
+ gh_pr_push: renderGh,
1285
+ gh_run_watch: renderGh,
1286
+ gh_search_issues: renderGh,
1287
+ gh_search_prs: renderGh,
1288
+ render_mermaid: renderMermaid,
1289
+ submit_result: renderSubmitResult,
1290
+ report_finding: renderReportFinding,
1291
+ report_tool_issue: renderReportToolIssue,
1292
+ calc: renderCalc,
1293
+ calculator: renderCalc,
1294
+ await: renderAwait,
1295
+ cancel_job: renderCancelJob,
1296
+ };
1297
+
1298
+ function renderToolCall(call) {
1299
+ const result = findToolResult(call.id);
1300
+ const isError = result?.isError || false;
1301
+ const statusClass = result ? (isError ? 'error' : 'success') : 'pending';
1302
+ const args = call.arguments || {};
1303
+ const name = call.name;
1304
+
1305
+ const ctx = {
1306
+ getResultText: () => {
1307
+ if (!result) return '';
1308
+ const textBlocks = result.content.filter(c => c.type === 'text');
1309
+ return textBlocks.map(c => c.text).join('\n');
1310
+ },
1311
+ getResultImages: () => {
1312
+ if (!result) return [];
1313
+ return result.content.filter(c => c.type === 'image');
1314
+ },
1315
+ renderResultImages: () => {
1316
+ if (!result) return '';
1317
+ const images = result.content.filter(c => c.type === 'image');
1318
+ if (images.length === 0) return '';
1319
+ return '<div class="tool-images">' +
1320
+ images.map(img => '<img src="data:' + img.mimeType + ';base64,' + img.data + '" class="tool-image" />').join('') +
1321
+ '</div>';
1322
+ },
1323
+ };
1324
+
1325
+ const renderer = TOOL_RENDERERS[name] || renderGenericTool;
1326
+ let html = '<div class="tool-execution ' + statusClass + '">';
1327
+ try {
1328
+ html += renderer(name, args, result, ctx);
1329
+ } catch (err) {
1330
+ html += renderGenericTool(name, args, result, ctx);
1331
+ }
818
1332
  html += '</div>';
819
1333
  return html;
820
1334
  }
821
1335
 
1336
+
822
1337
  /**
823
1338
  * Build a shareable URL for a specific message.
824
1339
  * URL format: base?gistId&leafId=<leafId>&targetId=<entryId>
@@ -977,7 +1492,7 @@
977
1492
  return html;
978
1493
  }
979
1494
 
980
- if (msg.role === 'pythonExecution') {
1495
+ if (msg.role === 'jsExecution') {
981
1496
  const isError = msg.cancelled || (msg.exitCode !== 0 && msg.exitCode !== null);
982
1497
  let html = `<div class="tool-execution ${isError ? 'error' : 'success'}" id="${entryId}">${tsHtml}`;
983
1498
  html += `<div class="tool-command">$ ${escapeHtml(msg.code)}</div>`;