@oh-my-pi/pi-coding-agent 3.37.0 → 4.0.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 (70) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +44 -3
  3. package/docs/extensions.md +29 -4
  4. package/docs/sdk.md +3 -3
  5. package/package.json +5 -5
  6. package/src/cli/args.ts +8 -0
  7. package/src/config.ts +5 -15
  8. package/src/core/agent-session.ts +193 -47
  9. package/src/core/auth-storage.ts +16 -3
  10. package/src/core/bash-executor.ts +79 -14
  11. package/src/core/custom-commands/types.ts +1 -1
  12. package/src/core/custom-tools/types.ts +1 -1
  13. package/src/core/export-html/index.ts +33 -1
  14. package/src/core/export-html/template.css +99 -0
  15. package/src/core/export-html/template.generated.ts +1 -1
  16. package/src/core/export-html/template.js +133 -8
  17. package/src/core/extensions/index.ts +22 -4
  18. package/src/core/extensions/loader.ts +152 -214
  19. package/src/core/extensions/runner.ts +139 -79
  20. package/src/core/extensions/types.ts +143 -19
  21. package/src/core/extensions/wrapper.ts +5 -8
  22. package/src/core/hooks/types.ts +1 -1
  23. package/src/core/index.ts +2 -1
  24. package/src/core/keybindings.ts +4 -1
  25. package/src/core/model-registry.ts +1 -1
  26. package/src/core/model-resolver.ts +35 -26
  27. package/src/core/sdk.ts +96 -76
  28. package/src/core/settings-manager.ts +45 -14
  29. package/src/core/system-prompt.ts +5 -15
  30. package/src/core/tools/bash.ts +115 -54
  31. package/src/core/tools/find.ts +86 -7
  32. package/src/core/tools/grep.ts +27 -6
  33. package/src/core/tools/index.ts +15 -6
  34. package/src/core/tools/ls.ts +49 -18
  35. package/src/core/tools/render-utils.ts +2 -1
  36. package/src/core/tools/task/worker.ts +35 -12
  37. package/src/core/tools/web-search/auth.ts +37 -32
  38. package/src/core/tools/web-search/providers/anthropic.ts +35 -22
  39. package/src/index.ts +101 -9
  40. package/src/main.ts +60 -20
  41. package/src/migrations.ts +47 -2
  42. package/src/modes/index.ts +2 -2
  43. package/src/modes/interactive/components/assistant-message.ts +25 -7
  44. package/src/modes/interactive/components/bash-execution.ts +5 -0
  45. package/src/modes/interactive/components/branch-summary-message.ts +5 -0
  46. package/src/modes/interactive/components/compaction-summary-message.ts +5 -0
  47. package/src/modes/interactive/components/countdown-timer.ts +38 -0
  48. package/src/modes/interactive/components/custom-editor.ts +8 -0
  49. package/src/modes/interactive/components/custom-message.ts +5 -0
  50. package/src/modes/interactive/components/footer.ts +2 -5
  51. package/src/modes/interactive/components/hook-input.ts +29 -20
  52. package/src/modes/interactive/components/hook-selector.ts +52 -38
  53. package/src/modes/interactive/components/index.ts +39 -0
  54. package/src/modes/interactive/components/login-dialog.ts +160 -0
  55. package/src/modes/interactive/components/model-selector.ts +10 -2
  56. package/src/modes/interactive/components/session-selector.ts +5 -1
  57. package/src/modes/interactive/components/settings-defs.ts +9 -0
  58. package/src/modes/interactive/components/status-line/segments.ts +3 -3
  59. package/src/modes/interactive/components/tool-execution.ts +9 -16
  60. package/src/modes/interactive/components/tree-selector.ts +1 -6
  61. package/src/modes/interactive/interactive-mode.ts +466 -215
  62. package/src/modes/interactive/theme/theme.ts +50 -2
  63. package/src/modes/print-mode.ts +78 -31
  64. package/src/modes/rpc/rpc-mode.ts +186 -78
  65. package/src/modes/rpc/rpc-types.ts +10 -3
  66. package/src/prompts/system-prompt.md +36 -28
  67. package/src/utils/clipboard.ts +90 -50
  68. package/src/utils/image-convert.ts +1 -1
  69. package/src/utils/image-resize.ts +1 -1
  70. package/src/utils/tools-manager.ts +2 -2
@@ -12,7 +12,21 @@
12
12
  bytes[i] = binary.charCodeAt(i);
13
13
  }
14
14
  const data = JSON.parse(new TextDecoder('utf-8').decode(bytes));
15
- const { header, entries, leafId, systemPrompt, tools } = data;
15
+ const { header, entries, leafId: defaultLeafId, systemPrompt, codexInjectionInfo, tools } = data;
16
+
17
+ // ============================================================
18
+ // URL PARAMETER HANDLING
19
+ // ============================================================
20
+
21
+ // Parse URL parameters for deep linking: leafId and targetId
22
+ // Check for injected params (when loaded in iframe via srcdoc) or use window.location
23
+ const injectedParams = document.querySelector('meta[name="pi-url-params"]');
24
+ const searchString = injectedParams ? injectedParams.content : window.location.search.substring(1);
25
+ const urlParams = new URLSearchParams(searchString);
26
+ const urlLeafId = urlParams.get('leafId');
27
+ const urlTargetId = urlParams.get('targetId');
28
+ // Use URL leafId if provided, otherwise fall back to session default
29
+ const leafId = urlLeafId || defaultLeafId;
16
30
 
17
31
  // ============================================================
18
32
  // DATA STRUCTURES
@@ -777,16 +791,98 @@
777
791
  return html;
778
792
  }
779
793
 
794
+ /**
795
+ * Build a shareable URL for a specific message.
796
+ * URL format: base?gistId&leafId=<leafId>&targetId=<entryId>
797
+ */
798
+ function buildShareUrl(entryId) {
799
+ // Check for injected base URL (used when loaded in iframe via srcdoc)
800
+ const baseUrlMeta = document.querySelector('meta[name="pi-share-base-url"]');
801
+ const baseUrl = baseUrlMeta ? baseUrlMeta.content : window.location.href.split('?')[0];
802
+
803
+ const url = new URL(window.location.href);
804
+ // Find the gist ID (first query param without value, e.g., ?abc123)
805
+ const gistId = Array.from(url.searchParams.keys()).find(k => !url.searchParams.get(k));
806
+
807
+ // Build the share URL
808
+ const params = new URLSearchParams();
809
+ params.set('leafId', currentLeafId);
810
+ params.set('targetId', entryId);
811
+
812
+ // If we have an injected base URL (iframe context), use it directly
813
+ if (baseUrlMeta) {
814
+ return `${baseUrl}&${params.toString()}`;
815
+ }
816
+
817
+ // Otherwise build from current location (direct file access)
818
+ url.search = gistId ? `?${gistId}&${params.toString()}` : `?${params.toString()}`;
819
+ return url.toString();
820
+ }
821
+
822
+ /**
823
+ * Copy text to clipboard with visual feedback.
824
+ * Uses navigator.clipboard with fallback to execCommand for HTTP contexts.
825
+ */
826
+ async function copyToClipboard(text, button) {
827
+ let success = false;
828
+ try {
829
+ if (navigator.clipboard && navigator.clipboard.writeText) {
830
+ await navigator.clipboard.writeText(text);
831
+ success = true;
832
+ }
833
+ } catch {
834
+ // Clipboard API failed, try fallback
835
+ }
836
+
837
+ // Fallback for HTTP or when Clipboard API is unavailable
838
+ if (!success) {
839
+ try {
840
+ const textarea = document.createElement('textarea');
841
+ textarea.value = text;
842
+ textarea.style.position = 'fixed';
843
+ textarea.style.opacity = '0';
844
+ document.body.appendChild(textarea);
845
+ textarea.select();
846
+ success = document.execCommand('copy');
847
+ document.body.removeChild(textarea);
848
+ } catch {
849
+ }
850
+ }
851
+
852
+ if (success && button) {
853
+ const originalHtml = button.innerHTML;
854
+ button.innerHTML = '✓';
855
+ button.classList.add('copied');
856
+ setTimeout(() => {
857
+ button.innerHTML = originalHtml;
858
+ button.classList.remove('copied');
859
+ }, 1500);
860
+ }
861
+ }
862
+
863
+ /**
864
+ * Render the copy-link button HTML for a message.
865
+ */
866
+ function renderCopyLinkButton(entryId) {
867
+ return `<button class="copy-link-btn" data-entry-id="${entryId}" title="Copy link to this message">
868
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
869
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
870
+ <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
871
+ </svg>
872
+ </button>`;
873
+ }
874
+
780
875
  function renderEntry(entry) {
781
876
  const ts = formatTimestamp(entry.timestamp);
782
877
  const tsHtml = ts ? `<div class="message-timestamp">${ts}</div>` : '';
783
878
  const entryId = `entry-${entry.id}`;
879
+ const copyBtnHtml = renderCopyLinkButton(entry.id);
784
880
 
785
881
  if (entry.type === 'message') {
786
882
  const msg = entry.message;
787
883
 
788
884
  if (msg.role === 'user') {
789
- let html = `<div class="user-message" id="${entryId}">${tsHtml}`;
885
+ let html = `<div class="user-message" id="${entryId}">${copyBtnHtml}${tsHtml}`;
790
886
  const content = msg.content;
791
887
 
792
888
  if (Array.isArray(content)) {
@@ -810,7 +906,7 @@
810
906
  }
811
907
 
812
908
  if (msg.role === 'assistant') {
813
- let html = `<div class="assistant-message" id="${entryId}">${tsHtml}`;
909
+ let html = `<div class="assistant-message" id="${entryId}">${copyBtnHtml}${tsHtml}`;
814
910
 
815
911
  for (const block of msg.content) {
816
912
  if (block.type === 'text' && block.text.trim()) {
@@ -857,7 +953,16 @@
857
953
  }
858
954
 
859
955
  if (entry.type === 'model_change') {
860
- return `<div class="model-change" id="${entryId}">${tsHtml}Switched to model: <span class="model-name">${escapeHtml(entry.provider)}/${escapeHtml(entry.modelId)}</span></div>`;
956
+ let html = `<div class="model-change" id="${entryId}">${tsHtml}Switched to model: <span class="model-name">${escapeHtml(entry.provider)}/${escapeHtml(entry.modelId)}</span>`;
957
+
958
+ if (entry.provider === 'openai-codex' && codexInjectionInfo) {
959
+ const fullContent = `# Codex Instructions\n${codexInjectionInfo.instructions}\n\n# Codex-Pi Bridge\n${codexInjectionInfo.bridge}`;
960
+ html += ` <span class="codex-bridge-toggle" onclick="event.stopPropagation(); this.parentElement.classList.toggle('show-bridge')">[bridge prompt]</span>`;
961
+ html += `<div class="codex-bridge-content"><pre>${escapeHtml(fullContent)}</pre></div>`;
962
+ }
963
+
964
+ html += '</div>';
965
+ return html;
861
966
  }
862
967
 
863
968
  if (entry.type === 'compaction') {
@@ -1010,7 +1115,7 @@
1010
1115
  return node;
1011
1116
  }
1012
1117
 
1013
- function navigateTo(targetId, scrollMode = 'target') {
1118
+ function navigateTo(targetId, scrollMode = 'target', scrollToEntryId = null) {
1014
1119
  currentLeafId = targetId;
1015
1120
  const path = getPath(targetId);
1016
1121
 
@@ -1032,15 +1137,30 @@
1032
1137
  messagesEl.innerHTML = '';
1033
1138
  messagesEl.appendChild(fragment);
1034
1139
 
1140
+ // Attach click handlers for copy-link buttons
1141
+ messagesEl.querySelectorAll('.copy-link-btn').forEach(btn => {
1142
+ btn.addEventListener('click', (e) => {
1143
+ e.stopPropagation();
1144
+ const entryId = btn.dataset.entryId;
1145
+ const shareUrl = buildShareUrl(entryId);
1146
+ copyToClipboard(shareUrl, btn);
1147
+ });
1148
+ });
1149
+
1035
1150
  // Use setTimeout(0) to ensure DOM is fully laid out before scrolling
1036
1151
  setTimeout(() => {
1037
1152
  const content = document.getElementById('content');
1038
1153
  if (scrollMode === 'bottom') {
1039
1154
  content.scrollTop = content.scrollHeight;
1040
1155
  } else if (scrollMode === 'target') {
1041
- const targetEl = document.getElementById(`entry-${targetId}`);
1156
+ const scrollTargetId = scrollToEntryId || targetId;
1157
+ const targetEl = document.getElementById(`entry-${scrollTargetId}`);
1042
1158
  if (targetEl) {
1043
1159
  targetEl.scrollIntoView({ block: 'center' });
1160
+ if (scrollToEntryId) {
1161
+ targetEl.classList.add('highlight');
1162
+ setTimeout(() => targetEl.classList.remove('highlight'), 2000);
1163
+ }
1044
1164
  }
1045
1165
  }
1046
1166
  }, 0);
@@ -1175,9 +1295,14 @@
1175
1295
  }
1176
1296
  });
1177
1297
 
1178
- // Initial render - don't scroll, stay at top
1298
+ // Initial render
1299
+ // If URL has targetId, scroll to that specific message; otherwise stay at top
1179
1300
  if (leafId) {
1180
- navigateTo(leafId, 'none');
1301
+ if (urlTargetId && byId.has(urlTargetId)) {
1302
+ navigateTo(leafId, 'target', urlTargetId);
1303
+ } else {
1304
+ navigateTo(leafId, 'none');
1305
+ }
1181
1306
  } else if (entries.length > 0) {
1182
1307
  // Fallback: use last entry if no leafId
1183
1308
  navigateTo(entries[entries.length - 1].id, 'none');
@@ -2,8 +2,14 @@
2
2
  * Extension system for lifecycle events and custom tools.
3
3
  */
4
4
 
5
- export { discoverAndLoadExtensions, loadExtensionFromFactory, loadExtensions } from "./loader";
6
- export type { BranchHandler, ExtensionErrorListener, NavigateTreeHandler, NewSessionHandler } from "./runner";
5
+ export { createExtensionRuntime, discoverAndLoadExtensions, loadExtensionFromFactory, loadExtensions } from "./loader";
6
+ export type {
7
+ BranchHandler,
8
+ ExtensionErrorListener,
9
+ NavigateTreeHandler,
10
+ NewSessionHandler,
11
+ ShutdownHandler,
12
+ } from "./runner";
7
13
  export { ExtensionRunner } from "./runner";
8
14
  export type {
9
15
  AgentEndEvent,
@@ -11,6 +17,7 @@ export type {
11
17
  // Re-exports
12
18
  AgentToolResult,
13
19
  AgentToolUpdateCallback,
20
+ AppAction,
14
21
  AppendEntryHandler,
15
22
  BashToolResultEvent,
16
23
  BeforeAgentStartEvent,
@@ -23,26 +30,32 @@ export type {
23
30
  EditToolResultEvent,
24
31
  ExecOptions,
25
32
  ExecResult,
33
+ Extension,
34
+ ExtensionActions,
26
35
  // API
27
36
  ExtensionAPI,
28
37
  ExtensionCommandContext,
38
+ ExtensionCommandContextActions,
29
39
  // Context
30
40
  ExtensionContext,
41
+ ExtensionContextActions,
31
42
  // Errors
32
43
  ExtensionError,
33
44
  ExtensionEvent,
34
45
  ExtensionFactory,
35
46
  ExtensionFlag,
36
47
  ExtensionHandler,
48
+ ExtensionRuntime,
37
49
  ExtensionShortcut,
38
50
  ExtensionUIContext,
51
+ ExtensionUIDialogOptions,
39
52
  FindToolResultEvent,
40
53
  GetActiveToolsHandler,
41
54
  GetAllToolsHandler,
55
+ GetThinkingLevelHandler,
42
56
  GrepToolResultEvent,
57
+ KeybindingsManager,
43
58
  LoadExtensionsResult,
44
- // Loaded Extension
45
- LoadedExtension,
46
59
  LsToolResultEvent,
47
60
  // Message Rendering
48
61
  MessageRenderer,
@@ -52,6 +65,7 @@ export type {
52
65
  RegisteredCommand,
53
66
  RegisteredTool,
54
67
  SendMessageHandler,
68
+ SendUserMessageHandler,
55
69
  SessionBeforeBranchEvent,
56
70
  SessionBeforeBranchResult,
57
71
  SessionBeforeCompactEvent,
@@ -69,6 +83,8 @@ export type {
69
83
  SessionSwitchEvent,
70
84
  SessionTreeEvent,
71
85
  SetActiveToolsHandler,
86
+ SetModelHandler,
87
+ SetThinkingLevelHandler,
72
88
  // Events - Tool
73
89
  ToolCallEvent,
74
90
  ToolCallEventResult,
@@ -80,6 +96,8 @@ export type {
80
96
  TreePreparation,
81
97
  TurnEndEvent,
82
98
  TurnStartEvent,
99
+ UserBashEvent,
100
+ UserBashEventResult,
83
101
  WriteToolResultEvent,
84
102
  } from "./types";
85
103
  // Type guards