@oh-my-pi/pi-coding-agent 3.37.1 → 4.0.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +103 -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 +217 -51
  9. package/src/core/auth-storage.ts +456 -47
  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 +4 -4
  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
@@ -14,6 +14,7 @@ import { nanoid } from "nanoid";
14
14
  import stripAnsi from "strip-ansi";
15
15
  import { getShellConfig, killProcessTree, sanitizeBinaryOutput } from "../utils/shell";
16
16
  import { getOrCreateSnapshot, getSnapshotSourceCommand } from "../utils/shell-snapshot";
17
+ import type { BashOperations } from "./tools/bash";
17
18
  import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate";
18
19
  import { ScopeSignal } from "./utils";
19
20
 
@@ -59,6 +60,19 @@ function createSanitizer(): TransformStream<Uint8Array, string> {
59
60
  });
60
61
  }
61
62
 
63
+ async function pumpStream(readable: ReadableStream<Uint8Array>, writer: WritableStreamDefaultWriter<string>) {
64
+ const reader = readable.pipeThrough(createSanitizer()).getReader();
65
+ try {
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done) break;
69
+ await writer.write(value);
70
+ }
71
+ } finally {
72
+ reader.releaseLock();
73
+ }
74
+ }
75
+
62
76
  function createOutputSink(
63
77
  spillThreshold: number,
64
78
  maxBuffer: number,
@@ -156,21 +170,9 @@ export async function executeBash(command: string, options?: BashExecutorOptions
156
170
 
157
171
  const writer = sink.getWriter();
158
172
  try {
159
- async function pumpStream(readable: ReadableStream<Uint8Array>) {
160
- const reader = readable.pipeThrough(createSanitizer()).getReader();
161
- try {
162
- while (true) {
163
- const { done, value } = await reader.read();
164
- if (done) break;
165
- await writer.write(value);
166
- }
167
- } finally {
168
- reader.releaseLock();
169
- }
170
- }
171
173
  await Promise.all([
172
- pumpStream(child.stdout as ReadableStream<Uint8Array>),
173
- pumpStream(child.stderr as ReadableStream<Uint8Array>),
174
+ pumpStream(child.stdout as ReadableStream<Uint8Array>, writer),
175
+ pumpStream(child.stderr as ReadableStream<Uint8Array>, writer),
174
176
  ]);
175
177
  } finally {
176
178
  await writer.close();
@@ -196,3 +198,66 @@ export async function executeBash(command: string, options?: BashExecutorOptions
196
198
  ...sink.dump(),
197
199
  };
198
200
  }
201
+
202
+ /**
203
+ * Execute a bash command using custom BashOperations.
204
+ * Used for remote execution (SSH, containers, etc.).
205
+ */
206
+ export async function executeBashWithOperations(
207
+ command: string,
208
+ cwd: string,
209
+ operations: BashOperations,
210
+ options?: BashExecutorOptions,
211
+ ): Promise<BashResult> {
212
+ const sink = createOutputSink(DEFAULT_MAX_BYTES, DEFAULT_MAX_BYTES * 2, options?.onChunk);
213
+ const writer = sink.getWriter();
214
+
215
+ // Create a ReadableStream from the callback-based operations.exec
216
+ let streamController: ReadableStreamDefaultController<Uint8Array>;
217
+ const dataStream = new ReadableStream<Uint8Array>({
218
+ start(controller) {
219
+ streamController = controller;
220
+ },
221
+ });
222
+
223
+ const onData = (data: Buffer) => {
224
+ streamController.enqueue(new Uint8Array(data));
225
+ };
226
+
227
+ // Start pumping the stream (will complete when stream closes)
228
+ const pumpPromise = pumpStream(dataStream, writer);
229
+
230
+ try {
231
+ const result = await operations.exec(command, cwd, {
232
+ onData,
233
+ signal: options?.signal,
234
+ timeout: options?.timeout,
235
+ });
236
+
237
+ streamController!.close();
238
+ await pumpPromise;
239
+ await writer.close();
240
+
241
+ const cancelled = options?.signal?.aborted ?? false;
242
+
243
+ return {
244
+ exitCode: cancelled ? undefined : (result.exitCode ?? undefined),
245
+ cancelled,
246
+ ...sink.dump(),
247
+ };
248
+ } catch (err) {
249
+ streamController!.close();
250
+ await pumpPromise;
251
+ await writer.close();
252
+
253
+ if (options?.signal?.aborted) {
254
+ return {
255
+ exitCode: undefined,
256
+ cancelled: true,
257
+ ...sink.dump(),
258
+ };
259
+ }
260
+
261
+ throw err;
262
+ }
263
+ }
@@ -23,7 +23,7 @@ export interface CustomCommandAPI {
23
23
  /** Injected @sinclair/typebox module */
24
24
  typebox: typeof import("@sinclair/typebox");
25
25
  /** Injected pi-coding-agent exports */
26
- pi: typeof import("../../index.js");
26
+ pi: typeof import("../../index");
27
27
  }
28
28
 
29
29
  /**
@@ -40,7 +40,7 @@ export interface CustomToolAPI {
40
40
  /** Injected @sinclair/typebox module */
41
41
  typebox: typeof import("@sinclair/typebox");
42
42
  /** Injected pi-coding-agent exports */
43
- pi: typeof import("../../index.js");
43
+ pi: typeof import("../../index");
44
44
  }
45
45
 
46
46
  /**
@@ -1,6 +1,7 @@
1
1
  import { existsSync, writeFileSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
- import type { AgentState } from "@oh-my-pi/pi-agent-core";
3
+ import type { AgentState, AgentTool } from "@oh-my-pi/pi-agent-core";
4
+ import { buildCodexPiBridge, getCodexInstructions } from "@oh-my-pi/pi-ai";
4
5
  import { APP_NAME } from "../../config";
5
6
  import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme";
6
7
  import { SessionManager } from "../session-manager";
@@ -13,6 +14,33 @@ export interface ExportOptions {
13
14
  themeName?: string;
14
15
  }
15
16
 
17
+ /** Info about Codex injection to show inline with model_change entries. */
18
+ interface CodexInjectionInfo {
19
+ /** Codex instructions text. */
20
+ instructions: string;
21
+ /** Bridge text (tool list). */
22
+ bridge: string;
23
+ }
24
+
25
+ /** Build Codex injection info for display inline with model_change entries. */
26
+ async function buildCodexInjectionInfo(tools?: AgentTool[]): Promise<CodexInjectionInfo | undefined> {
27
+ let instructions: string | null = null;
28
+ try {
29
+ instructions = await getCodexInstructions("gpt-5.1-codex");
30
+ } catch {
31
+ // Cache miss is expected before the first Codex request.
32
+ }
33
+
34
+ const bridgeText = buildCodexPiBridge(tools);
35
+ const instructionsText =
36
+ instructions ?? "(Codex instructions not cached. Run a Codex request to populate the local cache.)";
37
+
38
+ return {
39
+ instructions: instructionsText,
40
+ bridge: bridgeText,
41
+ };
42
+ }
43
+
16
44
  /** Parse a color string to RGB values. */
17
45
  function parseColor(color: string): { r: number; g: number; b: number } | undefined {
18
46
  const hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
@@ -97,6 +125,8 @@ interface SessionData {
97
125
  entries: ReturnType<SessionManager["getEntries"]>;
98
126
  leafId: string | null;
99
127
  systemPrompt?: string;
128
+ /** Info for rendering Codex injection inline with model_change entries. */
129
+ codexInjectionInfo?: CodexInjectionInfo;
100
130
  tools?: { name: string; description: string }[];
101
131
  }
102
132
 
@@ -128,6 +158,7 @@ export async function exportSessionToHtml(
128
158
  entries: sm.getEntries(),
129
159
  leafId: sm.getLeafId(),
130
160
  systemPrompt: state?.systemPrompt,
161
+ codexInjectionInfo: await buildCodexInjectionInfo(state?.tools),
131
162
  tools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),
132
163
  };
133
164
 
@@ -149,6 +180,7 @@ export async function exportFromFile(inputPath: string, options?: ExportOptions
149
180
  header: sm.getHeader(),
150
181
  entries: sm.getEntries(),
151
182
  leafId: sm.getLeafId(),
183
+ codexInjectionInfo: await buildCodexInjectionInfo(),
152
184
  };
153
185
 
154
186
  const html = generateHtml(sessionData, opts.themeName);
@@ -273,10 +273,65 @@
273
273
  color: var(--userMessageText);
274
274
  padding: var(--line-height);
275
275
  border-radius: 4px;
276
+ position: relative;
276
277
  }
277
278
 
278
279
  .assistant-message {
279
280
  padding: 0;
281
+ position: relative;
282
+ }
283
+
284
+ /* Copy link button - appears on hover */
285
+ .copy-link-btn {
286
+ position: absolute;
287
+ top: 8px;
288
+ right: 8px;
289
+ width: 28px;
290
+ height: 28px;
291
+ padding: 6px;
292
+ background: var(--container-bg);
293
+ border: 1px solid var(--dim);
294
+ border-radius: 4px;
295
+ color: var(--muted);
296
+ cursor: pointer;
297
+ opacity: 0;
298
+ transition: opacity 0.15s, background 0.15s, color 0.15s;
299
+ display: flex;
300
+ align-items: center;
301
+ justify-content: center;
302
+ z-index: 10;
303
+ }
304
+
305
+ .user-message:hover .copy-link-btn,
306
+ .assistant-message:hover .copy-link-btn {
307
+ opacity: 1;
308
+ }
309
+
310
+ .copy-link-btn:hover {
311
+ background: var(--accent);
312
+ color: var(--body-bg);
313
+ border-color: var(--accent);
314
+ }
315
+
316
+ .copy-link-btn.copied {
317
+ background: var(--success, #22c55e);
318
+ color: white;
319
+ border-color: var(--success, #22c55e);
320
+ }
321
+
322
+ /* Highlight effect for deep-linked messages */
323
+ .user-message.highlight,
324
+ .assistant-message.highlight {
325
+ animation: highlight-pulse 2s ease-out;
326
+ }
327
+
328
+ @keyframes highlight-pulse {
329
+ 0% {
330
+ box-shadow: 0 0 0 3px var(--accent);
331
+ }
332
+ 100% {
333
+ box-shadow: 0 0 0 0 transparent;
334
+ }
280
335
  }
281
336
 
282
337
  .assistant-message > .message-timestamp {
@@ -446,6 +501,39 @@
446
501
  font-weight: bold;
447
502
  }
448
503
 
504
+ .codex-bridge-toggle {
505
+ color: var(--muted);
506
+ cursor: pointer;
507
+ text-decoration: underline;
508
+ font-size: 10px;
509
+ }
510
+
511
+ .codex-bridge-toggle:hover {
512
+ color: var(--accent);
513
+ }
514
+
515
+ .codex-bridge-content {
516
+ display: none;
517
+ margin-top: 8px;
518
+ padding: 8px;
519
+ background: var(--exportCardBg, var(--container-bg));
520
+ border-radius: 4px;
521
+ font-size: 11px;
522
+ max-height: 300px;
523
+ overflow: auto;
524
+ }
525
+
526
+ .codex-bridge-content pre {
527
+ margin: 0;
528
+ white-space: pre-wrap;
529
+ word-break: break-word;
530
+ color: var(--muted);
531
+ }
532
+
533
+ .model-change.show-bridge .codex-bridge-content {
534
+ display: block;
535
+ }
536
+
449
537
  /* Compaction / Branch Summary - matches customMessage colors from TUI */
450
538
  .compaction {
451
539
  background: var(--customMessageBg);
@@ -501,6 +589,17 @@
501
589
  margin-top: var(--line-height);
502
590
  }
503
591
 
592
+ .system-prompt.provider-prompt {
593
+ border-left: 3px solid var(--warning);
594
+ }
595
+
596
+ .system-prompt-note {
597
+ font-size: 10px;
598
+ font-style: italic;
599
+ color: var(--muted);
600
+ margin-top: 4px;
601
+ }
602
+
504
603
  /* Tools list */
505
604
  .tools-list {
506
605
  background: var(--customMessageBg);