@oh-my-pi/pi-coding-agent 9.6.4 → 9.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 +29 -0
- package/package.json +8 -8
- package/src/exec/bash-executor.ts +95 -97
- package/src/ipy/executor.ts +2 -1
- package/src/ipy/gateway-coordinator.ts +3 -3
- package/src/ipy/kernel.ts +0 -1
- package/src/lsp/client.ts +0 -1
- package/src/main.ts +6 -4
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +5 -4
- package/src/prompts/system/system-prompt.md +26 -20
- package/src/session/agent-session.ts +1 -0
- package/src/system-prompt.ts +24 -244
- package/src/tools/read.ts +0 -1
- package/src/utils/image-convert.ts +3 -3
- package/src/utils/image-resize.ts +10 -7
- package/src/web/scrapers/utils.ts +0 -1
- package/src/exec/shell-session.ts +0 -609
- package/src/utils/clipboard.ts +0 -248
package/src/system-prompt.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
-
import { find as wasmFind } from "@oh-my-pi/pi-natives";
|
|
7
|
+
import { getSystemInfo as getNativeSystemInfo, type SystemInfo, find as wasmFind } from "@oh-my-pi/pi-natives";
|
|
8
8
|
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import { $ } from "bun";
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import { contextFileCapability } from "./capability/context-file";
|
|
12
12
|
import { systemPromptCapability } from "./capability/system-prompt";
|
|
13
13
|
import { renderPromptTemplate } from "./config/prompt-templates";
|
|
14
|
-
import {
|
|
14
|
+
import type { SkillsSettings } from "./config/settings";
|
|
15
15
|
import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
|
|
16
16
|
import { loadSkills, type Skill } from "./extensibility/skills";
|
|
17
17
|
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
|
|
@@ -87,7 +87,7 @@ export async function loadGitContext(cwd: string): Promise<GitContext | null> {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
function firstNonEmpty(values:
|
|
90
|
+
function firstNonEmpty(...values: (string | undefined | null)[]): string | null {
|
|
91
91
|
for (const value of values) {
|
|
92
92
|
const trimmed = value?.trim();
|
|
93
93
|
if (trimmed) return trimmed;
|
|
@@ -95,15 +95,6 @@ function firstNonEmpty(values: Array<string | undefined | null>): string | null
|
|
|
95
95
|
return null;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function firstNonEmptyLine(value: string | null): string | null {
|
|
99
|
-
if (!value) return null;
|
|
100
|
-
const line = value
|
|
101
|
-
.split("\n")
|
|
102
|
-
.map(entry => entry.trim())
|
|
103
|
-
.filter(Boolean)[0];
|
|
104
|
-
return line ?? null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
98
|
function parseWmicTable(output: string, header: string): string | null {
|
|
108
99
|
const lines = output
|
|
109
100
|
.split("\n")
|
|
@@ -113,23 +104,6 @@ function parseWmicTable(output: string, header: string): string | null {
|
|
|
113
104
|
return filtered[0] ?? null;
|
|
114
105
|
}
|
|
115
106
|
|
|
116
|
-
function parseKeyValueOutput(output: string): Record<string, string> {
|
|
117
|
-
const result: Record<string, string> = {};
|
|
118
|
-
for (const line of output.split("\n")) {
|
|
119
|
-
const trimmed = line.trim();
|
|
120
|
-
if (!trimmed) continue;
|
|
121
|
-
const [key, ...rest] = trimmed.split("=");
|
|
122
|
-
if (!key || rest.length === 0) continue;
|
|
123
|
-
const value = rest.join("=").trim();
|
|
124
|
-
if (value) result[key.trim()] = value;
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function stripQuotes(value: string): string {
|
|
130
|
-
return value.replace(/^"|"$/g, "");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
107
|
const AGENTS_MD_PATTERN = "**/AGENTS.md";
|
|
134
108
|
const AGENTS_MD_LIMIT = 200;
|
|
135
109
|
const PROJECT_TREE_LIMIT = 2000;
|
|
@@ -382,147 +356,6 @@ async function buildProjectTreeSnapshot(root: string): Promise<string> {
|
|
|
382
356
|
return renderProjectTree(scan, root);
|
|
383
357
|
}
|
|
384
358
|
|
|
385
|
-
function getOsName(): string {
|
|
386
|
-
switch (process.platform) {
|
|
387
|
-
case "win32":
|
|
388
|
-
return "Windows";
|
|
389
|
-
case "darwin":
|
|
390
|
-
return "macOS";
|
|
391
|
-
case "linux":
|
|
392
|
-
return "Linux";
|
|
393
|
-
case "freebsd":
|
|
394
|
-
return "FreeBSD";
|
|
395
|
-
case "openbsd":
|
|
396
|
-
return "OpenBSD";
|
|
397
|
-
case "netbsd":
|
|
398
|
-
return "NetBSD";
|
|
399
|
-
case "aix":
|
|
400
|
-
return "AIX";
|
|
401
|
-
default:
|
|
402
|
-
return process.platform || "unknown";
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
async function getKernelVersion(): Promise<string> {
|
|
407
|
-
if (process.platform === "win32") {
|
|
408
|
-
return await $`ver`
|
|
409
|
-
.quiet()
|
|
410
|
-
.text()
|
|
411
|
-
.catch(() => "unknown");
|
|
412
|
-
} else {
|
|
413
|
-
return await $`uname -sr`
|
|
414
|
-
.quiet()
|
|
415
|
-
.text()
|
|
416
|
-
.catch(() => "unknown");
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async function getOsDistro(): Promise<string | null> {
|
|
421
|
-
switch (process.platform) {
|
|
422
|
-
case "win32": {
|
|
423
|
-
const output = await $`wmic os get Caption,Version /value`
|
|
424
|
-
.quiet()
|
|
425
|
-
.text()
|
|
426
|
-
.catch(() => null);
|
|
427
|
-
if (!output) return null;
|
|
428
|
-
const parsed = parseKeyValueOutput(output);
|
|
429
|
-
const caption = parsed.Caption;
|
|
430
|
-
const version = parsed.Version;
|
|
431
|
-
if (caption && version) return `${caption} ${version}`.trim();
|
|
432
|
-
return caption ?? version ?? null;
|
|
433
|
-
}
|
|
434
|
-
case "darwin": {
|
|
435
|
-
const name = firstNonEmptyLine(
|
|
436
|
-
await $`sw_vers -productName`
|
|
437
|
-
.quiet()
|
|
438
|
-
.text()
|
|
439
|
-
.catch(() => null),
|
|
440
|
-
);
|
|
441
|
-
const version = firstNonEmptyLine(
|
|
442
|
-
await $`sw_vers -productVersion`
|
|
443
|
-
.quiet()
|
|
444
|
-
.text()
|
|
445
|
-
.catch(() => null),
|
|
446
|
-
);
|
|
447
|
-
if (name && version) return `${name} ${version}`.trim();
|
|
448
|
-
return name ?? version ?? null;
|
|
449
|
-
}
|
|
450
|
-
case "linux": {
|
|
451
|
-
const lsb = firstNonEmptyLine(
|
|
452
|
-
await $`lsb_release -ds`
|
|
453
|
-
.quiet()
|
|
454
|
-
.text()
|
|
455
|
-
.catch(() => null),
|
|
456
|
-
);
|
|
457
|
-
if (lsb) return stripQuotes(lsb);
|
|
458
|
-
const osRelease = await Bun.file("/etc/os-release")
|
|
459
|
-
.text()
|
|
460
|
-
.catch(() => null);
|
|
461
|
-
if (!osRelease) return null;
|
|
462
|
-
const parsed = parseKeyValueOutput(osRelease);
|
|
463
|
-
const pretty = parsed.PRETTY_NAME ?? parsed.NAME;
|
|
464
|
-
const version = parsed.VERSION ?? parsed.VERSION_ID;
|
|
465
|
-
if (pretty) return stripQuotes(pretty);
|
|
466
|
-
if (parsed.NAME && version) return `${stripQuotes(parsed.NAME)} ${stripQuotes(version)}`.trim();
|
|
467
|
-
return parsed.NAME ? stripQuotes(parsed.NAME) : null;
|
|
468
|
-
}
|
|
469
|
-
default:
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function getCpuArch(): string {
|
|
475
|
-
return process.arch || "unknown";
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
async function getCpuModel(): Promise<string | null> {
|
|
479
|
-
switch (process.platform) {
|
|
480
|
-
case "win32": {
|
|
481
|
-
const output = await $`wmic cpu get Name`
|
|
482
|
-
.quiet()
|
|
483
|
-
.text()
|
|
484
|
-
.catch(() => null);
|
|
485
|
-
return output ? parseWmicTable(output, "Name") : null;
|
|
486
|
-
}
|
|
487
|
-
case "darwin": {
|
|
488
|
-
return firstNonEmptyLine(
|
|
489
|
-
await $`sysctl -n machdep.cpu.brand_string`
|
|
490
|
-
.quiet()
|
|
491
|
-
.text()
|
|
492
|
-
.catch(() => null),
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
case "linux": {
|
|
496
|
-
const lscpu = await $`lscpu`
|
|
497
|
-
.quiet()
|
|
498
|
-
.text()
|
|
499
|
-
.catch(() => null);
|
|
500
|
-
if (lscpu) {
|
|
501
|
-
const match = lscpu
|
|
502
|
-
.split("\n")
|
|
503
|
-
.map(line => line.trim())
|
|
504
|
-
.find(line => line.toLowerCase().startsWith("model name:"));
|
|
505
|
-
if (match) return match.split(":").slice(1).join(":").trim();
|
|
506
|
-
}
|
|
507
|
-
const cpuInfo = await Bun.file("/proc/cpuinfo")
|
|
508
|
-
.text()
|
|
509
|
-
.catch(() => null);
|
|
510
|
-
if (!cpuInfo) return null;
|
|
511
|
-
for (const line of cpuInfo.split("\n")) {
|
|
512
|
-
const [key, ...rest] = line.split(":");
|
|
513
|
-
if (!key || rest.length === 0) continue;
|
|
514
|
-
const normalized = key.trim().toLowerCase();
|
|
515
|
-
if (normalized === "model name" || normalized === "hardware" || normalized === "processor") {
|
|
516
|
-
return rest.join(":").trim();
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return null;
|
|
520
|
-
}
|
|
521
|
-
default:
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
359
|
async function getGpuModel(): Promise<string | null> {
|
|
527
360
|
switch (process.platform) {
|
|
528
361
|
case "win32": {
|
|
@@ -582,7 +415,7 @@ function getTerminalName(): string {
|
|
|
582
415
|
|
|
583
416
|
if (process.env.WT_SESSION) return "Windows Terminal";
|
|
584
417
|
|
|
585
|
-
const term = firstNonEmpty(
|
|
418
|
+
const term = firstNonEmpty(process.env.TERM, process.env.COLORTERM, process.env.TERMINAL_EMULATOR);
|
|
586
419
|
return term ?? "unknown";
|
|
587
420
|
}
|
|
588
421
|
|
|
@@ -598,12 +431,12 @@ function normalizeDesktopValue(value: string): string {
|
|
|
598
431
|
|
|
599
432
|
function getDesktopEnvironment(): string {
|
|
600
433
|
if (process.env.KDE_FULL_SESSION === "true") return "KDE";
|
|
601
|
-
const raw = firstNonEmpty(
|
|
434
|
+
const raw = firstNonEmpty(
|
|
602
435
|
process.env.XDG_CURRENT_DESKTOP,
|
|
603
436
|
process.env.DESKTOP_SESSION,
|
|
604
437
|
process.env.XDG_SESSION_DESKTOP,
|
|
605
438
|
process.env.GDMSESSION,
|
|
606
|
-
|
|
439
|
+
);
|
|
607
440
|
return raw ? normalizeDesktopValue(raw) : "unknown";
|
|
608
441
|
}
|
|
609
442
|
|
|
@@ -633,10 +466,10 @@ function matchKnownWindowManager(value: string): string | null {
|
|
|
633
466
|
}
|
|
634
467
|
|
|
635
468
|
function getWindowManager(): string {
|
|
636
|
-
const explicit = firstNonEmpty(
|
|
469
|
+
const explicit = firstNonEmpty(process.env.WINDOWMANAGER);
|
|
637
470
|
if (explicit) return explicit;
|
|
638
471
|
|
|
639
|
-
const desktop = firstNonEmpty(
|
|
472
|
+
const desktop = firstNonEmpty(process.env.XDG_CURRENT_DESKTOP, process.env.DESKTOP_SESSION);
|
|
640
473
|
if (desktop) {
|
|
641
474
|
const matched = matchKnownWindowManager(desktop);
|
|
642
475
|
if (matched) return matched;
|
|
@@ -682,76 +515,27 @@ async function saveSystemInfoCache(info: SystemInfoCache): Promise<void> {
|
|
|
682
515
|
}
|
|
683
516
|
|
|
684
517
|
async function collectSystemInfo(): Promise<SystemInfoCache> {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
518
|
+
let nativeInfo: SystemInfo | null = null;
|
|
519
|
+
try {
|
|
520
|
+
nativeInfo = getNativeSystemInfo();
|
|
521
|
+
} catch {
|
|
522
|
+
nativeInfo = null;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const gpu = await getGpuModel();
|
|
526
|
+
const cpus = os.cpus();
|
|
527
|
+
|
|
692
528
|
return {
|
|
693
|
-
os:
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
cpu: cpu ??
|
|
529
|
+
os: `${os.platform()} ${os.release()}`,
|
|
530
|
+
arch: os.arch(),
|
|
531
|
+
distro: nativeInfo?.distro ?? os.type(),
|
|
532
|
+
kernel: nativeInfo?.kernel ?? os.version(),
|
|
533
|
+
cpu: `${cpus.length}x ${nativeInfo?.cpu ?? cpus[0]?.model}`,
|
|
698
534
|
gpu: gpu ?? "unknown",
|
|
699
|
-
disk: disk ?? "unknown",
|
|
535
|
+
disk: nativeInfo?.disk ?? "unknown",
|
|
700
536
|
};
|
|
701
537
|
}
|
|
702
538
|
|
|
703
|
-
function formatBytes(bytes: number): string {
|
|
704
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
705
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
706
|
-
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
707
|
-
if (bytes < 1024 * 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
|
|
708
|
-
return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(1)}TB`;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
async function getDiskInfo(): Promise<string | null> {
|
|
712
|
-
switch (process.platform) {
|
|
713
|
-
case "win32": {
|
|
714
|
-
const output = await $`wmic logicaldisk get Caption,Size,FreeSpace /format:csv`
|
|
715
|
-
.quiet()
|
|
716
|
-
.text()
|
|
717
|
-
.catch(() => null);
|
|
718
|
-
if (!output) return null;
|
|
719
|
-
const lines = output.split("\n").filter(l => l.trim() && !l.startsWith("Node"));
|
|
720
|
-
const disks: string[] = [];
|
|
721
|
-
for (const line of lines) {
|
|
722
|
-
const parts = line.split(",");
|
|
723
|
-
if (parts.length < 4) continue;
|
|
724
|
-
const caption = parts[1]?.trim();
|
|
725
|
-
const freeSpace = Number.parseInt(parts[2]?.trim() ?? "", 10);
|
|
726
|
-
const size = Number.parseInt(parts[3]?.trim() ?? "", 10);
|
|
727
|
-
if (!caption || Number.isNaN(size) || size === 0) continue;
|
|
728
|
-
const used = size - (Number.isNaN(freeSpace) ? 0 : freeSpace);
|
|
729
|
-
const pct = Math.round((used / size) * 100);
|
|
730
|
-
disks.push(`${caption} ${formatBytes(used)}/${formatBytes(size)} (${pct}%)`);
|
|
731
|
-
}
|
|
732
|
-
return disks.length > 0 ? disks.join(", ") : null;
|
|
733
|
-
}
|
|
734
|
-
case "linux":
|
|
735
|
-
case "darwin": {
|
|
736
|
-
const output = await $`df -h /`
|
|
737
|
-
.quiet()
|
|
738
|
-
.text()
|
|
739
|
-
.catch(() => null);
|
|
740
|
-
if (!output) return null;
|
|
741
|
-
const lines = output.split("\n");
|
|
742
|
-
if (lines.length < 2) return null;
|
|
743
|
-
const parts = lines[1].split(/\s+/);
|
|
744
|
-
if (parts.length < 5) return null;
|
|
745
|
-
const size = parts[1];
|
|
746
|
-
const used = parts[2];
|
|
747
|
-
const pct = parts[4];
|
|
748
|
-
return `/ ${used}/${size} (${pct})`;
|
|
749
|
-
}
|
|
750
|
-
default:
|
|
751
|
-
return null;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
539
|
async function getEnvironmentInfo(): Promise<Array<{ label: string; value: string }>> {
|
|
756
540
|
// Load cached system info or collect fresh
|
|
757
541
|
let sysInfo = await loadSystemInfoCache();
|
|
@@ -760,9 +544,6 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
|
|
|
760
544
|
await saveSystemInfoCache(sysInfo);
|
|
761
545
|
}
|
|
762
546
|
|
|
763
|
-
// Get the actual shell used for command execution (not $SHELL)
|
|
764
|
-
const shellConfig = (await Settings.init()).getShellConfig();
|
|
765
|
-
|
|
766
547
|
return [
|
|
767
548
|
{ label: "OS", value: sysInfo.os },
|
|
768
549
|
{ label: "Distro", value: sysInfo.distro },
|
|
@@ -771,7 +552,6 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
|
|
|
771
552
|
{ label: "CPU", value: sysInfo.cpu },
|
|
772
553
|
{ label: "GPU", value: sysInfo.gpu },
|
|
773
554
|
{ label: "Disk", value: sysInfo.disk },
|
|
774
|
-
{ label: "Shell", value: shellConfig.shell },
|
|
775
555
|
{ label: "Terminal", value: getTerminalName() },
|
|
776
556
|
{ label: "DE", value: getDesktopEnvironment() },
|
|
777
557
|
{ label: "WM", value: getWindowManager() },
|
package/src/tools/read.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PhotonImage } from "@oh-my-pi/pi-natives";
|
|
1
|
+
import { ImageFormat, PhotonImage } from "@oh-my-pi/pi-natives";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Convert image to PNG format for terminal display.
|
|
@@ -14,8 +14,8 @@ export async function convertToPng(
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
|
-
|
|
18
|
-
const pngBuffer = await image.
|
|
17
|
+
const image = await PhotonImage.parse(new Uint8Array(Buffer.from(base64Data, "base64")));
|
|
18
|
+
const pngBuffer = await image.encode(ImageFormat.PNG, 100);
|
|
19
19
|
return {
|
|
20
20
|
data: Buffer.from(pngBuffer).toString("base64"),
|
|
21
21
|
mimeType: "image/png",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
|
-
import {
|
|
2
|
+
import { ImageFormat, PhotonImage, SamplingFilter } from "@oh-my-pi/pi-natives";
|
|
3
3
|
|
|
4
4
|
export interface ImageResizeOptions {
|
|
5
5
|
maxWidth?: number; // Default: 2000
|
|
@@ -54,10 +54,10 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
|
|
54
54
|
const inputBuffer = Buffer.from(img.data, "base64");
|
|
55
55
|
|
|
56
56
|
try {
|
|
57
|
-
|
|
57
|
+
const image = await PhotonImage.parse(inputBuffer);
|
|
58
58
|
|
|
59
|
-
const originalWidth = image.
|
|
60
|
-
const originalHeight = image.
|
|
59
|
+
const originalWidth = image.width;
|
|
60
|
+
const originalHeight = image.height;
|
|
61
61
|
const format = img.mimeType?.split("/")[1] ?? "png";
|
|
62
62
|
|
|
63
63
|
// Check if already within all limits (dimensions AND size)
|
|
@@ -91,11 +91,14 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
|
|
91
91
|
async function tryBothFormats(
|
|
92
92
|
width: number,
|
|
93
93
|
height: number,
|
|
94
|
-
|
|
94
|
+
quality: number,
|
|
95
95
|
): Promise<{ buffer: Uint8Array; mimeType: string }> {
|
|
96
|
-
|
|
96
|
+
const resized = await image.resize(width, height, SamplingFilter.Lanczos3);
|
|
97
97
|
|
|
98
|
-
const [pngBuffer, jpegBuffer] = await Promise.all([
|
|
98
|
+
const [pngBuffer, jpegBuffer] = await Promise.all([
|
|
99
|
+
resized.encode(ImageFormat.PNG, quality),
|
|
100
|
+
resized.encode(ImageFormat.JPEG, quality),
|
|
101
|
+
]);
|
|
99
102
|
|
|
100
103
|
return pickSmaller(
|
|
101
104
|
{ buffer: pngBuffer, mimeType: "image/png" },
|