@oh-my-pi/pi-coding-agent 17.2.12 → 17.2.14
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 +63 -0
- package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-za420td8.md} +63 -0
- package/dist/cli.js +3658 -3627
- package/dist/types/advisor/delta-split.d.ts +24 -0
- package/dist/types/advisor/runtime.d.ts +2 -2
- package/dist/types/async/job-manager.d.ts +8 -1
- package/dist/types/cli/update-cli.d.ts +53 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-resolver.d.ts +15 -2
- package/dist/types/config/settings-schema.d.ts +14 -0
- package/dist/types/discovery/agents-md.d.ts +10 -1
- package/dist/types/eval/runner-cache.d.ts +12 -0
- package/dist/types/extensibility/extensions/runner.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +15 -4
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
- package/dist/types/lib/xai-http.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +8 -5
- package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +4 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +17 -8
- package/dist/types/modes/types.d.ts +7 -10
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +24 -3
- package/dist/types/session/messages.d.ts +20 -0
- package/dist/types/session/retry-fallback-chains.d.ts +13 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +35 -5
- package/dist/types/session/turn-recovery.d.ts +34 -5
- package/dist/types/slash-commands/types.d.ts +5 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/approval.d.ts +7 -0
- package/dist/types/tools/builtin-names.d.ts +1 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/think.d.ts +38 -0
- package/dist/types/tools/todo.d.ts +14 -15
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/local-date.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/brave.d.ts +8 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
- package/dist/types/web/search/providers/jina.d.ts +3 -3
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +4 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/delta-split.ts +98 -0
- package/src/advisor/runtime.ts +321 -69
- package/src/async/job-manager.ts +14 -3
- package/src/cli/gallery-fixtures/agentic.ts +16 -0
- package/src/cli/plugin-cli.ts +30 -2
- package/src/cli/update-cli.ts +259 -24
- package/src/config/keybindings.ts +52 -9
- package/src/config/model-resolver.ts +19 -3
- package/src/config/settings-schema.ts +16 -0
- package/src/cursor.ts +10 -5
- package/src/discovery/agents-md.ts +61 -23
- package/src/eval/jl/kernel.ts +2 -20
- package/src/eval/py/kernel.ts +2 -20
- package/src/eval/rb/kernel.ts +2 -20
- package/src/eval/runner-cache.ts +41 -0
- package/src/exec/non-interactive-env.ts +14 -3
- package/src/extensibility/extensions/loader.ts +5 -2
- package/src/extensibility/extensions/runner.ts +184 -66
- package/src/extensibility/extensions/types.ts +26 -2
- package/src/extensibility/extensions/wrapper.ts +13 -7
- package/src/extensibility/plugins/marketplace/manager.ts +6 -2
- package/src/hindsight/client.ts +1 -1
- package/src/lib/xai-http.ts +0 -4
- package/src/lsp/client.ts +2 -0
- package/src/lsp/servers.ts +1 -1
- package/src/mcp/tool-bridge.ts +15 -6
- package/src/modes/components/agent-hub-renderer.ts +9 -3
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/status-line/component.ts +58 -6
- package/src/modes/components/status-line/segments.ts +12 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/user-message.ts +20 -5
- package/src/modes/controllers/event-controller.ts +48 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -7
- package/src/modes/controllers/input-controller.ts +25 -6
- package/src/modes/controllers/selector-controller.ts +5 -0
- package/src/modes/interactive-mode.ts +315 -129
- package/src/modes/rpc/rpc-frame.ts +13 -5
- package/src/modes/theme/tui-adapters.ts +4 -5
- package/src/modes/types.ts +13 -7
- package/src/modes/utils/hotkeys-markdown.ts +10 -6
- package/src/prompts/system/system-prompt.md +10 -1
- package/src/registry/persisted-agents.ts +43 -8
- package/src/sdk.ts +146 -9
- package/src/session/agent-session-types.ts +4 -0
- package/src/session/agent-session.ts +91 -10
- package/src/session/messages.ts +98 -28
- package/src/session/retry-fallback-chains.ts +14 -0
- package/src/session/session-advisors.ts +32 -15
- package/src/session/session-history-format.ts +15 -1
- package/src/session/session-maintenance.ts +8 -8
- package/src/session/session-manager.ts +6 -2
- package/src/session/session-tools.ts +355 -183
- package/src/session/turn-recovery.ts +225 -47
- package/src/slash-commands/builtin-modes.ts +41 -12
- package/src/slash-commands/types.ts +5 -1
- package/src/task/executor.ts +92 -45
- package/src/task/structured-subagent.ts +5 -5
- package/src/tools/approval.ts +44 -10
- package/src/tools/builtin-names.ts +1 -2
- package/src/tools/fetch.ts +21 -2
- package/src/tools/image-gen.ts +6 -8
- package/src/tools/index.ts +8 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/think.ts +62 -0
- package/src/tools/todo.ts +70 -26
- package/src/tools/tts.ts +3 -2
- package/src/tools/write.ts +7 -3
- package/src/utils/local-date.ts +13 -0
- package/src/utils/tools-manager.ts +2 -2
- package/src/vibe/runtime.ts +22 -14
- package/src/web/kagi.ts +91 -34
- package/src/web/parallel.ts +11 -2
- package/src/web/scrapers/crates-io.ts +2 -2
- package/src/web/scrapers/discogs.ts +2 -2
- package/src/web/scrapers/docs-rs.ts +2 -2
- package/src/web/scrapers/github.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/pubmed.ts +2 -2
- package/src/web/scrapers/sec-edgar.ts +2 -2
- package/src/web/search/providers/brave.ts +121 -46
- package/src/web/search/providers/codex.ts +88 -12
- package/src/web/search/providers/exa.ts +45 -10
- package/src/web/search/providers/firecrawl.ts +53 -11
- package/src/web/search/providers/gemini.ts +139 -27
- package/src/web/search/providers/jina.ts +48 -25
- package/src/web/search/providers/parallel.ts +23 -9
- package/src/web/search/providers/perplexity.ts +24 -7
- package/src/web/search/providers/searxng.ts +77 -1
- package/src/web/search/providers/tavily.ts +23 -22
- package/src/web/search/providers/tinyfish.ts +44 -10
- package/src/web/search/providers/xai.ts +85 -14
package/src/cursor.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
ToolResultMessage,
|
|
19
19
|
} from "@oh-my-pi/pi-ai";
|
|
20
20
|
import {
|
|
21
|
+
omitUndefinedArgs,
|
|
21
22
|
piEscapeRegexLiteral,
|
|
22
23
|
piGrepSkip,
|
|
23
24
|
piJoinPath,
|
|
@@ -239,7 +240,11 @@ async function executeTool(
|
|
|
239
240
|
return createToolResultMessage(toolCallId, toolName, result, true);
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
|
|
243
|
+
// Same rule as synthesizeCursorExecToolCall: optional kwargs must be absent,
|
|
244
|
+
// not `undefined`, or ArkType validation rejects the call.
|
|
245
|
+
const toolArgs = omitUndefinedArgs(args);
|
|
246
|
+
|
|
247
|
+
options.emitEvent?.({ type: "tool_execution_start", toolCallId, toolName, args: toolArgs });
|
|
243
248
|
|
|
244
249
|
let result: AgentToolResult<unknown>;
|
|
245
250
|
let isError = false;
|
|
@@ -254,7 +259,7 @@ async function executeTool(
|
|
|
254
259
|
type: "tool_execution_update",
|
|
255
260
|
toolCallId,
|
|
256
261
|
toolName,
|
|
257
|
-
args,
|
|
262
|
+
args: toolArgs,
|
|
258
263
|
partialResult: sanitizedResult,
|
|
259
264
|
});
|
|
260
265
|
}
|
|
@@ -263,7 +268,7 @@ async function executeTool(
|
|
|
263
268
|
try {
|
|
264
269
|
result = await tool.execute(
|
|
265
270
|
toolCallId,
|
|
266
|
-
|
|
271
|
+
toolArgs as Record<string, unknown>,
|
|
267
272
|
undefined,
|
|
268
273
|
onUpdate,
|
|
269
274
|
options.getToolContext?.(),
|
|
@@ -509,11 +514,11 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
509
514
|
}
|
|
510
515
|
|
|
511
516
|
const timeoutSeconds = args.timeout && args.timeout > 0 ? args.timeout : undefined;
|
|
512
|
-
const toolArgs
|
|
517
|
+
const toolArgs = omitUndefinedArgs({
|
|
513
518
|
command: args.command,
|
|
514
519
|
cwd: args.workingDirectory || undefined,
|
|
515
520
|
timeout: timeoutSeconds,
|
|
516
|
-
};
|
|
521
|
+
});
|
|
517
522
|
|
|
518
523
|
this.options.emitEvent?.({ type: "tool_execution_start", toolCallId, toolName, args: toolArgs });
|
|
519
524
|
|
|
@@ -15,43 +15,81 @@ import { calculateDepth, createSourceMeta } from "./helpers";
|
|
|
15
15
|
const PROVIDER_ID = "agents-md";
|
|
16
16
|
const DISPLAY_NAME = "AGENTS.md";
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Compare paths while tolerating Windows drive casing.
|
|
20
|
+
*/
|
|
21
|
+
function samePath(left: string, right: string): boolean {
|
|
22
|
+
const normalizedLeft = path.resolve(left);
|
|
23
|
+
const normalizedRight = path.resolve(right);
|
|
24
|
+
return process.platform === "win32"
|
|
25
|
+
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
|
|
26
|
+
: normalizedLeft === normalizedRight;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return whether `child` is at or below `parent`.
|
|
31
|
+
*/
|
|
32
|
+
function isWithin(parent: string, child: string): boolean {
|
|
33
|
+
const normalizedParent = path.resolve(parent);
|
|
34
|
+
const normalizedChild = path.resolve(child);
|
|
35
|
+
const relative = path.relative(
|
|
36
|
+
process.platform === "win32" ? normalizedParent.toLowerCase() : normalizedParent,
|
|
37
|
+
process.platform === "win32" ? normalizedChild.toLowerCase() : normalizedChild,
|
|
38
|
+
);
|
|
39
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
/**
|
|
19
43
|
* Load standalone AGENTS.md files.
|
|
44
|
+
*
|
|
45
|
+
* When a repository is nested below the user's home directory, continue past
|
|
46
|
+
* the Git root to discover workspace-level AGENTS.md files, but stop before
|
|
47
|
+
* loading the home directory's own AGENTS.md as project context.
|
|
20
48
|
*/
|
|
21
|
-
async function loadAgentsMd(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
|
|
49
|
+
export async function loadAgentsMd(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
|
|
22
50
|
const items: ContextFile[] = [];
|
|
23
51
|
const warnings: string[] = [];
|
|
52
|
+
const home = path.resolve(ctx.home);
|
|
53
|
+
const cwd = path.resolve(ctx.cwd);
|
|
54
|
+
const repoRoot = ctx.repoRoot ? path.resolve(ctx.repoRoot) : null;
|
|
55
|
+
const filesystemRoot = path.parse(cwd).root;
|
|
56
|
+
const cwdIsUnderHome = isWithin(home, cwd);
|
|
57
|
+
const repoIsUnderHome = repoRoot !== null && isWithin(home, repoRoot);
|
|
58
|
+
const scanToHome = repoRoot !== null && cwdIsUnderHome && repoIsUnderHome;
|
|
59
|
+
const boundary = scanToHome ? home : (repoRoot ?? (cwdIsUnderHome ? home : filesystemRoot));
|
|
60
|
+
const includeBoundary = repoRoot === null ? cwdIsUnderHome : !samePath(boundary, home);
|
|
61
|
+
const excludeHome = scanToHome;
|
|
24
62
|
|
|
25
|
-
|
|
26
|
-
let current = ctx.cwd;
|
|
27
|
-
|
|
63
|
+
let current = cwd;
|
|
28
64
|
while (true) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
65
|
+
const atBoundary = samePath(current, boundary);
|
|
66
|
+
const atHome = excludeHome && samePath(current, home);
|
|
67
|
+
if (!(atHome || (atBoundary && !includeBoundary))) {
|
|
68
|
+
const candidate = path.join(current, "AGENTS.md");
|
|
69
|
+
const content = await readFile(candidate);
|
|
31
70
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
71
|
+
if (content !== null) {
|
|
72
|
+
const parent = path.dirname(candidate);
|
|
73
|
+
const baseName = parent.split(path.sep).pop() ?? "";
|
|
35
74
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
75
|
+
if (!baseName.startsWith(".")) {
|
|
76
|
+
const fileDir = path.dirname(candidate);
|
|
77
|
+
const calculatedDepth = calculateDepth(cwd, fileDir, path.sep);
|
|
39
78
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
79
|
+
items.push({
|
|
80
|
+
path: candidate,
|
|
81
|
+
content,
|
|
82
|
+
level: "project",
|
|
83
|
+
depth: calculatedDepth,
|
|
84
|
+
_source: createSourceMeta(PROVIDER_ID, candidate, "project"),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
47
87
|
}
|
|
48
88
|
}
|
|
89
|
+
if (atBoundary) break;
|
|
49
90
|
|
|
50
|
-
if (current === (ctx.repoRoot ?? ctx.home)) break; // scanned repo root or home, stop
|
|
51
|
-
|
|
52
|
-
// Move to parent directory
|
|
53
91
|
const parent = path.dirname(current);
|
|
54
|
-
if (parent === current) break;
|
|
92
|
+
if (parent === current) break;
|
|
55
93
|
current = parent;
|
|
56
94
|
}
|
|
57
95
|
|
package/src/eval/jl/kernel.ts
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* Ruby runners via BaseKernel; this module supplies the Julia binary, runner
|
|
6
6
|
* script, and the runner's TSV/Base64 wire protocol.
|
|
7
7
|
*/
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import * as os from "node:os";
|
|
10
8
|
import * as path from "node:path";
|
|
11
9
|
import { $flag, Snowflake } from "@oh-my-pi/pi-utils";
|
|
12
10
|
import { $ } from "bun";
|
|
@@ -14,6 +12,7 @@ import { Settings } from "../../config/settings";
|
|
|
14
12
|
import { BaseKernel, getRemainingTimeMs, type KernelStartOptions } from "../kernel-base";
|
|
15
13
|
import type { KernelDisplayOutput } from "../py/display";
|
|
16
14
|
import { hostHasInheritableConsole, shouldDetachKernel, shouldHideKernelWindow } from "../py/spawn-options";
|
|
15
|
+
import { stageRunnerScript } from "../runner-cache";
|
|
17
16
|
import { JULIA_PRELUDE } from "./prelude";
|
|
18
17
|
import RUNNER_SCRIPT from "./runner.jl" with { type: "text" };
|
|
19
18
|
import {
|
|
@@ -30,23 +29,6 @@ export type { KernelDisplayOutput };
|
|
|
30
29
|
|
|
31
30
|
const TRACE_IPC = $flag("PI_JULIA_IPC_TRACE");
|
|
32
31
|
|
|
33
|
-
// Cache the runner script on disk so the subprocess loads it normally. Cached
|
|
34
|
-
// per script hash so installs don't race across versions.
|
|
35
|
-
const RUNNER_CACHE_DIR = path.join(os.tmpdir(), "omp-julia-runner");
|
|
36
|
-
let RUNNER_SCRIPT_PATH: string | null = null;
|
|
37
|
-
|
|
38
|
-
async function ensureRunnerScript(): Promise<string> {
|
|
39
|
-
if (RUNNER_SCRIPT_PATH) return RUNNER_SCRIPT_PATH;
|
|
40
|
-
await fs.promises.mkdir(RUNNER_CACHE_DIR, { recursive: true });
|
|
41
|
-
const hash = Bun.hash(RUNNER_SCRIPT).toString(36);
|
|
42
|
-
const target = path.join(RUNNER_CACHE_DIR, `runner-${hash}.jl`);
|
|
43
|
-
if (!fs.existsSync(target)) {
|
|
44
|
-
await Bun.write(target, RUNNER_SCRIPT);
|
|
45
|
-
}
|
|
46
|
-
RUNNER_SCRIPT_PATH = target;
|
|
47
|
-
return target;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
32
|
const SHUTDOWN_GRACE_MS = 1_000;
|
|
51
33
|
const STARTUP_TIMEOUT_MS = 15_000; // Julia compile/warmup can be slightly slower
|
|
52
34
|
const INTERRUPT_ESCALATION_MS = 5_000;
|
|
@@ -180,7 +162,7 @@ export class JuliaKernel extends BaseKernel<KernelExecuteOptions> {
|
|
|
180
162
|
if (typeof value === "string") spawnEnv[key] = value;
|
|
181
163
|
}
|
|
182
164
|
|
|
183
|
-
const scriptPath = await
|
|
165
|
+
const scriptPath = await stageRunnerScript("omp-julia-runner", "jl", RUNNER_SCRIPT);
|
|
184
166
|
const kernel = new JuliaKernel(Snowflake.next());
|
|
185
167
|
|
|
186
168
|
const proc = Bun.spawn(
|
package/src/eval/py/kernel.ts
CHANGED
|
@@ -7,13 +7,12 @@
|
|
|
7
7
|
* code. Shutdown writes `{"type":"exit"}` and escalates to SIGTERM/SIGKILL on
|
|
8
8
|
* timeout.
|
|
9
9
|
*/
|
|
10
|
-
import * as fs from "node:fs";
|
|
11
|
-
import * as os from "node:os";
|
|
12
10
|
import * as path from "node:path";
|
|
13
11
|
import { $flag, isBunTestRuntime, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
14
12
|
import { $ } from "bun";
|
|
15
13
|
import { Settings } from "../../config/settings";
|
|
16
14
|
import { BaseKernel, getRemainingTimeMs, type KernelStartOptions } from "../kernel-base";
|
|
15
|
+
import { stageRunnerScript } from "../runner-cache";
|
|
17
16
|
import { PYTHON_PRELUDE } from "./prelude";
|
|
18
17
|
import RUNNER_SCRIPT from "./runner.py" with { type: "text" };
|
|
19
18
|
import {
|
|
@@ -38,23 +37,6 @@ export { renderKernelDisplay } from "./display";
|
|
|
38
37
|
|
|
39
38
|
const TRACE_IPC = $flag("PI_PYTHON_IPC_TRACE");
|
|
40
39
|
|
|
41
|
-
// Cache the runner script on disk so the subprocess loads it normally. Cached
|
|
42
|
-
// per script hash so installs don't race across versions.
|
|
43
|
-
const RUNNER_CACHE_DIR = path.join(os.tmpdir(), "omp-python-runner");
|
|
44
|
-
let RUNNER_SCRIPT_PATH: string | null = null;
|
|
45
|
-
|
|
46
|
-
async function ensureRunnerScript(): Promise<string> {
|
|
47
|
-
if (RUNNER_SCRIPT_PATH) return RUNNER_SCRIPT_PATH;
|
|
48
|
-
await fs.promises.mkdir(RUNNER_CACHE_DIR, { recursive: true });
|
|
49
|
-
const hash = Bun.hash(RUNNER_SCRIPT).toString(36);
|
|
50
|
-
const target = path.join(RUNNER_CACHE_DIR, `runner-${hash}.py`);
|
|
51
|
-
if (!fs.existsSync(target)) {
|
|
52
|
-
await Bun.write(target, RUNNER_SCRIPT);
|
|
53
|
-
}
|
|
54
|
-
RUNNER_SCRIPT_PATH = target;
|
|
55
|
-
return target;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
40
|
const SHUTDOWN_GRACE_MS = 1_000;
|
|
59
41
|
const STARTUP_TIMEOUT_MS = 10_000;
|
|
60
42
|
// How long to wait after SIGINT for the runner to emit `done`. If the cell is
|
|
@@ -189,7 +171,7 @@ export class PythonKernel extends BaseKernel {
|
|
|
189
171
|
spawnEnv.PYTHONUNBUFFERED = "1";
|
|
190
172
|
spawnEnv.PYTHONIOENCODING = "utf-8";
|
|
191
173
|
|
|
192
|
-
const scriptPath = await
|
|
174
|
+
const scriptPath = await stageRunnerScript("omp-python-runner", "py", RUNNER_SCRIPT);
|
|
193
175
|
const kernel = new PythonKernel(Snowflake.next());
|
|
194
176
|
|
|
195
177
|
const proc = Bun.spawn([runtime.pythonPath, "-u", scriptPath], {
|
package/src/eval/rb/kernel.ts
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* (eval/py/kernel.ts); the IPC loop, lifecycle, and display rendering are shared
|
|
9
9
|
* with it via BaseKernel.
|
|
10
10
|
*/
|
|
11
|
-
import * as fs from "node:fs";
|
|
12
|
-
import * as os from "node:os";
|
|
13
11
|
import * as path from "node:path";
|
|
14
12
|
import { $flag, isBunTestRuntime, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
15
13
|
import { $ } from "bun";
|
|
@@ -17,6 +15,7 @@ import { Settings } from "../../config/settings";
|
|
|
17
15
|
import { BaseKernel, getRemainingTimeMs, type KernelRuntimeEnv, type KernelStartOptions } from "../kernel-base";
|
|
18
16
|
import type { KernelDisplayOutput } from "../py/display";
|
|
19
17
|
import { hostHasInheritableConsole, shouldDetachKernel, shouldHideKernelWindow } from "../py/spawn-options";
|
|
18
|
+
import { stageRunnerScript } from "../runner-cache";
|
|
20
19
|
import { RUBY_PRELUDE } from "./prelude";
|
|
21
20
|
import RUNNER_SCRIPT from "./runner.rb" with { type: "text" };
|
|
22
21
|
import {
|
|
@@ -33,23 +32,6 @@ export { renderKernelDisplay } from "../py/display";
|
|
|
33
32
|
|
|
34
33
|
const TRACE_IPC = $flag("PI_RUBY_IPC_TRACE");
|
|
35
34
|
|
|
36
|
-
// Cache the runner script on disk so the subprocess loads it normally. Cached
|
|
37
|
-
// per script hash so installs don't race across versions.
|
|
38
|
-
const RUNNER_CACHE_DIR = path.join(os.tmpdir(), "omp-ruby-runner");
|
|
39
|
-
let RUNNER_SCRIPT_PATH: string | null = null;
|
|
40
|
-
|
|
41
|
-
async function ensureRunnerScript(): Promise<string> {
|
|
42
|
-
if (RUNNER_SCRIPT_PATH) return RUNNER_SCRIPT_PATH;
|
|
43
|
-
await fs.promises.mkdir(RUNNER_CACHE_DIR, { recursive: true });
|
|
44
|
-
const hash = Bun.hash(RUNNER_SCRIPT).toString(36);
|
|
45
|
-
const target = path.join(RUNNER_CACHE_DIR, `runner-${hash}.rb`);
|
|
46
|
-
if (!fs.existsSync(target)) {
|
|
47
|
-
await Bun.write(target, RUNNER_SCRIPT);
|
|
48
|
-
}
|
|
49
|
-
RUNNER_SCRIPT_PATH = target;
|
|
50
|
-
return target;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
35
|
const SHUTDOWN_GRACE_MS = 1_000;
|
|
54
36
|
const STARTUP_TIMEOUT_MS = 10_000;
|
|
55
37
|
// How long to wait after SIGINT for the runner to emit `done` before escalating
|
|
@@ -181,7 +163,7 @@ export class RubyKernel extends BaseKernel<KernelExecuteOptions> {
|
|
|
181
163
|
if (typeof value === "string") spawnEnv[key] = value;
|
|
182
164
|
}
|
|
183
165
|
|
|
184
|
-
const scriptPath = await
|
|
166
|
+
const scriptPath = await stageRunnerScript("omp-ruby-runner", "rb", RUNNER_SCRIPT);
|
|
185
167
|
const kernel = new RubyKernel(Snowflake.next());
|
|
186
168
|
|
|
187
169
|
const proc = Bun.spawn([runtime.rubyPath, scriptPath], {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared on-disk staging for subprocess kernel runner scripts.
|
|
3
|
+
*
|
|
4
|
+
* Each language kernel (Python/Julia/Ruby) ships its runner as a compiled-in
|
|
5
|
+
* text asset, then stages it under `os.tmpdir()` so the interpreter can load it
|
|
6
|
+
* as a normal file. Staging is cached per language directory so repeated kernel
|
|
7
|
+
* starts within a process avoid redundant writes.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
|
|
13
|
+
// Memoized staged path per cache directory. The value is re-validated on every
|
|
14
|
+
// call: a tmpdir sweep (e.g. macOS `periodic daily clean_tmps`) or any external
|
|
15
|
+
// clear must self-heal within a long-lived process, not only across restarts.
|
|
16
|
+
const stagedPaths = new Map<string, string>();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Stage `script` under `os.tmpdir()/<dirName>` and return the runner path.
|
|
20
|
+
*
|
|
21
|
+
* The staged path is memoized per `dirName` but re-checked with `fs.existsSync`
|
|
22
|
+
* before reuse, so a runner deleted mid-session is re-written on the next call
|
|
23
|
+
* instead of handing back a path to a missing file (issue #8140).
|
|
24
|
+
*
|
|
25
|
+
* @param dirName Cache subdirectory under the OS temp dir (unique per language).
|
|
26
|
+
* @param ext Runner file extension without the dot (e.g. `py`, `jl`, `rb`).
|
|
27
|
+
* @param script Runner source, hashed to key the cached file per version.
|
|
28
|
+
*/
|
|
29
|
+
export async function stageRunnerScript(dirName: string, ext: string, script: string): Promise<string> {
|
|
30
|
+
const memoized = stagedPaths.get(dirName);
|
|
31
|
+
if (memoized && fs.existsSync(memoized)) return memoized;
|
|
32
|
+
const dir = path.join(os.tmpdir(), dirName);
|
|
33
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
34
|
+
const hash = Bun.hash(script).toString(36);
|
|
35
|
+
const target = path.join(dir, `runner-${hash}.${ext}`);
|
|
36
|
+
if (!fs.existsSync(target)) {
|
|
37
|
+
await Bun.write(target, script);
|
|
38
|
+
}
|
|
39
|
+
stagedPaths.set(dirName, target);
|
|
40
|
+
return target;
|
|
41
|
+
}
|
|
@@ -23,7 +23,7 @@ export const NON_INTERACTIVE_ENV: Readonly<Record<string, string>> = {
|
|
|
23
23
|
EDITOR: "true",
|
|
24
24
|
GIT_TERMINAL_PROMPT: "0",
|
|
25
25
|
SSH_ASKPASS: "/usr/bin/false",
|
|
26
|
-
CI: "
|
|
26
|
+
CI: "true",
|
|
27
27
|
AGENT: "1",
|
|
28
28
|
// Package manager defaults for unattended execution.
|
|
29
29
|
npm_config_yes: "true",
|
|
@@ -96,17 +96,28 @@ function hasEnvGroupValue(
|
|
|
96
96
|
return false;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Copy of the base env with `CI` removed, for the `PI_BASH_NO_CI` opt-out. */
|
|
100
|
+
function withoutCI(env: Readonly<Record<string, string>>): Record<string, string> {
|
|
101
|
+
const { CI: _ci, ...rest } = env;
|
|
102
|
+
return rest;
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
/** Builds the per-command environment for non-interactive child processes. */
|
|
100
106
|
export function buildNonInteractiveEnv(
|
|
101
107
|
overrides?: Record<string, string>,
|
|
102
108
|
baseEnv: Record<string, string | undefined> = Bun.env,
|
|
103
109
|
platform: NodeJS.Platform = process.platform,
|
|
104
110
|
): Record<string, string> {
|
|
111
|
+
// `PI_BASH_NO_CI` (and its legacy alias) opts out of the automatic `CI=true`
|
|
112
|
+
// injection. Mirrors the session-env gate in `procmgr.ts` so the opt-out
|
|
113
|
+
// reaches the per-command env, which otherwise overrides the session value.
|
|
114
|
+
const base =
|
|
115
|
+
baseEnv.PI_BASH_NO_CI || baseEnv.CLAUDE_BASH_NO_CI ? withoutCI(NON_INTERACTIVE_ENV) : NON_INTERACTIVE_ENV;
|
|
105
116
|
if (platform !== "win32") {
|
|
106
|
-
return overrides ? { ...
|
|
117
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
const env: Record<string, string> = { ...
|
|
120
|
+
const env: Record<string, string> = { ...base };
|
|
110
121
|
for (const group of WINDOWS_UTF8_ENV_DEFAULT_GROUPS) {
|
|
111
122
|
if (hasEnvGroupValue(baseEnv, group, platform) || hasEnvGroupValue(overrides, group, platform)) {
|
|
112
123
|
continue;
|
|
@@ -175,10 +175,12 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
registerTool<TParams extends TSchema = TSchema, TDetails = unknown>(tool: ToolDefinition<TParams, TDetails>): void {
|
|
178
|
-
|
|
178
|
+
const registered = {
|
|
179
179
|
definition: tool,
|
|
180
180
|
extensionPath: this.extension.path,
|
|
181
|
-
}
|
|
181
|
+
};
|
|
182
|
+
this.extension.tools.set(tool.name, registered);
|
|
183
|
+
for (const listener of this.extension.toolRegistrationListeners ?? []) listener(tool.name);
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
registerCommand(
|
|
@@ -316,6 +318,7 @@ function createExtension(extensionPath: string, resolvedPath: string): Extension
|
|
|
316
318
|
resolvedPath,
|
|
317
319
|
handlers: new Map(),
|
|
318
320
|
tools: new Map(),
|
|
321
|
+
toolRegistrationListeners: new Set(),
|
|
319
322
|
assistantThinkingRenderers: [],
|
|
320
323
|
messageRenderers: new Map(),
|
|
321
324
|
commands: new Map(),
|