@oh-my-pi/pi-coding-agent 5.5.0 → 5.6.70
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 +105 -0
- package/docs/python-repl.md +77 -0
- package/examples/hooks/snake.ts +7 -7
- package/package.json +5 -5
- package/src/bun-imports.d.ts +6 -0
- package/src/cli/args.ts +7 -0
- package/src/cli/setup-cli.ts +231 -0
- package/src/cli.ts +2 -0
- package/src/core/agent-session.ts +118 -15
- package/src/core/bash-executor.ts +3 -84
- package/src/core/compaction/compaction.ts +10 -5
- package/src/core/extensions/index.ts +2 -0
- package/src/core/extensions/loader.ts +13 -1
- package/src/core/extensions/runner.ts +50 -2
- package/src/core/extensions/types.ts +67 -2
- package/src/core/keybindings.ts +51 -1
- package/src/core/prompt-templates.ts +15 -0
- package/src/core/python-executor-display.test.ts +42 -0
- package/src/core/python-executor-lifecycle.test.ts +99 -0
- package/src/core/python-executor-mapping.test.ts +41 -0
- package/src/core/python-executor-per-call.test.ts +49 -0
- package/src/core/python-executor-session.test.ts +103 -0
- package/src/core/python-executor-streaming.test.ts +77 -0
- package/src/core/python-executor-timeout.test.ts +35 -0
- package/src/core/python-executor.lifecycle.test.ts +139 -0
- package/src/core/python-executor.result.test.ts +49 -0
- package/src/core/python-executor.test.ts +180 -0
- package/src/core/python-executor.ts +313 -0
- package/src/core/python-gateway-coordinator.ts +832 -0
- package/src/core/python-kernel-display.test.ts +54 -0
- package/src/core/python-kernel-env.test.ts +138 -0
- package/src/core/python-kernel-session.test.ts +87 -0
- package/src/core/python-kernel-ws.test.ts +104 -0
- package/src/core/python-kernel.lifecycle.test.ts +249 -0
- package/src/core/python-kernel.test.ts +461 -0
- package/src/core/python-kernel.ts +1182 -0
- package/src/core/python-modules.test.ts +102 -0
- package/src/core/python-modules.ts +110 -0
- package/src/core/python-prelude.py +889 -0
- package/src/core/python-prelude.test.ts +140 -0
- package/src/core/python-prelude.ts +3 -0
- package/src/core/sdk.ts +24 -6
- package/src/core/session-manager.ts +174 -82
- package/src/core/settings-manager-python.test.ts +23 -0
- package/src/core/settings-manager.ts +202 -0
- package/src/core/streaming-output.test.ts +26 -0
- package/src/core/streaming-output.ts +100 -0
- package/src/core/system-prompt.python.test.ts +17 -0
- package/src/core/system-prompt.ts +3 -1
- package/src/core/timings.ts +1 -1
- package/src/core/tools/bash.ts +13 -2
- package/src/core/tools/edit-diff.ts +9 -1
- package/src/core/tools/index.test.ts +50 -23
- package/src/core/tools/index.ts +83 -1
- package/src/core/tools/python-execution.test.ts +68 -0
- package/src/core/tools/python-fallback.test.ts +72 -0
- package/src/core/tools/python-renderer.test.ts +36 -0
- package/src/core/tools/python-tool-mode.test.ts +43 -0
- package/src/core/tools/python.test.ts +121 -0
- package/src/core/tools/python.ts +760 -0
- package/src/core/tools/renderers.ts +2 -0
- package/src/core/tools/schema-validation.test.ts +1 -0
- package/src/core/tools/task/executor.ts +146 -3
- package/src/core/tools/task/worker-protocol.ts +32 -2
- package/src/core/tools/task/worker.ts +182 -15
- package/src/index.ts +6 -0
- package/src/main.ts +136 -40
- package/src/modes/interactive/components/custom-editor.ts +16 -31
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +5 -16
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -13
- package/src/modes/interactive/components/history-search.ts +5 -8
- package/src/modes/interactive/components/hook-editor.ts +3 -4
- package/src/modes/interactive/components/hook-input.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +5 -15
- package/src/modes/interactive/components/index.ts +1 -0
- package/src/modes/interactive/components/keybinding-hints.ts +66 -0
- package/src/modes/interactive/components/model-selector.ts +53 -66
- package/src/modes/interactive/components/oauth-selector.ts +5 -5
- package/src/modes/interactive/components/session-selector.ts +29 -23
- package/src/modes/interactive/components/settings-defs.ts +404 -196
- package/src/modes/interactive/components/settings-selector.ts +14 -10
- package/src/modes/interactive/components/status-line-segment-editor.ts +7 -7
- package/src/modes/interactive/components/tool-execution.ts +8 -0
- package/src/modes/interactive/components/tree-selector.ts +29 -23
- package/src/modes/interactive/components/user-message-selector.ts +6 -17
- package/src/modes/interactive/controllers/command-controller.ts +86 -37
- package/src/modes/interactive/controllers/event-controller.ts +8 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +51 -0
- package/src/modes/interactive/controllers/input-controller.ts +42 -6
- package/src/modes/interactive/interactive-mode.ts +56 -30
- package/src/modes/interactive/theme/theme-schema.json +2 -2
- package/src/modes/interactive/types.ts +6 -1
- package/src/modes/interactive/utils/ui-helpers.ts +2 -1
- package/src/modes/print-mode.ts +23 -0
- package/src/modes/rpc/rpc-mode.ts +21 -0
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/system/system-prompt.md +32 -1
- package/src/prompts/tools/python.md +91 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { basename, join, resolve } from "node:path";
|
|
5
|
+
import { discoverPythonModules, loadPythonModules, type PythonModuleExecutor } from "./python-modules";
|
|
6
|
+
|
|
7
|
+
const fixturesDir = resolve(__dirname, "../../test/fixtures/python-modules");
|
|
8
|
+
|
|
9
|
+
const readFixture = (name: string): string => readFileSync(join(fixturesDir, name), "utf-8");
|
|
10
|
+
|
|
11
|
+
const writeModule = (dir: string, name: string, tag: string) => {
|
|
12
|
+
mkdirSync(dir, { recursive: true });
|
|
13
|
+
const base = readFixture(name);
|
|
14
|
+
writeFileSync(join(dir, name), `${base}\n# ${tag}`);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const createTempRoot = () => mkdtempSync(join(tmpdir(), "omp-python-modules-"));
|
|
18
|
+
|
|
19
|
+
describe("python modules", () => {
|
|
20
|
+
let tempRoot: string | null = null;
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
if (tempRoot) {
|
|
24
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
tempRoot = null;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("discovers modules with project override and sorted order", async () => {
|
|
30
|
+
tempRoot = createTempRoot();
|
|
31
|
+
const homeDir = join(tempRoot, "home");
|
|
32
|
+
const cwd = join(tempRoot, "project");
|
|
33
|
+
|
|
34
|
+
writeModule(join(homeDir, ".omp", "agent", "modules"), "alpha.py", "user-omp");
|
|
35
|
+
writeModule(join(homeDir, ".pi", "agent", "modules"), "beta.py", "user-pi");
|
|
36
|
+
writeModule(join(homeDir, ".pi", "agent", "modules"), "delta.py", "user-pi");
|
|
37
|
+
|
|
38
|
+
writeModule(join(cwd, ".omp", "modules"), "alpha.py", "project-omp");
|
|
39
|
+
writeModule(join(cwd, ".omp", "modules"), "beta.py", "project-omp");
|
|
40
|
+
writeModule(join(cwd, ".pi", "modules"), "gamma.py", "project-pi");
|
|
41
|
+
|
|
42
|
+
const modules = await discoverPythonModules({ cwd, homeDir });
|
|
43
|
+
const names = modules.map((module) => basename(module.path));
|
|
44
|
+
expect(names).toEqual(["alpha.py", "beta.py", "delta.py", "gamma.py"]);
|
|
45
|
+
expect(modules.map((module) => ({ name: basename(module.path), source: module.source }))).toEqual([
|
|
46
|
+
{ name: "alpha.py", source: "project" },
|
|
47
|
+
{ name: "beta.py", source: "project" },
|
|
48
|
+
{ name: "delta.py", source: "user" },
|
|
49
|
+
{ name: "gamma.py", source: "project" },
|
|
50
|
+
]);
|
|
51
|
+
expect(modules.find((module) => module.path.endsWith("alpha.py"))?.content).toContain("project-omp");
|
|
52
|
+
expect(modules.find((module) => module.path.endsWith("delta.py"))?.content).toContain("user-pi");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("loads modules in sorted order with silent execution", async () => {
|
|
56
|
+
tempRoot = createTempRoot();
|
|
57
|
+
const homeDir = join(tempRoot, "home");
|
|
58
|
+
const cwd = join(tempRoot, "project");
|
|
59
|
+
|
|
60
|
+
writeModule(join(homeDir, ".omp", "agent", "modules"), "beta.py", "user-omp");
|
|
61
|
+
writeModule(join(homeDir, ".omp", "agent", "modules"), "alpha.py", "user-omp");
|
|
62
|
+
|
|
63
|
+
const calls: Array<{ name: string; options?: { silent?: boolean; storeHistory?: boolean } }> = [];
|
|
64
|
+
const executor: PythonModuleExecutor = {
|
|
65
|
+
execute: async (code: string, options?: { silent?: boolean; storeHistory?: boolean }) => {
|
|
66
|
+
const name = code.includes("def alpha") ? "alpha" : "beta";
|
|
67
|
+
calls.push({ name, options });
|
|
68
|
+
return { status: "ok", cancelled: false };
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
await loadPythonModules(executor, { cwd, homeDir });
|
|
73
|
+
expect(calls.map((call) => call.name)).toEqual(["alpha", "beta"]);
|
|
74
|
+
for (const call of calls) {
|
|
75
|
+
expect(call.options).toEqual({ silent: true, storeHistory: false });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("fails fast when a module fails to execute", async () => {
|
|
80
|
+
tempRoot = createTempRoot();
|
|
81
|
+
const homeDir = join(tempRoot, "home");
|
|
82
|
+
const cwd = join(tempRoot, "project");
|
|
83
|
+
|
|
84
|
+
writeModule(join(homeDir, ".omp", "agent", "modules"), "alpha.py", "user-omp");
|
|
85
|
+
writeModule(join(cwd, ".omp", "modules"), "beta.py", "project-omp");
|
|
86
|
+
|
|
87
|
+
const executor: PythonModuleExecutor = {
|
|
88
|
+
execute: async (code: string) => {
|
|
89
|
+
if (code.includes("def beta")) {
|
|
90
|
+
return {
|
|
91
|
+
status: "error",
|
|
92
|
+
cancelled: false,
|
|
93
|
+
error: { name: "Error", value: "boom", traceback: [] },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { status: "ok", cancelled: false };
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
await expect(loadPythonModules(executor, { cwd, homeDir })).rejects.toThrow("Failed to load Python module");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
export type PythonModuleSource = "user" | "project";
|
|
6
|
+
|
|
7
|
+
export interface PythonModuleEntry {
|
|
8
|
+
path: string;
|
|
9
|
+
content: string;
|
|
10
|
+
source: PythonModuleSource;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PythonModuleExecuteResult {
|
|
14
|
+
status: "ok" | "error";
|
|
15
|
+
cancelled: boolean;
|
|
16
|
+
error?: { name: string; value: string; traceback: string[] };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PythonModuleExecutor {
|
|
20
|
+
execute: (
|
|
21
|
+
code: string,
|
|
22
|
+
options?: { silent?: boolean; storeHistory?: boolean },
|
|
23
|
+
) => Promise<PythonModuleExecuteResult>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DiscoverPythonModulesOptions {
|
|
27
|
+
/** Working directory for project-level modules. Default: process.cwd() */
|
|
28
|
+
cwd?: string;
|
|
29
|
+
/** Home directory for user-level modules. Default: os.homedir() */
|
|
30
|
+
homeDir?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ModuleCandidate {
|
|
34
|
+
name: string;
|
|
35
|
+
path: string;
|
|
36
|
+
source: PythonModuleSource;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function listModuleCandidates(dir: string, source: PythonModuleSource): Promise<ModuleCandidate[]> {
|
|
40
|
+
try {
|
|
41
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
42
|
+
return entries
|
|
43
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".py"))
|
|
44
|
+
.map((entry) => ({
|
|
45
|
+
name: entry.name,
|
|
46
|
+
path: resolve(dir, entry.name),
|
|
47
|
+
source,
|
|
48
|
+
}));
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function readModuleContent(candidate: ModuleCandidate): Promise<PythonModuleEntry> {
|
|
55
|
+
try {
|
|
56
|
+
const content = await Bun.file(candidate.path).text();
|
|
57
|
+
return { path: candidate.path, content, source: candidate.source };
|
|
58
|
+
} catch (err) {
|
|
59
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
60
|
+
throw new Error(`Failed to read Python module ${candidate.path}: ${message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Discover Python prelude extension modules from user and project directories.
|
|
66
|
+
*/
|
|
67
|
+
export async function discoverPythonModules(options: DiscoverPythonModulesOptions = {}): Promise<PythonModuleEntry[]> {
|
|
68
|
+
const cwd = options.cwd ?? process.cwd();
|
|
69
|
+
const homeDir = options.homeDir ?? homedir();
|
|
70
|
+
|
|
71
|
+
const userDirs = [join(homeDir, ".omp", "agent", "modules"), join(homeDir, ".pi", "agent", "modules")];
|
|
72
|
+
const projectDirs = [resolve(cwd, ".omp", "modules"), resolve(cwd, ".pi", "modules")];
|
|
73
|
+
|
|
74
|
+
const userCandidates = (await Promise.all(userDirs.map((dir) => listModuleCandidates(dir, "user")))).flat();
|
|
75
|
+
const projectCandidates = (await Promise.all(projectDirs.map((dir) => listModuleCandidates(dir, "project")))).flat();
|
|
76
|
+
|
|
77
|
+
const byName = new Map<string, ModuleCandidate>();
|
|
78
|
+
for (const candidate of userCandidates) {
|
|
79
|
+
if (!byName.has(candidate.name)) {
|
|
80
|
+
byName.set(candidate.name, candidate);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const candidate of projectCandidates) {
|
|
84
|
+
const existing = byName.get(candidate.name);
|
|
85
|
+
if (!existing || existing.source === "user") {
|
|
86
|
+
byName.set(candidate.name, candidate);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sorted = Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
91
|
+
return Promise.all(sorted.map((candidate) => readModuleContent(candidate)));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Load Python prelude extension modules into an active kernel.
|
|
96
|
+
*/
|
|
97
|
+
export async function loadPythonModules(
|
|
98
|
+
executor: PythonModuleExecutor,
|
|
99
|
+
options: DiscoverPythonModulesOptions = {},
|
|
100
|
+
): Promise<PythonModuleEntry[]> {
|
|
101
|
+
const modules = await discoverPythonModules(options);
|
|
102
|
+
for (const module of modules) {
|
|
103
|
+
const result = await executor.execute(module.content, { silent: true, storeHistory: false });
|
|
104
|
+
if (result.cancelled || result.status === "error") {
|
|
105
|
+
const details = result.error ? `${result.error.name}: ${result.error.value}` : "unknown error";
|
|
106
|
+
throw new Error(`Failed to load Python module ${module.path}: ${details}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return modules;
|
|
110
|
+
}
|