@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.
- package/CHANGELOG.md +103 -0
- package/README.md +44 -3
- package/docs/extensions.md +29 -4
- package/docs/sdk.md +3 -3
- package/package.json +5 -5
- package/src/cli/args.ts +8 -0
- package/src/config.ts +5 -15
- package/src/core/agent-session.ts +217 -51
- package/src/core/auth-storage.ts +456 -47
- package/src/core/bash-executor.ts +79 -14
- package/src/core/custom-commands/types.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/export-html/index.ts +33 -1
- package/src/core/export-html/template.css +99 -0
- package/src/core/export-html/template.generated.ts +1 -1
- package/src/core/export-html/template.js +133 -8
- package/src/core/extensions/index.ts +22 -4
- package/src/core/extensions/loader.ts +152 -214
- package/src/core/extensions/runner.ts +139 -79
- package/src/core/extensions/types.ts +143 -19
- package/src/core/extensions/wrapper.ts +5 -8
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +2 -1
- package/src/core/keybindings.ts +4 -1
- package/src/core/model-registry.ts +4 -4
- package/src/core/model-resolver.ts +35 -26
- package/src/core/sdk.ts +96 -76
- package/src/core/settings-manager.ts +45 -14
- package/src/core/system-prompt.ts +5 -15
- package/src/core/tools/bash.ts +115 -54
- package/src/core/tools/find.ts +86 -7
- package/src/core/tools/grep.ts +27 -6
- package/src/core/tools/index.ts +15 -6
- package/src/core/tools/ls.ts +49 -18
- package/src/core/tools/render-utils.ts +2 -1
- package/src/core/tools/task/worker.ts +35 -12
- package/src/core/tools/web-search/auth.ts +37 -32
- package/src/core/tools/web-search/providers/anthropic.ts +35 -22
- package/src/index.ts +101 -9
- package/src/main.ts +60 -20
- package/src/migrations.ts +47 -2
- package/src/modes/index.ts +2 -2
- package/src/modes/interactive/components/assistant-message.ts +25 -7
- package/src/modes/interactive/components/bash-execution.ts +5 -0
- package/src/modes/interactive/components/branch-summary-message.ts +5 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +5 -0
- package/src/modes/interactive/components/countdown-timer.ts +38 -0
- package/src/modes/interactive/components/custom-editor.ts +8 -0
- package/src/modes/interactive/components/custom-message.ts +5 -0
- package/src/modes/interactive/components/footer.ts +2 -5
- package/src/modes/interactive/components/hook-input.ts +29 -20
- package/src/modes/interactive/components/hook-selector.ts +52 -38
- package/src/modes/interactive/components/index.ts +39 -0
- package/src/modes/interactive/components/login-dialog.ts +160 -0
- package/src/modes/interactive/components/model-selector.ts +10 -2
- package/src/modes/interactive/components/session-selector.ts +5 -1
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line/segments.ts +3 -3
- package/src/modes/interactive/components/tool-execution.ts +9 -16
- package/src/modes/interactive/components/tree-selector.ts +1 -6
- package/src/modes/interactive/interactive-mode.ts +466 -215
- package/src/modes/interactive/theme/theme.ts +50 -2
- package/src/modes/print-mode.ts +78 -31
- package/src/modes/rpc/rpc-mode.ts +186 -78
- package/src/modes/rpc/rpc-types.ts +10 -3
- package/src/prompts/system-prompt.md +36 -28
- package/src/utils/clipboard.ts +90 -50
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- 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
|
|
26
|
+
pi: typeof import("../../index");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -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);
|