@oh-my-pi/pi-coding-agent 8.12.2 → 8.12.4
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/package.json +7 -10
- package/src/config/settings-manager.ts +36 -0
- package/src/config.ts +0 -5
- package/src/exec/bash-executor.ts +4 -4
- package/src/exec/exec.ts +9 -12
- package/src/extensibility/plugins/doctor.ts +0 -2
- package/src/ipy/kernel.ts +11 -13
- package/src/migrations.ts +1 -46
- package/src/modes/components/settings-defs.ts +23 -0
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/session/compaction/compaction.ts +37 -3
- package/src/ssh/ssh-executor.ts +3 -5
- package/src/system-prompt.ts +14 -9
- package/src/tools/ask.ts +38 -6
- package/src/tools/fetch.ts +9 -61
- package/src/tools/find.ts +19 -14
- package/src/tools/grep.ts +110 -562
- package/src/tools/read.ts +31 -25
- package/src/utils/image-convert.ts +7 -11
- package/src/utils/image-resize.ts +15 -25
- package/src/utils/tools-manager.ts +3 -43
- package/src/web/scrapers/utils.ts +11 -6
- package/src/web/scrapers/youtube.ts +21 -49
- package/src/utils/utils.ts +0 -1
- package/src/vendor/photon/LICENSE.md +0 -201
- package/src/vendor/photon/README.md +0 -158
- package/src/vendor/photon/index.d.ts +0 -3013
- package/src/vendor/photon/index.js +0 -4521
- package/src/vendor/photon/photon_rs_bg.wasm +0 -0
- package/src/vendor/photon/photon_rs_bg.wasm.d.ts +0 -193
package/src/tools/ask.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Users will always be able to select "Other" to provide custom text input
|
|
13
13
|
* - Use multi: true to allow multiple answers to be selected for a question
|
|
14
14
|
* - Use recommended: <index> to mark the default option; "(Recommended)" suffix is added automatically
|
|
15
|
-
* - Questions time out
|
|
15
|
+
* - Questions may time out and auto-select the recommended option (configurable, disabled in plan mode)
|
|
16
16
|
*/
|
|
17
17
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
18
18
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
@@ -23,6 +23,7 @@ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
|
23
23
|
import { type Theme, theme } from "../modes/theme/theme";
|
|
24
24
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
25
25
|
import { renderStatusLine } from "../tui";
|
|
26
|
+
import { detectNotificationProtocol, isNotificationSuppressed, sendNotification } from "../utils/terminal-notify";
|
|
26
27
|
import type { ToolSession } from ".";
|
|
27
28
|
import { ToolUIKit } from "./render-utils";
|
|
28
29
|
|
|
@@ -77,7 +78,8 @@ export interface AskToolDetails {
|
|
|
77
78
|
|
|
78
79
|
const OTHER_OPTION = "Other (type your own)";
|
|
79
80
|
const RECOMMENDED_SUFFIX = " (Recommended)";
|
|
80
|
-
|
|
81
|
+
/** Default timeout in milliseconds (used when settings unavailable) */
|
|
82
|
+
const DEFAULT_ASK_TIMEOUT_MS = 30000;
|
|
81
83
|
|
|
82
84
|
function getDoneOptionLabel(): string {
|
|
83
85
|
return `${theme.status.success} Done selecting`;
|
|
@@ -119,13 +121,20 @@ interface UIContext {
|
|
|
119
121
|
input(prompt: string): Promise<string | undefined>;
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
interface AskQuestionOptions {
|
|
125
|
+
/** Timeout in milliseconds, null/undefined to disable */
|
|
126
|
+
timeout?: number | null;
|
|
127
|
+
}
|
|
128
|
+
|
|
122
129
|
async function askSingleQuestion(
|
|
123
130
|
ui: UIContext,
|
|
124
131
|
question: string,
|
|
125
132
|
optionLabels: string[],
|
|
126
133
|
multi: boolean,
|
|
127
134
|
recommended?: number,
|
|
135
|
+
options?: AskQuestionOptions,
|
|
128
136
|
): Promise<SelectionResult> {
|
|
137
|
+
const timeout = options?.timeout ?? undefined;
|
|
129
138
|
const doneLabel = getDoneOptionLabel();
|
|
130
139
|
let selectedOptions: string[] = [];
|
|
131
140
|
let customInput: string | undefined;
|
|
@@ -152,11 +161,11 @@ async function askSingleQuestion(
|
|
|
152
161
|
const selectionStart = Date.now();
|
|
153
162
|
const choice = await ui.select(`${prefix}${question}`, opts, {
|
|
154
163
|
initialIndex: cursorIndex,
|
|
155
|
-
timeout:
|
|
164
|
+
timeout: timeout ?? undefined,
|
|
156
165
|
outline: true,
|
|
157
166
|
});
|
|
158
167
|
const elapsed = Date.now() - selectionStart;
|
|
159
|
-
const timedOut = elapsed >=
|
|
168
|
+
const timedOut = timeout != null && elapsed >= timeout;
|
|
160
169
|
|
|
161
170
|
if (choice === undefined || choice === doneLabel) break;
|
|
162
171
|
|
|
@@ -198,7 +207,7 @@ async function askSingleQuestion(
|
|
|
198
207
|
} else {
|
|
199
208
|
const displayLabels = addRecommendedSuffix(optionLabels, recommended);
|
|
200
209
|
const choice = await ui.select(question, [...displayLabels, OTHER_OPTION], {
|
|
201
|
-
timeout:
|
|
210
|
+
timeout: timeout ?? undefined,
|
|
202
211
|
initialIndex: recommended,
|
|
203
212
|
outline: true,
|
|
204
213
|
});
|
|
@@ -254,8 +263,10 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
254
263
|
public readonly label = "Ask";
|
|
255
264
|
public readonly description: string;
|
|
256
265
|
public readonly parameters = askSchema;
|
|
266
|
+
private readonly session: ToolSession;
|
|
257
267
|
|
|
258
|
-
constructor(
|
|
268
|
+
constructor(session: ToolSession) {
|
|
269
|
+
this.session = session;
|
|
259
270
|
this.description = renderPromptTemplate(askDescription);
|
|
260
271
|
}
|
|
261
272
|
|
|
@@ -263,6 +274,17 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
263
274
|
return session.hasUI ? new AskTool(session) : null;
|
|
264
275
|
}
|
|
265
276
|
|
|
277
|
+
/** Send terminal notification when ask tool is waiting for input */
|
|
278
|
+
private sendAskNotification(): void {
|
|
279
|
+
if (isNotificationSuppressed()) return;
|
|
280
|
+
|
|
281
|
+
const method = this.session.settingsManager?.getAskNotification() ?? "auto";
|
|
282
|
+
if (method === "off") return;
|
|
283
|
+
|
|
284
|
+
const protocol = method === "auto" ? detectNotificationProtocol() : method;
|
|
285
|
+
sendNotification(protocol, "Waiting for input");
|
|
286
|
+
}
|
|
287
|
+
|
|
266
288
|
public async execute(
|
|
267
289
|
_toolCallId: string,
|
|
268
290
|
params: AskParams,
|
|
@@ -280,6 +302,14 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
280
302
|
|
|
281
303
|
const { ui } = context;
|
|
282
304
|
|
|
305
|
+
// Determine timeout based on settings and plan mode
|
|
306
|
+
const planModeEnabled = this.session.getPlanModeState?.()?.enabled ?? false;
|
|
307
|
+
const settingsTimeout = this.session.settingsManager?.getAskTimeout() ?? DEFAULT_ASK_TIMEOUT_MS;
|
|
308
|
+
const timeout = planModeEnabled ? null : settingsTimeout;
|
|
309
|
+
|
|
310
|
+
// Send notification if waiting and not suppressed
|
|
311
|
+
this.sendAskNotification();
|
|
312
|
+
|
|
283
313
|
// Multi-part questions mode
|
|
284
314
|
if (params.questions && params.questions.length > 0) {
|
|
285
315
|
const results: QuestionResult[] = [];
|
|
@@ -292,6 +322,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
292
322
|
optionLabels,
|
|
293
323
|
q.multi ?? false,
|
|
294
324
|
q.recommended,
|
|
325
|
+
{ timeout },
|
|
295
326
|
);
|
|
296
327
|
|
|
297
328
|
results.push({
|
|
@@ -330,6 +361,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
330
361
|
optionLabels,
|
|
331
362
|
multi,
|
|
332
363
|
params.recommended,
|
|
364
|
+
{ timeout },
|
|
333
365
|
);
|
|
334
366
|
|
|
335
367
|
const details: AskToolDetails = {
|
package/src/tools/fetch.ts
CHANGED
|
@@ -75,65 +75,6 @@ const CONVERTIBLE_EXTENSIONS = new Set([
|
|
|
75
75
|
// Utilities
|
|
76
76
|
// =============================================================================
|
|
77
77
|
|
|
78
|
-
/**
|
|
79
|
-
* Execute a command and return stdout
|
|
80
|
-
*/
|
|
81
|
-
|
|
82
|
-
type WritableLike = {
|
|
83
|
-
write: (chunk: string | Uint8Array) => unknown;
|
|
84
|
-
flush?: () => unknown;
|
|
85
|
-
end?: () => unknown;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const textEncoder = new TextEncoder();
|
|
89
|
-
|
|
90
|
-
async function writeStdin(handle: unknown, input: string | Buffer): Promise<void> {
|
|
91
|
-
if (!handle || typeof handle === "number") return;
|
|
92
|
-
if (typeof (handle as WritableStream<Uint8Array>).getWriter === "function") {
|
|
93
|
-
const writer = (handle as WritableStream<Uint8Array>).getWriter();
|
|
94
|
-
try {
|
|
95
|
-
const chunk = typeof input === "string" ? textEncoder.encode(input) : new Uint8Array(input);
|
|
96
|
-
await writer.write(chunk);
|
|
97
|
-
} finally {
|
|
98
|
-
await writer.close();
|
|
99
|
-
}
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const sink = handle as WritableLike;
|
|
104
|
-
sink.write(input);
|
|
105
|
-
if (sink.flush) sink.flush();
|
|
106
|
-
if (sink.end) sink.end();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function exec(
|
|
110
|
-
cmd: string,
|
|
111
|
-
args: string[],
|
|
112
|
-
options?: { timeout?: number; input?: string | Buffer },
|
|
113
|
-
): Promise<{ stdout: string; stderr: string; ok: boolean }> {
|
|
114
|
-
const proc = ptree.cspawn([cmd, ...args], {
|
|
115
|
-
stdin: options?.input ? "pipe" : null,
|
|
116
|
-
timeout: options?.timeout ? options.timeout * 1000 : undefined,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
if (options?.input) {
|
|
120
|
-
await writeStdin(proc.stdin, options.input);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const [stdout, stderr] = await Promise.all([proc.stdout.text(), proc.stderr.text()]);
|
|
124
|
-
try {
|
|
125
|
-
await proc.exited;
|
|
126
|
-
} catch {
|
|
127
|
-
// Handle non-zero exit or timeout
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
stdout,
|
|
132
|
-
stderr,
|
|
133
|
-
ok: proc.exitCode === 0,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
78
|
/**
|
|
138
79
|
* Check if a command exists (cross-platform)
|
|
139
80
|
*/
|
|
@@ -456,13 +397,20 @@ async function renderHtmlToText(
|
|
|
456
397
|
|
|
457
398
|
try {
|
|
458
399
|
await Bun.write(tmpFile, html);
|
|
400
|
+
const execOptions = {
|
|
401
|
+
mode: "group" as const,
|
|
402
|
+
timeout: timeout * 1000,
|
|
403
|
+
allowNonZero: true,
|
|
404
|
+
allowAbort: true,
|
|
405
|
+
stderr: "full" as const,
|
|
406
|
+
};
|
|
459
407
|
|
|
460
408
|
// Try lynx first (can't auto-install, system package)
|
|
461
409
|
const lynx = hasCommand("lynx");
|
|
462
410
|
if (lynx) {
|
|
463
411
|
const normalizedPath = tmpFile.replace(/\\/g, "/");
|
|
464
412
|
const fileUrl = normalizedPath.startsWith("/") ? `file://${normalizedPath}` : `file:///${normalizedPath}`;
|
|
465
|
-
const result = await exec("lynx",
|
|
413
|
+
const result = await ptree.exec(["lynx", "-dump", "-nolist", "-width", "120", fileUrl], execOptions);
|
|
466
414
|
if (result.ok) {
|
|
467
415
|
return { content: result.stdout, ok: true, method: "lynx" };
|
|
468
416
|
}
|
|
@@ -471,7 +419,7 @@ async function renderHtmlToText(
|
|
|
471
419
|
// Fall back to html2text (auto-install via uv/pip)
|
|
472
420
|
const html2text = await ensureTool("html2text", true);
|
|
473
421
|
if (html2text) {
|
|
474
|
-
const result = await exec(html2text,
|
|
422
|
+
const result = await ptree.exec([html2text, tmpFile], execOptions);
|
|
475
423
|
if (result.ok) {
|
|
476
424
|
return { content: result.stdout, ok: true, method: "html2text" };
|
|
477
425
|
}
|
package/src/tools/find.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import { find as wasmFind } from "@oh-my-pi/pi-natives";
|
|
4
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
|
-
import {
|
|
7
|
+
import { isEnoent, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import type { Static } from "@sinclair/typebox";
|
|
8
9
|
import { Type } from "@sinclair/typebox";
|
|
9
10
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
@@ -169,22 +170,26 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
let lines: string[];
|
|
173
|
+
const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
|
|
174
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
172
175
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
const result = await untilAborted(combinedSignal, () =>
|
|
177
|
+
wasmFind({
|
|
178
|
+
pattern: globPattern,
|
|
179
|
+
path: searchPath,
|
|
180
|
+
fileType: "file",
|
|
181
|
+
hidden: includeHidden,
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
lines = result.matches.map(match => match.path);
|
|
180
185
|
} catch (error) {
|
|
181
186
|
if (error instanceof Error && error.name === "AbortError") {
|
|
187
|
+
if (timeoutSignal.aborted && !signal?.aborted) {
|
|
188
|
+
const timeoutSeconds = Math.max(1, Math.round(GLOB_TIMEOUT_MS / 1000));
|
|
189
|
+
throw new ToolError(`find timed out after ${timeoutSeconds}s`);
|
|
190
|
+
}
|
|
182
191
|
throw new ToolAbortError();
|
|
183
192
|
}
|
|
184
|
-
if (error instanceof Error && error.name === "TimeoutError") {
|
|
185
|
-
const timeoutSeconds = Math.max(1, Math.round(GLOB_TIMEOUT_MS / 1000));
|
|
186
|
-
throw new ToolError(`glob timed out after ${timeoutSeconds}s`);
|
|
187
|
-
}
|
|
188
193
|
throw error;
|
|
189
194
|
}
|
|
190
195
|
|
|
@@ -197,13 +202,13 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
197
202
|
|
|
198
203
|
for (const rawLine of lines) {
|
|
199
204
|
throwIfAborted(signal);
|
|
200
|
-
const line = rawLine
|
|
205
|
+
const line = rawLine;
|
|
201
206
|
if (!line) {
|
|
202
207
|
continue;
|
|
203
208
|
}
|
|
204
209
|
|
|
205
210
|
const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\");
|
|
206
|
-
let relativePath = line
|
|
211
|
+
let relativePath = line;
|
|
207
212
|
|
|
208
213
|
let mtimeMs = 0;
|
|
209
214
|
let isDirectory = false;
|