@oh-my-pi/pi-coding-agent 5.6.77 → 5.7.68

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +8 -8
  3. package/src/migrations.ts +1 -34
  4. package/src/utils/image-convert.ts +1 -1
  5. package/src/utils/image-resize.ts +2 -2
  6. package/src/vendor/photon/LICENSE.md +201 -0
  7. package/src/vendor/photon/README.md +158 -0
  8. package/src/vendor/photon/index.d.ts +3013 -0
  9. package/src/vendor/photon/index.js +4461 -0
  10. package/src/vendor/photon/photon_rs_bg.wasm +0 -0
  11. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
  12. package/src/vendor/photon/photon_rs_bg.wasm.d.ts +193 -0
  13. package/src/core/python-executor-display.test.ts +0 -42
  14. package/src/core/python-executor-lifecycle.test.ts +0 -99
  15. package/src/core/python-executor-mapping.test.ts +0 -41
  16. package/src/core/python-executor-per-call.test.ts +0 -49
  17. package/src/core/python-executor-session.test.ts +0 -103
  18. package/src/core/python-executor-streaming.test.ts +0 -77
  19. package/src/core/python-executor-timeout.test.ts +0 -35
  20. package/src/core/python-executor.lifecycle.test.ts +0 -139
  21. package/src/core/python-executor.result.test.ts +0 -49
  22. package/src/core/python-executor.test.ts +0 -180
  23. package/src/core/python-kernel-display.test.ts +0 -54
  24. package/src/core/python-kernel-env.test.ts +0 -138
  25. package/src/core/python-kernel-session.test.ts +0 -87
  26. package/src/core/python-kernel-ws.test.ts +0 -104
  27. package/src/core/python-kernel.lifecycle.test.ts +0 -249
  28. package/src/core/python-kernel.test.ts +0 -461
  29. package/src/core/python-modules.test.ts +0 -102
  30. package/src/core/python-prelude.test.ts +0 -140
  31. package/src/core/settings-manager-python.test.ts +0 -23
  32. package/src/core/streaming-output.test.ts +0 -26
  33. package/src/core/system-prompt.python.test.ts +0 -17
  34. package/src/core/tools/index.test.ts +0 -212
  35. package/src/core/tools/python-execution.test.ts +0 -68
  36. package/src/core/tools/python-fallback.test.ts +0 -72
  37. package/src/core/tools/python-renderer.test.ts +0 -36
  38. package/src/core/tools/python-tool-mode.test.ts +0 -43
  39. package/src/core/tools/python.test.ts +0 -121
  40. package/src/core/tools/schema-validation.test.ts +0 -530
  41. package/src/core/tools/web-scrapers/academic.test.ts +0 -239
  42. package/src/core/tools/web-scrapers/business.test.ts +0 -82
  43. package/src/core/tools/web-scrapers/dev-platforms.test.ts +0 -254
  44. package/src/core/tools/web-scrapers/documentation.test.ts +0 -85
  45. package/src/core/tools/web-scrapers/finance-media.test.ts +0 -144
  46. package/src/core/tools/web-scrapers/git-hosting.test.ts +0 -272
  47. package/src/core/tools/web-scrapers/media.test.ts +0 -138
  48. package/src/core/tools/web-scrapers/package-managers-2.test.ts +0 -199
  49. package/src/core/tools/web-scrapers/package-managers.test.ts +0 -171
  50. package/src/core/tools/web-scrapers/package-registries.test.ts +0 -259
  51. package/src/core/tools/web-scrapers/research.test.ts +0 -107
  52. package/src/core/tools/web-scrapers/security.test.ts +0 -103
  53. package/src/core/tools/web-scrapers/social-extended.test.ts +0 -192
  54. package/src/core/tools/web-scrapers/social.test.ts +0 -259
  55. package/src/core/tools/web-scrapers/stackexchange.test.ts +0 -120
  56. package/src/core/tools/web-scrapers/standards.test.ts +0 -122
  57. package/src/core/tools/web-scrapers/wikipedia.test.ts +0 -73
  58. package/src/core/tools/web-scrapers/youtube.test.ts +0 -198
  59. package/src/discovery/helpers.test.ts +0 -131
@@ -1,23 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { SettingsManager } from "./settings-manager";
3
-
4
- describe("SettingsManager python settings", () => {
5
- it("defaults to both and session", () => {
6
- const settings = SettingsManager.inMemory();
7
-
8
- expect(settings.getPythonToolMode()).toBe("both");
9
- expect(settings.getPythonKernelMode()).toBe("session");
10
- });
11
-
12
- it("persists python tool and kernel modes", async () => {
13
- const settings = SettingsManager.inMemory();
14
-
15
- await settings.setPythonToolMode("bash-only");
16
- await settings.setPythonKernelMode("per-call");
17
-
18
- expect(settings.getPythonToolMode()).toBe("bash-only");
19
- expect(settings.getPythonKernelMode()).toBe("per-call");
20
- expect(settings.serialize().python?.toolMode).toBe("bash-only");
21
- expect(settings.serialize().python?.kernelMode).toBe("per-call");
22
- });
23
- });
@@ -1,26 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { createOutputSink } from "./streaming-output";
3
-
4
- function makeLargeOutput(size: number): string {
5
- return "x".repeat(size);
6
- }
7
-
8
- describe("createOutputSink", () => {
9
- it("spills to disk and truncates large output", async () => {
10
- const largeOutput = makeLargeOutput(60_000);
11
- const sink = createOutputSink(10, 70_000);
12
- const writer = sink.getWriter();
13
-
14
- await writer.write(largeOutput);
15
- await writer.close();
16
-
17
- const result = sink.dump();
18
-
19
- expect(result.truncated).toBe(true);
20
- expect(result.fullOutputPath).toBeDefined();
21
- expect(result.output.length).toBeLessThan(largeOutput.length);
22
-
23
- const fullOutput = await Bun.file(result.fullOutputPath!).text();
24
- expect(fullOutput).toBe(largeOutput);
25
- });
26
- });
@@ -1,17 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { buildSystemPrompt } from "./system-prompt";
3
-
4
- describe("buildSystemPrompt", () => {
5
- it("includes python tool details when enabled", async () => {
6
- const prompt = await buildSystemPrompt({
7
- cwd: "/tmp",
8
- toolNames: ["python"],
9
- contextFiles: [],
10
- skills: [],
11
- rules: [],
12
- });
13
-
14
- expect(prompt).toContain("python: Execute Python code via a session-backed IPython kernel");
15
- expect(prompt).toContain("What python IS for");
16
- });
17
- });
@@ -1,212 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { BUILTIN_TOOLS, createTools, HIDDEN_TOOLS, type ToolSession } from "./index";
3
-
4
- process.env.OMP_PYTHON_SKIP_CHECK = "1";
5
-
6
- function createTestSession(overrides: Partial<ToolSession> = {}): ToolSession {
7
- return {
8
- cwd: "/tmp/test",
9
- hasUI: false,
10
- getSessionFile: () => null,
11
- getSessionSpawns: () => "*",
12
- ...overrides,
13
- };
14
- }
15
-
16
- function createBaseSettings(overrides: Partial<NonNullable<ToolSession["settings"]>> = {}) {
17
- return {
18
- getImageAutoResize: () => true,
19
- getLspFormatOnWrite: () => true,
20
- getLspDiagnosticsOnWrite: () => true,
21
- getLspDiagnosticsOnEdit: () => false,
22
- getEditFuzzyMatch: () => true,
23
- getGitToolEnabled: () => true,
24
- getBashInterceptorEnabled: () => true,
25
- getBashInterceptorSimpleLsEnabled: () => true,
26
- getBashInterceptorRules: () => [],
27
- ...overrides,
28
- };
29
- }
30
-
31
- describe("createTools", () => {
32
- it("creates all builtin tools by default", async () => {
33
- const session = createTestSession();
34
- const tools = await createTools(session);
35
- const names = tools.map((t) => t.name);
36
-
37
- // Core tools should always be present
38
- expect(names).toContain("python");
39
- expect(names).not.toContain("bash");
40
- expect(names).toContain("calc");
41
- expect(names).toContain("read");
42
- expect(names).toContain("edit");
43
- expect(names).toContain("write");
44
- expect(names).toContain("grep");
45
- expect(names).toContain("find");
46
- expect(names).toContain("ls");
47
- expect(names).toContain("lsp");
48
- expect(names).toContain("notebook");
49
- expect(names).toContain("task");
50
- expect(names).toContain("todo_write");
51
- expect(names).toContain("output");
52
- expect(names).toContain("web_fetch");
53
- expect(names).toContain("web_search");
54
- });
55
-
56
- it("includes bash and python when python mode is both", async () => {
57
- const session = createTestSession({
58
- settings: createBaseSettings({
59
- getPythonToolMode: () => "both",
60
- getPythonKernelMode: () => "session",
61
- }),
62
- });
63
- const tools = await createTools(session);
64
- const names = tools.map((t) => t.name);
65
-
66
- expect(names).toContain("bash");
67
- expect(names).toContain("python");
68
- });
69
-
70
- it("includes bash only when python mode is bash-only", async () => {
71
- const session = createTestSession({
72
- settings: createBaseSettings({
73
- getPythonToolMode: () => "bash-only",
74
- getPythonKernelMode: () => "session",
75
- }),
76
- });
77
- const tools = await createTools(session);
78
- const names = tools.map((t) => t.name);
79
-
80
- expect(names).toContain("bash");
81
- expect(names).not.toContain("python");
82
- });
83
-
84
- it("excludes lsp tool when session disables LSP", async () => {
85
- const session = createTestSession({ enableLsp: false });
86
- const tools = await createTools(session, ["read", "lsp", "write"]);
87
- const names = tools.map((t) => t.name);
88
-
89
- expect(names).toEqual(["read", "write"]);
90
- });
91
-
92
- it("excludes lsp tool when disabled", async () => {
93
- const session = createTestSession({ enableLsp: false });
94
- const tools = await createTools(session);
95
- const names = tools.map((t) => t.name);
96
-
97
- expect(names).not.toContain("lsp");
98
- });
99
-
100
- it("respects requested tool subset", async () => {
101
- const session = createTestSession();
102
- const tools = await createTools(session, ["read", "write"]);
103
- const names = tools.map((t) => t.name);
104
-
105
- expect(names).toEqual(["read", "write"]);
106
- });
107
-
108
- it("includes hidden tools when explicitly requested", async () => {
109
- const session = createTestSession();
110
- const tools = await createTools(session, ["report_finding"]);
111
- const names = tools.map((t) => t.name);
112
-
113
- expect(names).toEqual(["report_finding"]);
114
- });
115
-
116
- it("includes complete tool when required", async () => {
117
- const session = createTestSession({ requireCompleteTool: true });
118
- const tools = await createTools(session);
119
- const names = tools.map((t) => t.name);
120
-
121
- expect(names).toContain("complete");
122
- });
123
-
124
- it("excludes ask tool when hasUI is false", async () => {
125
- const session = createTestSession({ hasUI: false });
126
- const tools = await createTools(session);
127
- const names = tools.map((t) => t.name);
128
-
129
- expect(names).not.toContain("ask");
130
- });
131
-
132
- it("includes ask tool when hasUI is true", async () => {
133
- const session = createTestSession({ hasUI: true });
134
- const tools = await createTools(session);
135
- const names = tools.map((t) => t.name);
136
-
137
- expect(names).toContain("ask");
138
- });
139
-
140
- it("excludes git tool when disabled in settings", async () => {
141
- const session = createTestSession({
142
- settings: createBaseSettings({ getGitToolEnabled: () => false }),
143
- });
144
- const tools = await createTools(session);
145
- const names = tools.map((t) => t.name);
146
-
147
- expect(names).not.toContain("git");
148
- });
149
-
150
- it("includes git tool when enabled in settings", async () => {
151
- const session = createTestSession({
152
- settings: createBaseSettings({ getGitToolEnabled: () => true }),
153
- });
154
- const tools = await createTools(session);
155
- const names = tools.map((t) => t.name);
156
-
157
- expect(names).toContain("git");
158
- });
159
-
160
- it("includes git tool when no settings provided (default enabled)", async () => {
161
- const session = createTestSession({ settings: undefined });
162
- const tools = await createTools(session);
163
- const names = tools.map((t) => t.name);
164
-
165
- expect(names).toContain("git");
166
- });
167
-
168
- it("always includes output tool when task tool is present", async () => {
169
- const session = createTestSession();
170
- const tools = await createTools(session);
171
- const names = tools.map((t) => t.name);
172
-
173
- // Both should be present together
174
- expect(names).toContain("task");
175
- expect(names).toContain("output");
176
- });
177
-
178
- it("BUILTIN_TOOLS contains all expected tools", () => {
179
- const expectedTools = [
180
- "ask",
181
- "bash",
182
- "python",
183
- "calc",
184
- "ssh",
185
- "edit",
186
- "find",
187
- "git",
188
- "grep",
189
- "ls",
190
- "lsp",
191
- "notebook",
192
- "output",
193
- "read",
194
- "task",
195
- "todo_write",
196
- "web_fetch",
197
- "web_search",
198
- "write",
199
- ];
200
-
201
- for (const tool of expectedTools) {
202
- expect(BUILTIN_TOOLS).toHaveProperty(tool);
203
- }
204
-
205
- // Ensure we haven't missed any
206
- expect(Object.keys(BUILTIN_TOOLS).sort()).toEqual(expectedTools.sort());
207
- });
208
-
209
- it("HIDDEN_TOOLS contains review tools", () => {
210
- expect(Object.keys(HIDDEN_TOOLS).sort()).toEqual(["complete", "report_finding"]);
211
- });
212
- });
@@ -1,68 +0,0 @@
1
- import { describe, expect, it, vi } from "bun:test";
2
- import { mkdtempSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import * as pythonExecutor from "../python-executor";
6
- import type { ToolSession } from "./index";
7
- import { createPythonTool } from "./python";
8
-
9
- function createSession(cwd: string): ToolSession {
10
- return {
11
- cwd,
12
- hasUI: false,
13
- getSessionFile: () => "session-file",
14
- getSessionSpawns: () => "*",
15
- settings: {
16
- getImageAutoResize: () => true,
17
- getLspFormatOnWrite: () => true,
18
- getLspDiagnosticsOnWrite: () => true,
19
- getLspDiagnosticsOnEdit: () => false,
20
- getEditFuzzyMatch: () => true,
21
- getGitToolEnabled: () => true,
22
- getBashInterceptorEnabled: () => true,
23
- getBashInterceptorSimpleLsEnabled: () => true,
24
- getBashInterceptorRules: () => [],
25
- getPythonToolMode: () => "ipy-only",
26
- getPythonKernelMode: () => "per-call",
27
- },
28
- };
29
- }
30
-
31
- describe("python tool execution", () => {
32
- it("passes kernel options from settings and args", async () => {
33
- const tempDir = mkdtempSync(join(tmpdir(), "python-tool-"));
34
- const executeSpy = vi.spyOn(pythonExecutor, "executePython").mockResolvedValue({
35
- output: "ok",
36
- exitCode: 0,
37
- cancelled: false,
38
- truncated: false,
39
- displayOutputs: [],
40
- stdinRequested: false,
41
- });
42
-
43
- const tool = createPythonTool(createSession(tempDir));
44
- const result = await tool.execute(
45
- "call-id",
46
- { code: "print('hi')", timeout: 5, workdir: tempDir, reset: true },
47
- undefined,
48
- undefined,
49
- undefined,
50
- );
51
-
52
- expect(executeSpy).toHaveBeenCalledWith(
53
- "print('hi')",
54
- expect.objectContaining({
55
- cwd: tempDir,
56
- timeout: 5000,
57
- sessionId: `session:session-file:workdir:${tempDir}`,
58
- kernelMode: "per-call",
59
- reset: true,
60
- }),
61
- );
62
- const text = result.content.find((item) => item.type === "text")?.text;
63
- expect(text).toBe("ok");
64
-
65
- executeSpy.mockRestore();
66
- rmSync(tempDir, { recursive: true, force: true });
67
- });
68
- });
@@ -1,72 +0,0 @@
1
- import { describe, expect, it, vi } from "bun:test";
2
- import * as pythonKernelModule from "../python-kernel";
3
- import type { ToolSession } from "./index";
4
- import { createTools } from "./index";
5
-
6
- function createTestSession(overrides: Partial<ToolSession> = {}): ToolSession {
7
- return {
8
- cwd: "/tmp/test",
9
- hasUI: false,
10
- getSessionFile: () => null,
11
- getSessionSpawns: () => "*",
12
- ...overrides,
13
- };
14
- }
15
-
16
- function createBaseSettings(overrides: Partial<NonNullable<ToolSession["settings"]>> = {}) {
17
- return {
18
- getImageAutoResize: () => true,
19
- getLspFormatOnWrite: () => true,
20
- getLspDiagnosticsOnWrite: () => true,
21
- getLspDiagnosticsOnEdit: () => false,
22
- getEditFuzzyMatch: () => true,
23
- getGitToolEnabled: () => true,
24
- getBashInterceptorEnabled: () => true,
25
- getBashInterceptorSimpleLsEnabled: () => true,
26
- getBashInterceptorRules: () => [],
27
- ...overrides,
28
- };
29
- }
30
-
31
- describe("createTools python fallback", () => {
32
- it("falls back to bash-only when kernel unavailable", async () => {
33
- const availabilitySpy = vi
34
- .spyOn(pythonKernelModule, "checkPythonKernelAvailability")
35
- .mockResolvedValue({ ok: false, reason: "unavailable" });
36
-
37
- const session = createTestSession({
38
- settings: createBaseSettings({
39
- getPythonToolMode: () => "ipy-only",
40
- getPythonKernelMode: () => "session",
41
- }),
42
- });
43
-
44
- const tools = await createTools(session, ["python"]);
45
- const names = tools.map((tool) => tool.name).sort();
46
-
47
- expect(names).toEqual(["bash"]);
48
-
49
- availabilitySpy.mockRestore();
50
- });
51
-
52
- it("keeps bash when python mode is both but unavailable", async () => {
53
- const availabilitySpy = vi
54
- .spyOn(pythonKernelModule, "checkPythonKernelAvailability")
55
- .mockResolvedValue({ ok: false, reason: "unavailable" });
56
-
57
- const session = createTestSession({
58
- settings: createBaseSettings({
59
- getPythonToolMode: () => "both",
60
- getPythonKernelMode: () => "session",
61
- }),
62
- });
63
-
64
- const tools = await createTools(session);
65
- const names = tools.map((tool) => tool.name);
66
-
67
- expect(names).toContain("bash");
68
- expect(names).not.toContain("python");
69
-
70
- availabilitySpy.mockRestore();
71
- });
72
- });
@@ -1,36 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import stripAnsi from "strip-ansi";
3
- import { getThemeByName } from "../../modes/interactive/theme/theme";
4
- import { pythonToolRenderer } from "./python";
5
- import { truncateTail } from "./truncate";
6
-
7
- describe("pythonToolRenderer", () => {
8
- it("renders truncated output when collapsed and full output when expanded", () => {
9
- const theme = getThemeByName("dark");
10
- expect(theme).toBeDefined();
11
- const uiTheme = theme!;
12
-
13
- const fullOutput = ["line 1", "line 2", "line 3", "line 4"].join("\n");
14
- const truncation = truncateTail(fullOutput, { maxLines: 2, maxBytes: 128 });
15
-
16
- const result = {
17
- content: [{ type: "text", text: truncation.content }],
18
- details: {
19
- truncation,
20
- fullOutput,
21
- },
22
- };
23
-
24
- const collapsed = pythonToolRenderer.renderResult(result, { expanded: false, isPartial: false }, uiTheme);
25
- const collapsedLines = stripAnsi(collapsed.render(80).join("\n"));
26
- expect(collapsedLines).toContain("line 4");
27
- expect(collapsedLines).not.toContain("line 1");
28
- expect(collapsedLines).toContain("Truncated:");
29
-
30
- const expanded = pythonToolRenderer.renderResult(result, { expanded: true, isPartial: false }, uiTheme);
31
- const expandedLines = stripAnsi(expanded.render(80).join("\n"));
32
- expect(expandedLines).toContain("line 1");
33
- expect(expandedLines).toContain("line 4");
34
- expect(expandedLines).not.toContain("Truncated:");
35
- });
36
- });
@@ -1,43 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { createTools, type ToolSession } from "./index";
3
-
4
- function createSession(overrides: Partial<ToolSession> = {}): ToolSession {
5
- return {
6
- cwd: "/tmp/test",
7
- hasUI: false,
8
- getSessionFile: () => null,
9
- getSessionSpawns: () => "*",
10
- settings: {
11
- getImageAutoResize: () => true,
12
- getLspFormatOnWrite: () => true,
13
- getLspDiagnosticsOnWrite: () => true,
14
- getLspDiagnosticsOnEdit: () => false,
15
- getEditFuzzyMatch: () => true,
16
- getGitToolEnabled: () => true,
17
- getBashInterceptorEnabled: () => true,
18
- getBashInterceptorSimpleLsEnabled: () => true,
19
- getBashInterceptorRules: () => [],
20
- getPythonToolMode: () => "bash-only",
21
- getPythonKernelMode: () => "session",
22
- },
23
- ...overrides,
24
- };
25
- }
26
-
27
- describe("createTools python fallback", () => {
28
- it("falls back to bash when python is requested but disabled", async () => {
29
- const previous = process.env.OMP_PYTHON_SKIP_CHECK;
30
- process.env.OMP_PYTHON_SKIP_CHECK = "1";
31
- const session = createSession();
32
- const tools = await createTools(session, ["python"]);
33
- const names = tools.map((tool) => tool.name);
34
-
35
- expect(names).toEqual(["bash"]);
36
-
37
- if (previous === undefined) {
38
- delete process.env.OMP_PYTHON_SKIP_CHECK;
39
- } else {
40
- process.env.OMP_PYTHON_SKIP_CHECK = previous;
41
- }
42
- });
43
- });
@@ -1,121 +0,0 @@
1
- import { afterAll, beforeAll, describe, expect, it, vi } from "bun:test";
2
- import * as pythonExecutor from "../python-executor";
3
- import { createTools, type ToolSession } from "./index";
4
- import { createPythonTool } from "./python";
5
-
6
- let previousSkipCheck: string | undefined;
7
-
8
- beforeAll(() => {
9
- previousSkipCheck = process.env.OMP_PYTHON_SKIP_CHECK;
10
- process.env.OMP_PYTHON_SKIP_CHECK = "1";
11
- });
12
-
13
- afterAll(() => {
14
- if (previousSkipCheck === undefined) {
15
- delete process.env.OMP_PYTHON_SKIP_CHECK;
16
- return;
17
- }
18
- process.env.OMP_PYTHON_SKIP_CHECK = previousSkipCheck;
19
- });
20
-
21
- function createSession(overrides: Partial<ToolSession> = {}): ToolSession {
22
- return {
23
- cwd: "/tmp/test",
24
- hasUI: false,
25
- getSessionFile: () => null,
26
- getSessionSpawns: () => "*",
27
- ...overrides,
28
- };
29
- }
30
-
31
- function createSettings(toolMode: "ipy-only" | "bash-only" | "both") {
32
- return {
33
- getImageAutoResize: () => true,
34
- getLspFormatOnWrite: () => true,
35
- getLspDiagnosticsOnWrite: () => true,
36
- getLspDiagnosticsOnEdit: () => false,
37
- getEditFuzzyMatch: () => true,
38
- getGitToolEnabled: () => true,
39
- getBashInterceptorEnabled: () => true,
40
- getBashInterceptorSimpleLsEnabled: () => true,
41
- getBashInterceptorRules: () => [],
42
- getPythonToolMode: () => toolMode,
43
- getPythonKernelMode: () => "session" as const,
44
- };
45
- }
46
-
47
- describe("python tool schema", () => {
48
- it("exposes expected parameters", () => {
49
- const tool = createPythonTool(createSession());
50
- const schema = tool.parameters as {
51
- type: string;
52
- properties: Record<string, { type: string; description?: string }>;
53
- required?: string[];
54
- };
55
-
56
- expect(schema.type).toBe("object");
57
- expect(schema.properties.code.type).toBe("string");
58
- expect(schema.properties.timeout.type).toBe("number");
59
- expect(schema.properties.workdir.type).toBe("string");
60
- expect(schema.properties.reset.type).toBe("boolean");
61
- expect(schema.required).toEqual(["code"]);
62
- });
63
- });
64
-
65
- describe("python tool docs template", () => {
66
- it("renders dynamic helper docs", () => {
67
- const docs = [
68
- {
69
- name: "read",
70
- signature: "(path)",
71
- docstring: "Read file contents.",
72
- category: "File I/O",
73
- },
74
- ];
75
- const spy = vi.spyOn(pythonExecutor, "getPreludeDocs").mockReturnValue(docs);
76
-
77
- const tool = createPythonTool(createSession());
78
-
79
- expect(tool.description).toContain("### File I/O");
80
- expect(tool.description).toContain("read(path)");
81
- expect(tool.description).toContain("Read file contents.");
82
-
83
- spy.mockRestore();
84
- });
85
-
86
- it("renders fallback when docs are unavailable", () => {
87
- const spy = vi.spyOn(pythonExecutor, "getPreludeDocs").mockReturnValue([]);
88
-
89
- const tool = createPythonTool(createSession());
90
-
91
- expect(tool.description).toContain("Documentation unavailable — Python kernel failed to start");
92
-
93
- spy.mockRestore();
94
- });
95
- });
96
-
97
- describe("python tool exposure", () => {
98
- it("includes python only in ipy-only mode", async () => {
99
- const session = createSession({ settings: createSettings("ipy-only") });
100
- const tools = await createTools(session);
101
- const names = tools.map((tool) => tool.name);
102
- expect(names).toContain("python");
103
- expect(names).not.toContain("bash");
104
- });
105
-
106
- it("includes bash only in bash-only mode", async () => {
107
- const session = createSession({ settings: createSettings("bash-only") });
108
- const tools = await createTools(session);
109
- const names = tools.map((tool) => tool.name);
110
- expect(names).toContain("bash");
111
- expect(names).not.toContain("python");
112
- });
113
-
114
- it("includes bash and python in both mode", async () => {
115
- const session = createSession({ settings: createSettings("both") });
116
- const tools = await createTools(session);
117
- const names = tools.map((tool) => tool.name);
118
- expect(names).toContain("bash");
119
- expect(names).toContain("python");
120
- });
121
- });