@oh-my-pi/pi-coding-agent 6.7.67 → 6.8.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.
- package/CHANGELOG.md +28 -0
- package/package.json +6 -7
- package/src/cli/session-picker.ts +27 -28
- package/src/cli/setup-cli.ts +7 -16
- package/src/cli/update-cli.ts +1 -1
- package/src/config.ts +1 -1
- package/src/core/agent-session.ts +202 -37
- package/src/core/agent-storage.ts +1 -1
- package/src/core/auth-storage.ts +15 -25
- package/src/core/bash-executor.ts +63 -105
- package/src/core/custom-commands/loader.ts +1 -1
- package/src/core/custom-tools/loader.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -2
- package/src/core/exec.ts +16 -100
- package/src/core/extensions/index.ts +1 -7
- package/src/core/extensions/loader.ts +1 -1
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +2 -2
- package/src/core/extensions/wrapper.ts +15 -20
- package/src/core/frontmatter.ts +1 -1
- package/src/core/history-storage.ts +3 -6
- package/src/core/hooks/index.ts +2 -2
- package/src/core/hooks/loader.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +14 -26
- package/src/core/hooks/types.ts +1 -2
- package/src/core/keybindings.ts +1 -1
- package/src/core/mcp/client.ts +13 -13
- package/src/core/mcp/json-rpc.ts +1 -1
- package/src/core/mcp/loader.ts +1 -1
- package/src/core/mcp/manager.ts +2 -2
- package/src/core/mcp/tool-cache.ts +1 -1
- package/src/core/mcp/transports/http.ts +32 -70
- package/src/core/model-registry.ts +1 -1
- package/src/core/plugins/installer.ts +13 -11
- package/src/core/prompt-templates.ts +4 -9
- package/src/core/python-executor.ts +23 -18
- package/src/core/python-gateway-coordinator.ts +29 -28
- package/src/core/python-kernel.ts +230 -211
- package/src/core/sdk.ts +10 -13
- package/src/core/session-manager.ts +1 -1
- package/src/core/settings-manager.ts +22 -9
- package/src/core/skills.ts +1 -1
- package/src/core/ssh/connection-manager.ts +19 -33
- package/src/core/ssh/ssh-executor.ts +39 -35
- package/src/core/ssh/sshfs-mount.ts +14 -33
- package/src/core/storage-migration.ts +1 -1
- package/src/core/streaming-output.ts +183 -127
- package/src/core/system-prompt.ts +119 -79
- package/src/core/title-generator.ts +1 -1
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +3 -3
- package/src/core/tools/calculator.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +1 -1
- package/src/core/tools/exa/render.ts +1 -1
- package/src/core/tools/find.ts +39 -71
- package/src/core/tools/gemini-image.ts +1 -1
- package/src/core/tools/grep.ts +88 -100
- package/src/core/tools/index.ts +1 -1
- package/src/core/tools/ls.ts +1 -1
- package/src/core/tools/lsp/client.ts +50 -50
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +1 -1
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/index.ts +2 -4
- package/src/core/tools/lsp/lspmux.ts +1 -1
- package/src/core/tools/lsp/rust-analyzer.ts +2 -2
- package/src/core/tools/lsp/utils.ts +0 -14
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/patch/shared.ts +3 -4
- package/src/core/tools/python.ts +3 -3
- package/src/core/tools/read.ts +29 -68
- package/src/core/tools/render-utils.ts +0 -5
- package/src/core/tools/ssh.ts +3 -3
- package/src/core/tools/task/model-resolver.ts +7 -9
- package/src/core/tools/task/worker.ts +144 -139
- package/src/core/tools/todo-write.ts +1 -1
- package/src/core/tools/truncate.ts +2 -2
- package/src/core/tools/web-fetch.ts +13 -15
- package/src/core/tools/web-scrapers/types.ts +1 -3
- package/src/core/tools/web-scrapers/utils.ts +14 -13
- package/src/core/tools/web-scrapers/youtube.ts +39 -12
- package/src/core/tools/web-search/auth.ts +9 -45
- package/src/core/tools/write.ts +1 -1
- package/src/core/ttsr.ts +1 -1
- package/src/core/utils.ts +1 -187
- package/src/core/voice-controller.ts +1 -1
- package/src/core/voice-supervisor.ts +11 -38
- package/src/core/voice.ts +1 -8
- package/src/discovery/codex.ts +1 -1
- package/src/index.ts +4 -4
- package/src/main.ts +5 -10
- package/src/migrations.ts +1 -1
- package/src/modes/index.ts +7 -40
- package/src/modes/interactive/components/extensions/state-manager.ts +1 -1
- package/src/modes/interactive/components/hook-editor.ts +12 -9
- package/src/modes/interactive/components/login-dialog.ts +24 -11
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line.ts +36 -35
- package/src/modes/interactive/components/todo-display.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +1 -1
- package/src/modes/interactive/controllers/command-controller.ts +50 -84
- package/src/modes/interactive/controllers/extension-ui-controller.ts +76 -76
- package/src/modes/interactive/controllers/input-controller.ts +12 -11
- package/src/modes/interactive/interactive-mode.ts +10 -11
- package/src/modes/interactive/theme/theme.ts +1 -1
- package/src/modes/interactive/types.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +91 -121
- package/src/modes/rpc/rpc-mode.ts +71 -79
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/utils/clipboard.ts +57 -141
- package/src/utils/shell-snapshot.ts +12 -60
- package/src/utils/shell.ts +35 -56
- package/src/utils/tools-manager.ts +42 -71
- package/src/core/logger.ts +0 -111
- package/src/modes/cleanup.ts +0 -23
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* System prompt construction and project context loading
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { existsSync
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { $ } from "bun";
|
|
8
9
|
import chalk from "chalk";
|
|
9
10
|
import { contextFileCapability } from "../capability/context-file";
|
|
10
11
|
import { systemPromptCapability } from "../capability/system-prompt";
|
|
@@ -16,15 +17,6 @@ import type { SkillsSettings } from "./settings-manager";
|
|
|
16
17
|
import { loadSkills, type Skill } from "./skills";
|
|
17
18
|
import type { ToolName } from "./tools/index";
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
* Execute a git command synchronously and return stdout or null on failure.
|
|
21
|
-
*/
|
|
22
|
-
function execGit(args: string[], cwd: string): string | null {
|
|
23
|
-
const result = Bun.spawnSync(["git", ...args], { cwd, stdin: "ignore", stdout: "pipe", stderr: "pipe" });
|
|
24
|
-
if (result.exitCode !== 0) return null;
|
|
25
|
-
return result.stdout.toString().trim() || null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
20
|
interface GitContext {
|
|
29
21
|
isRepo: boolean;
|
|
30
22
|
currentBranch: string;
|
|
@@ -37,31 +29,36 @@ interface GitContext {
|
|
|
37
29
|
* Load git context for the system prompt.
|
|
38
30
|
* Returns structured git data or null if not in a git repo.
|
|
39
31
|
*/
|
|
40
|
-
export function loadGitContext(cwd: string): GitContext | null {
|
|
32
|
+
export async function loadGitContext(cwd: string): Promise<GitContext | null> {
|
|
33
|
+
const git = (...args: string[]) =>
|
|
34
|
+
$`git ${args}`
|
|
35
|
+
.cwd(cwd)
|
|
36
|
+
.quiet()
|
|
37
|
+
.text()
|
|
38
|
+
.catch(() => null)
|
|
39
|
+
.then((text) => text?.trim() ?? null);
|
|
40
|
+
|
|
41
41
|
// Check if inside a git repo
|
|
42
|
-
const isGitRepo =
|
|
42
|
+
const isGitRepo = await git("rev-parse", "--is-inside-work-tree");
|
|
43
43
|
if (isGitRepo !== "true") return null;
|
|
44
44
|
|
|
45
45
|
// Get current branch
|
|
46
|
-
const currentBranch =
|
|
46
|
+
const currentBranch = await git("rev-parse", "--abbrev-ref", "HEAD");
|
|
47
47
|
if (!currentBranch) return null;
|
|
48
48
|
|
|
49
49
|
// Detect main branch (check for 'main' first, then 'master')
|
|
50
50
|
let mainBranch = "main";
|
|
51
|
-
const mainExists =
|
|
51
|
+
const mainExists = await git("rev-parse", "--verify", "main");
|
|
52
52
|
if (mainExists === null) {
|
|
53
|
-
const masterExists =
|
|
53
|
+
const masterExists = await git("rev-parse", "--verify", "master");
|
|
54
54
|
if (masterExists !== null) mainBranch = "master";
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Get git status (porcelain format for parsing)
|
|
58
|
-
const
|
|
59
|
-
const status = gitStatus?.trim() || "(clean)";
|
|
58
|
+
const status = (await git("status", "--porcelain")) || "(clean)";
|
|
60
59
|
|
|
61
60
|
// Get recent commits
|
|
62
|
-
const
|
|
63
|
-
const commits = recentCommits?.trim() || "(no commits)";
|
|
64
|
-
|
|
61
|
+
const commits = (await git("log", "--oneline", "-5")) || "(no commits)";
|
|
65
62
|
return {
|
|
66
63
|
isRepo: true,
|
|
67
64
|
currentBranch,
|
|
@@ -94,18 +91,6 @@ const toolDescriptions: Record<ToolName, string> = {
|
|
|
94
91
|
report_finding: "Report a finding during code review",
|
|
95
92
|
};
|
|
96
93
|
|
|
97
|
-
function execCommand(args: string[]): string | null {
|
|
98
|
-
const result = Bun.spawnSync(args, { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
|
|
99
|
-
if (result.exitCode !== 0) return null;
|
|
100
|
-
const output = result.stdout.toString().trim();
|
|
101
|
-
return output.length > 0 ? output : null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function execIfExists(command: string, args: string[]): string | null {
|
|
105
|
-
if (!Bun.which(command)) return null;
|
|
106
|
-
return execCommand([command, ...args]);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
94
|
function firstNonEmpty(values: Array<string | undefined | null>): string | null {
|
|
110
95
|
for (const value of values) {
|
|
111
96
|
const trimmed = value?.trim();
|
|
@@ -209,18 +194,27 @@ function getOsName(): string {
|
|
|
209
194
|
}
|
|
210
195
|
}
|
|
211
196
|
|
|
212
|
-
function getKernelVersion(): string {
|
|
197
|
+
async function getKernelVersion(): Promise<string> {
|
|
213
198
|
if (process.platform === "win32") {
|
|
214
|
-
return
|
|
199
|
+
return await $`ver`
|
|
200
|
+
.quiet()
|
|
201
|
+
.text()
|
|
202
|
+
.catch(() => "unknown");
|
|
203
|
+
} else {
|
|
204
|
+
return await $`uname -sr`
|
|
205
|
+
.quiet()
|
|
206
|
+
.text()
|
|
207
|
+
.catch(() => "unknown");
|
|
215
208
|
}
|
|
216
|
-
|
|
217
|
-
return execCommand(["uname", "-sr"]) ?? "unknown";
|
|
218
209
|
}
|
|
219
210
|
|
|
220
|
-
function getOsDistro(): string | null {
|
|
211
|
+
async function getOsDistro(): Promise<string | null> {
|
|
221
212
|
switch (process.platform) {
|
|
222
213
|
case "win32": {
|
|
223
|
-
const output =
|
|
214
|
+
const output = await $`wmic os get Caption,Version /value`
|
|
215
|
+
.quiet()
|
|
216
|
+
.text()
|
|
217
|
+
.catch(() => null);
|
|
224
218
|
if (!output) return null;
|
|
225
219
|
const parsed = parseKeyValueOutput(output);
|
|
226
220
|
const caption = parsed.Caption;
|
|
@@ -229,15 +223,32 @@ function getOsDistro(): string | null {
|
|
|
229
223
|
return caption ?? version ?? null;
|
|
230
224
|
}
|
|
231
225
|
case "darwin": {
|
|
232
|
-
const name = firstNonEmptyLine(
|
|
233
|
-
|
|
226
|
+
const name = firstNonEmptyLine(
|
|
227
|
+
await $`sw_vers -productName`
|
|
228
|
+
.quiet()
|
|
229
|
+
.text()
|
|
230
|
+
.catch(() => null),
|
|
231
|
+
);
|
|
232
|
+
const version = firstNonEmptyLine(
|
|
233
|
+
await $`sw_vers -productVersion`
|
|
234
|
+
.quiet()
|
|
235
|
+
.text()
|
|
236
|
+
.catch(() => null),
|
|
237
|
+
);
|
|
234
238
|
if (name && version) return `${name} ${version}`.trim();
|
|
235
239
|
return name ?? version ?? null;
|
|
236
240
|
}
|
|
237
241
|
case "linux": {
|
|
238
|
-
const lsb = firstNonEmptyLine(
|
|
242
|
+
const lsb = firstNonEmptyLine(
|
|
243
|
+
await $`lsb_release -ds`
|
|
244
|
+
.quiet()
|
|
245
|
+
.text()
|
|
246
|
+
.catch(() => null),
|
|
247
|
+
);
|
|
239
248
|
if (lsb) return stripQuotes(lsb);
|
|
240
|
-
const osRelease =
|
|
249
|
+
const osRelease = await Bun.file("/etc/os-release")
|
|
250
|
+
.text()
|
|
251
|
+
.catch(() => null);
|
|
241
252
|
if (!osRelease) return null;
|
|
242
253
|
const parsed = parseKeyValueOutput(osRelease);
|
|
243
254
|
const pretty = parsed.PRETTY_NAME ?? parsed.NAME;
|
|
@@ -255,17 +266,28 @@ function getCpuArch(): string {
|
|
|
255
266
|
return process.arch || "unknown";
|
|
256
267
|
}
|
|
257
268
|
|
|
258
|
-
function getCpuModel(): string | null {
|
|
269
|
+
async function getCpuModel(): Promise<string | null> {
|
|
259
270
|
switch (process.platform) {
|
|
260
271
|
case "win32": {
|
|
261
|
-
const output =
|
|
272
|
+
const output = await $`wmic cpu get Name`
|
|
273
|
+
.quiet()
|
|
274
|
+
.text()
|
|
275
|
+
.catch(() => null);
|
|
262
276
|
return output ? parseWmicTable(output, "Name") : null;
|
|
263
277
|
}
|
|
264
278
|
case "darwin": {
|
|
265
|
-
return firstNonEmptyLine(
|
|
279
|
+
return firstNonEmptyLine(
|
|
280
|
+
await $`sysctl -n machdep.cpu.brand_string`
|
|
281
|
+
.quiet()
|
|
282
|
+
.text()
|
|
283
|
+
.catch(() => null),
|
|
284
|
+
);
|
|
266
285
|
}
|
|
267
286
|
case "linux": {
|
|
268
|
-
const lscpu =
|
|
287
|
+
const lscpu = await $`lscpu`
|
|
288
|
+
.quiet()
|
|
289
|
+
.text()
|
|
290
|
+
.catch(() => null);
|
|
269
291
|
if (lscpu) {
|
|
270
292
|
const match = lscpu
|
|
271
293
|
.split("\n")
|
|
@@ -273,7 +295,9 @@ function getCpuModel(): string | null {
|
|
|
273
295
|
.find((line) => line.toLowerCase().startsWith("model name:"));
|
|
274
296
|
if (match) return match.split(":").slice(1).join(":").trim();
|
|
275
297
|
}
|
|
276
|
-
const cpuInfo =
|
|
298
|
+
const cpuInfo = await Bun.file("/proc/cpuinfo")
|
|
299
|
+
.text()
|
|
300
|
+
.catch(() => null);
|
|
277
301
|
if (!cpuInfo) return null;
|
|
278
302
|
for (const line of cpuInfo.split("\n")) {
|
|
279
303
|
const [key, ...rest] = line.split(":");
|
|
@@ -290,14 +314,20 @@ function getCpuModel(): string | null {
|
|
|
290
314
|
}
|
|
291
315
|
}
|
|
292
316
|
|
|
293
|
-
function getGpuModel(): string | null {
|
|
317
|
+
async function getGpuModel(): Promise<string | null> {
|
|
294
318
|
switch (process.platform) {
|
|
295
319
|
case "win32": {
|
|
296
|
-
const output =
|
|
320
|
+
const output = await $`wmic path win32_VideoController get name`
|
|
321
|
+
.quiet()
|
|
322
|
+
.text()
|
|
323
|
+
.catch(() => null);
|
|
297
324
|
return output ? parseWmicTable(output, "Name") : null;
|
|
298
325
|
}
|
|
299
326
|
case "linux": {
|
|
300
|
-
const output =
|
|
327
|
+
const output = await $`lspci`
|
|
328
|
+
.quiet()
|
|
329
|
+
.text()
|
|
330
|
+
.catch(() => null);
|
|
301
331
|
if (!output) return null;
|
|
302
332
|
const gpus: Array<{ name: string; priority: number }> = [];
|
|
303
333
|
for (const line of output.split("\n")) {
|
|
@@ -426,39 +456,42 @@ function getSystemInfoCachePath(): string {
|
|
|
426
456
|
return join(homedir(), ".omp", "system_info.json");
|
|
427
457
|
}
|
|
428
458
|
|
|
429
|
-
function loadSystemInfoCache(): SystemInfoCache | null {
|
|
459
|
+
async function loadSystemInfoCache(): Promise<SystemInfoCache | null> {
|
|
430
460
|
try {
|
|
431
461
|
const cachePath = getSystemInfoCachePath();
|
|
432
462
|
if (!existsSync(cachePath)) return null;
|
|
433
|
-
const content =
|
|
434
|
-
return
|
|
463
|
+
const content = await Bun.file(cachePath).json();
|
|
464
|
+
return content as SystemInfoCache;
|
|
435
465
|
} catch {
|
|
436
466
|
return null;
|
|
437
467
|
}
|
|
438
468
|
}
|
|
439
469
|
|
|
440
|
-
function saveSystemInfoCache(info: SystemInfoCache): void {
|
|
470
|
+
async function saveSystemInfoCache(info: SystemInfoCache): Promise<void> {
|
|
441
471
|
try {
|
|
442
472
|
const cachePath = getSystemInfoCachePath();
|
|
443
|
-
|
|
444
|
-
if (!existsSync(dir)) {
|
|
445
|
-
mkdirSync(dir, { recursive: true });
|
|
446
|
-
}
|
|
447
|
-
writeFileSync(cachePath, JSON.stringify(info, null, "\t"), "utf-8");
|
|
473
|
+
await Bun.write(cachePath, JSON.stringify(info, null, "\t"));
|
|
448
474
|
} catch {
|
|
449
475
|
// Silently ignore cache write failures
|
|
450
476
|
}
|
|
451
477
|
}
|
|
452
478
|
|
|
453
|
-
function collectSystemInfo(): SystemInfoCache {
|
|
479
|
+
async function collectSystemInfo(): Promise<SystemInfoCache> {
|
|
480
|
+
const [distro, cpu, gpu, disk, kernel] = await Promise.all([
|
|
481
|
+
getOsDistro(),
|
|
482
|
+
getCpuModel(),
|
|
483
|
+
getGpuModel(),
|
|
484
|
+
getDiskInfo(),
|
|
485
|
+
getKernelVersion(),
|
|
486
|
+
]);
|
|
454
487
|
return {
|
|
455
488
|
os: getOsName(),
|
|
456
|
-
distro:
|
|
457
|
-
kernel:
|
|
489
|
+
distro: distro ?? "unknown",
|
|
490
|
+
kernel: kernel ?? "unknown",
|
|
458
491
|
arch: getCpuArch(),
|
|
459
|
-
cpu:
|
|
460
|
-
gpu:
|
|
461
|
-
disk:
|
|
492
|
+
cpu: cpu ?? "unknown",
|
|
493
|
+
gpu: gpu ?? "unknown",
|
|
494
|
+
disk: disk ?? "unknown",
|
|
462
495
|
};
|
|
463
496
|
}
|
|
464
497
|
|
|
@@ -470,10 +503,13 @@ function formatBytes(bytes: number): string {
|
|
|
470
503
|
return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(1)}TB`;
|
|
471
504
|
}
|
|
472
505
|
|
|
473
|
-
function getDiskInfo(): string | null {
|
|
506
|
+
async function getDiskInfo(): Promise<string | null> {
|
|
474
507
|
switch (process.platform) {
|
|
475
508
|
case "win32": {
|
|
476
|
-
const output =
|
|
509
|
+
const output = await $`wmic logicaldisk get Caption,Size,FreeSpace /format:csv`
|
|
510
|
+
.quiet()
|
|
511
|
+
.text()
|
|
512
|
+
.catch(() => null);
|
|
477
513
|
if (!output) return null;
|
|
478
514
|
const lines = output.split("\n").filter((l) => l.trim() && !l.startsWith("Node"));
|
|
479
515
|
const disks: string[] = [];
|
|
@@ -492,7 +528,10 @@ function getDiskInfo(): string | null {
|
|
|
492
528
|
}
|
|
493
529
|
case "linux":
|
|
494
530
|
case "darwin": {
|
|
495
|
-
const output =
|
|
531
|
+
const output = await $`df -h /`
|
|
532
|
+
.quiet()
|
|
533
|
+
.text()
|
|
534
|
+
.catch(() => null);
|
|
496
535
|
if (!output) return null;
|
|
497
536
|
const lines = output.split("\n");
|
|
498
537
|
if (lines.length < 2) return null;
|
|
@@ -508,12 +547,12 @@ function getDiskInfo(): string | null {
|
|
|
508
547
|
}
|
|
509
548
|
}
|
|
510
549
|
|
|
511
|
-
function getEnvironmentInfo(): Array<{ label: string; value: string }
|
|
550
|
+
async function getEnvironmentInfo(): Promise<Array<{ label: string; value: string }>> {
|
|
512
551
|
// Load cached system info or collect fresh
|
|
513
|
-
let sysInfo = loadSystemInfoCache();
|
|
552
|
+
let sysInfo = await loadSystemInfoCache();
|
|
514
553
|
if (!sysInfo) {
|
|
515
|
-
sysInfo = collectSystemInfo();
|
|
516
|
-
saveSystemInfoCache(sysInfo);
|
|
554
|
+
sysInfo = await collectSystemInfo();
|
|
555
|
+
await saveSystemInfoCache(sysInfo);
|
|
517
556
|
}
|
|
518
557
|
|
|
519
558
|
return [
|
|
@@ -532,14 +571,15 @@ function getEnvironmentInfo(): Array<{ label: string; value: string }> {
|
|
|
532
571
|
}
|
|
533
572
|
|
|
534
573
|
/** Resolve input as file path or literal string */
|
|
535
|
-
export function resolvePromptInput(input: string | undefined, description: string): string | undefined {
|
|
574
|
+
export async function resolvePromptInput(input: string | undefined, description: string): Promise<string | undefined> {
|
|
536
575
|
if (!input) {
|
|
537
576
|
return undefined;
|
|
538
577
|
}
|
|
539
578
|
|
|
540
|
-
|
|
579
|
+
const file = Bun.file(input);
|
|
580
|
+
if (await file.exists()) {
|
|
541
581
|
try {
|
|
542
|
-
return
|
|
582
|
+
return await file.text();
|
|
543
583
|
} catch (error) {
|
|
544
584
|
console.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));
|
|
545
585
|
return input;
|
|
@@ -649,8 +689,8 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
649
689
|
rules,
|
|
650
690
|
} = options;
|
|
651
691
|
const resolvedCwd = cwd ?? process.cwd();
|
|
652
|
-
const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
|
|
653
|
-
const resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, "append system prompt");
|
|
692
|
+
const resolvedCustomPrompt = await resolvePromptInput(customPrompt, "system prompt");
|
|
693
|
+
const resolvedAppendPrompt = await resolvePromptInput(appendSystemPrompt, "append system prompt");
|
|
654
694
|
|
|
655
695
|
// Load SYSTEM.md customization (prepended to prompt)
|
|
656
696
|
const systemPromptCustomization = await loadSystemPromptFiles({ cwd: resolvedCwd });
|
|
@@ -697,7 +737,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
697
737
|
(skillsSettings?.enabled !== false ? (await loadSkills({ ...skillsSettings, cwd: resolvedCwd })).skills : []);
|
|
698
738
|
|
|
699
739
|
// Get git context
|
|
700
|
-
const git = loadGitContext(resolvedCwd);
|
|
740
|
+
const git = await loadGitContext(resolvedCwd);
|
|
701
741
|
|
|
702
742
|
// Filter skills to only include those with read tool
|
|
703
743
|
const hasRead = tools?.has("read");
|
|
@@ -722,7 +762,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
722
762
|
return renderPromptTemplate(systemPromptTemplate, {
|
|
723
763
|
tools: toolNamesArray,
|
|
724
764
|
toolDescriptions: toolDescriptionsArray,
|
|
725
|
-
environment: getEnvironmentInfo(),
|
|
765
|
+
environment: await getEnvironmentInfo(),
|
|
726
766
|
systemPromptCustomization: systemPromptCustomization ?? "",
|
|
727
767
|
contextFiles,
|
|
728
768
|
agentsMdSearch,
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
8
|
-
import { logger } from "./logger";
|
|
9
9
|
import type { ModelRegistry } from "./model-registry";
|
|
10
10
|
import { parseModelString, SMOL_MODEL_PRIORITY } from "./model-resolver";
|
|
11
11
|
import { renderPromptTemplate } from "./prompt-templates";
|
package/src/core/tools/ask.ts
CHANGED
|
@@ -24,7 +24,7 @@ import askDescription from "../../prompts/tools/ask.md" with { type: "text" };
|
|
|
24
24
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
25
25
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
26
26
|
import type { ToolSession } from "./index";
|
|
27
|
-
import {
|
|
27
|
+
import { ToolUIKit } from "./render-utils";
|
|
28
28
|
|
|
29
29
|
// =============================================================================
|
|
30
30
|
// Types
|
|
@@ -324,7 +324,7 @@ interface AskRenderArgs {
|
|
|
324
324
|
|
|
325
325
|
export const askToolRenderer = {
|
|
326
326
|
renderCall(args: AskRenderArgs, uiTheme: Theme): Component {
|
|
327
|
-
const ui =
|
|
327
|
+
const ui = new ToolUIKit(uiTheme);
|
|
328
328
|
const label = ui.title("Ask");
|
|
329
329
|
|
|
330
330
|
// Multi-part questions
|
package/src/core/tools/bash.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { renderPromptTemplate } from "../prompt-templates";
|
|
|
12
12
|
import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor";
|
|
13
13
|
import type { ToolSession } from "./index";
|
|
14
14
|
import { resolveToCwd } from "./path-utils";
|
|
15
|
-
import {
|
|
15
|
+
import { ToolUIKit } from "./render-utils";
|
|
16
16
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
|
|
17
17
|
|
|
18
18
|
export const BASH_DEFAULT_PREVIEW_LINES = 10;
|
|
@@ -196,7 +196,7 @@ export const BASH_PREVIEW_LINES = 10;
|
|
|
196
196
|
|
|
197
197
|
export const bashToolRenderer = {
|
|
198
198
|
renderCall(args: BashRenderArgs, uiTheme: Theme): Component {
|
|
199
|
-
const ui =
|
|
199
|
+
const ui = new ToolUIKit(uiTheme);
|
|
200
200
|
const command = args.command || uiTheme.format.ellipsis;
|
|
201
201
|
const prompt = uiTheme.fg("accent", "$");
|
|
202
202
|
const cwd = process.cwd();
|
|
@@ -231,7 +231,7 @@ export const bashToolRenderer = {
|
|
|
231
231
|
options: RenderResultOptions & { renderContext?: BashRenderContext },
|
|
232
232
|
uiTheme: Theme,
|
|
233
233
|
): Component {
|
|
234
|
-
const ui =
|
|
234
|
+
const ui = new ToolUIKit(uiTheme);
|
|
235
235
|
const { renderContext } = options;
|
|
236
236
|
const details = result.details;
|
|
237
237
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { Type } from "@sinclair/typebox";
|
|
5
6
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
6
7
|
import calculatorDescription from "../../prompts/tools/calculator.md" with { type: "text" };
|
|
7
8
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
8
9
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
9
|
-
import { untilAborted } from "../utils";
|
|
10
10
|
import type { ToolSession } from "./index";
|
|
11
11
|
import {
|
|
12
12
|
formatCount,
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
9
10
|
import type { TSchema } from "@sinclair/typebox";
|
|
10
11
|
import type { CustomTool, CustomToolResult } from "../../custom-tools/types";
|
|
11
|
-
import { logger } from "../../logger";
|
|
12
12
|
import { callMCP } from "../../mcp/json-rpc";
|
|
13
13
|
import type {
|
|
14
14
|
ExaRenderDetails,
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
9
10
|
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
10
11
|
import type { RenderResultOptions } from "../../custom-tools/types";
|
|
11
|
-
import { logger } from "../../logger";
|
|
12
12
|
import {
|
|
13
13
|
formatCount,
|
|
14
14
|
formatExpandHint,
|
package/src/core/tools/find.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
3
3
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
|
+
import { ptree, untilAborted } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import type { Static } from "@sinclair/typebox";
|
|
7
8
|
import { Type } from "@sinclair/typebox";
|
|
8
9
|
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
@@ -10,10 +11,9 @@ import findDescription from "../../prompts/tools/find.md" with { type: "text" };
|
|
|
10
11
|
import { ensureTool } from "../../utils/tools-manager";
|
|
11
12
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
12
13
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
13
|
-
import { ScopeSignal, untilAborted } from "../utils";
|
|
14
14
|
import type { ToolSession } from "./index";
|
|
15
15
|
import { resolveToCwd } from "./path-utils";
|
|
16
|
-
import {
|
|
16
|
+
import { PREVIEW_LIMITS, ToolUIKit } from "./render-utils";
|
|
17
17
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate";
|
|
18
18
|
|
|
19
19
|
const findSchema = Type.Object({
|
|
@@ -63,51 +63,35 @@ export interface FindToolOptions {
|
|
|
63
63
|
operations?: FindOperations;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const child = Bun.spawn([command, ...args], {
|
|
72
|
-
stdin: "ignore",
|
|
73
|
-
stdout: "pipe",
|
|
74
|
-
stderr: "pipe",
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
using scope = new ScopeSignal(signal ? { signal } : undefined);
|
|
78
|
-
scope.catch(() => {
|
|
79
|
-
child.kill();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const stdoutReader = (child.stdout as ReadableStream<Uint8Array>).getReader();
|
|
83
|
-
const stderrReader = (child.stderr as ReadableStream<Uint8Array>).getReader();
|
|
84
|
-
const stdoutDecoder = new TextDecoder();
|
|
85
|
-
const stderrDecoder = new TextDecoder();
|
|
86
|
-
let stdout = "";
|
|
87
|
-
let stderr = "";
|
|
88
|
-
|
|
89
|
-
await Promise.all([
|
|
90
|
-
(async () => {
|
|
91
|
-
while (true) {
|
|
92
|
-
const { done, value } = await stdoutReader.read();
|
|
93
|
-
if (done) break;
|
|
94
|
-
stdout += stdoutDecoder.decode(value, { stream: true });
|
|
95
|
-
}
|
|
96
|
-
stdout += stdoutDecoder.decode();
|
|
97
|
-
})(),
|
|
98
|
-
(async () => {
|
|
99
|
-
while (true) {
|
|
100
|
-
const { done, value } = await stderrReader.read();
|
|
101
|
-
if (done) break;
|
|
102
|
-
stderr += stderrDecoder.decode(value, { stream: true });
|
|
103
|
-
}
|
|
104
|
-
stderr += stderrDecoder.decode();
|
|
105
|
-
})(),
|
|
106
|
-
]);
|
|
66
|
+
export interface FdResult {
|
|
67
|
+
stdout: string;
|
|
68
|
+
stderr: string;
|
|
69
|
+
exitCode: number | null;
|
|
70
|
+
}
|
|
107
71
|
|
|
108
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Run fd command and capture output.
|
|
74
|
+
*
|
|
75
|
+
* @throws Error with message "Operation aborted" if signal is aborted
|
|
76
|
+
*/
|
|
77
|
+
export async function runFd(fdPath: string, args: string[], signal?: AbortSignal): Promise<FdResult> {
|
|
78
|
+
const child = ptree.cspawn([fdPath, ...args], { signal });
|
|
79
|
+
|
|
80
|
+
let stdout: string;
|
|
81
|
+
try {
|
|
82
|
+
stdout = await child.nothrow().text();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (err instanceof ptree.Exception && err.aborted) {
|
|
85
|
+
throw new Error("Operation aborted");
|
|
86
|
+
}
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
109
89
|
|
|
110
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
stdout,
|
|
92
|
+
stderr: child.peekStderr(),
|
|
93
|
+
exitCode: child.exitCode,
|
|
94
|
+
};
|
|
111
95
|
}
|
|
112
96
|
|
|
113
97
|
export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
@@ -254,7 +238,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
254
238
|
"--no-ignore",
|
|
255
239
|
"--type",
|
|
256
240
|
"f",
|
|
257
|
-
"--
|
|
241
|
+
"--glob",
|
|
258
242
|
".gitignore",
|
|
259
243
|
"--exclude",
|
|
260
244
|
".git",
|
|
@@ -263,24 +247,17 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
263
247
|
"--absolute-path",
|
|
264
248
|
searchPath,
|
|
265
249
|
];
|
|
266
|
-
const { stdout: gitignoreStdout
|
|
267
|
-
fdPath,
|
|
268
|
-
gitignoreArgs,
|
|
269
|
-
signal,
|
|
270
|
-
);
|
|
271
|
-
if (gitignoreAborted) {
|
|
272
|
-
throw new Error("Operation aborted");
|
|
273
|
-
}
|
|
250
|
+
const { stdout: gitignoreStdout } = await runFd(fdPath, gitignoreArgs, signal);
|
|
274
251
|
for (const rawLine of gitignoreStdout.split("\n")) {
|
|
275
252
|
const file = rawLine.trim();
|
|
276
253
|
if (!file) continue;
|
|
277
254
|
gitignoreFiles.add(file);
|
|
278
255
|
}
|
|
279
256
|
} catch (err) {
|
|
280
|
-
if (
|
|
281
|
-
throw err
|
|
257
|
+
if (err instanceof Error && err.message === "Operation aborted") {
|
|
258
|
+
throw err;
|
|
282
259
|
}
|
|
283
|
-
// Ignore lookup errors
|
|
260
|
+
// Ignore other lookup errors
|
|
284
261
|
}
|
|
285
262
|
|
|
286
263
|
for (const gitignorePath of gitignoreFiles) {
|
|
@@ -291,20 +268,11 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
291
268
|
args.push(effectivePattern, searchPath);
|
|
292
269
|
|
|
293
270
|
// Run fd
|
|
294
|
-
const { stdout, stderr, exitCode
|
|
295
|
-
|
|
296
|
-
if (aborted) {
|
|
297
|
-
throw new Error("Operation aborted");
|
|
298
|
-
}
|
|
299
|
-
|
|
271
|
+
const { stdout, stderr, exitCode } = await runFd(fdPath, args, signal);
|
|
300
272
|
const output = stdout.trim();
|
|
301
273
|
|
|
302
|
-
if (exitCode !== 0) {
|
|
303
|
-
|
|
304
|
-
// fd returns non-zero for some errors but may still have partial output
|
|
305
|
-
if (!output) {
|
|
306
|
-
throw new Error(errorMsg);
|
|
307
|
-
}
|
|
274
|
+
if (exitCode !== 0 && !output) {
|
|
275
|
+
throw new Error(stderr.trim() || `fd exited with code ${exitCode ?? -1}`);
|
|
308
276
|
}
|
|
309
277
|
|
|
310
278
|
if (!output) {
|
|
@@ -421,7 +389,7 @@ const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
|
421
389
|
export const findToolRenderer = {
|
|
422
390
|
inline: true,
|
|
423
391
|
renderCall(args: FindRenderArgs, uiTheme: Theme): Component {
|
|
424
|
-
const ui =
|
|
392
|
+
const ui = new ToolUIKit(uiTheme);
|
|
425
393
|
const label = ui.title("Find");
|
|
426
394
|
let text = `${uiTheme.format.bullet} ${label} ${uiTheme.fg("accent", args.pattern || "*")}`;
|
|
427
395
|
|
|
@@ -442,7 +410,7 @@ export const findToolRenderer = {
|
|
|
442
410
|
{ expanded }: RenderResultOptions,
|
|
443
411
|
uiTheme: Theme,
|
|
444
412
|
): Component {
|
|
445
|
-
const ui =
|
|
413
|
+
const ui = new ToolUIKit(uiTheme);
|
|
446
414
|
const details = result.details;
|
|
447
415
|
|
|
448
416
|
if (result.isError || details?.error) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
6
|
import { nanoid } from "nanoid";
|
|
6
7
|
import geminiImageDescription from "../../prompts/tools/gemini-image.md" with { type: "text" };
|
|
7
8
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime";
|
|
8
9
|
import type { CustomTool } from "../custom-tools/types";
|
|
9
10
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
10
|
-
import { untilAborted } from "../utils";
|
|
11
11
|
import { resolveReadPath } from "./path-utils";
|
|
12
12
|
import { getEnv } from "./web-search/auth";
|
|
13
13
|
|