@oh-my-pi/pi-coding-agent 15.10.3 → 15.10.5
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 +72 -0
- package/dist/types/capability/rule-buckets.d.ts +1 -1
- package/dist/types/capability/rule.d.ts +6 -1
- package/dist/types/cli/update-cli.d.ts +11 -1
- package/dist/types/config/model-registry.d.ts +18 -1
- package/dist/types/discovery/at-imports.d.ts +15 -0
- package/dist/types/edit/diff.d.ts +3 -2
- package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
- package/dist/types/eval/__tests__/js-context-manager.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +7 -0
- package/dist/types/eval/bridge-timeout.d.ts +1 -1
- package/dist/types/eval/{llm-bridge.d.ts → completion-bridge.d.ts} +8 -8
- package/dist/types/eval/idle-timeout.d.ts +1 -1
- package/dist/types/eval/js/context-manager.d.ts +1 -0
- package/dist/types/eval/js/executor.d.ts +2 -0
- package/dist/types/eval/js/index.d.ts +1 -1
- package/dist/types/eval/js/shared/helpers.d.ts +6 -0
- package/dist/types/eval/js/shared/runtime.d.ts +5 -0
- package/dist/types/eval/js/worker-protocol.d.ts +6 -0
- package/dist/types/eval/py/executor.d.ts +7 -0
- package/dist/types/eval/py/index.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -1
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
- package/dist/types/internal-urls/local-protocol.d.ts +10 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -2
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +31 -2
- package/dist/types/modes/image-references.d.ts +8 -3
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +0 -2
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +15 -0
- package/dist/types/tools/index.d.ts +17 -0
- package/dist/types/tools/render-utils.d.ts +1 -1
- package/dist/types/tools/tool-timeouts.d.ts +1 -1
- package/dist/types/utils/block-context.d.ts +35 -0
- package/dist/types/utils/image-loading.d.ts +12 -0
- package/package.json +29 -9
- package/src/capability/rule-buckets.ts +4 -2
- package/src/capability/rule.ts +10 -1
- package/src/cli/auth-broker-cli.ts +6 -7
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/list-models.ts +5 -0
- package/src/cli/update-cli.ts +138 -16
- package/src/config/model-registry.ts +81 -2
- package/src/debug/index.ts +4 -8
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-rules/index.ts +4 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/helpers.ts +2 -1
- package/src/edit/diff.ts +114 -4
- package/src/edit/hashline/diff.ts +1 -1
- package/src/edit/hashline/execute.ts +1 -1
- package/src/edit/modes/patch.ts +6 -2
- package/src/edit/modes/replace.ts +1 -1
- package/src/edit/renderer.ts +12 -2
- package/src/eval/__tests__/agent-bridge.test.ts +13 -0
- package/src/eval/__tests__/{llm-bridge.test.ts → completion-bridge.test.ts} +60 -54
- package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
- package/src/eval/__tests__/js-context-manager.test.ts +241 -0
- package/src/eval/agent-bridge.ts +6 -1
- package/src/eval/backend.ts +15 -0
- package/src/eval/bridge-timeout.ts +1 -1
- package/src/eval/{llm-bridge.ts → completion-bridge.ts} +30 -27
- package/src/eval/idle-timeout.ts +1 -1
- package/src/eval/js/context-manager.ts +70 -8
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/index.ts +7 -1
- package/src/eval/js/shared/helpers.ts +53 -6
- package/src/eval/js/shared/prelude.txt +4 -4
- package/src/eval/js/shared/runtime.ts +8 -0
- package/src/eval/js/tool-bridge.ts +3 -3
- package/src/eval/js/worker-core.ts +1 -0
- package/src/eval/js/worker-entry.ts +6 -0
- package/src/eval/js/worker-protocol.ts +6 -0
- package/src/eval/py/executor.ts +12 -0
- package/src/eval/py/index.ts +7 -1
- package/src/eval/py/prelude.py +46 -7
- package/src/eval/py/runner.py +1 -0
- package/src/exa/render.ts +1 -1
- package/src/export/ttsr.ts +122 -1
- package/src/extensibility/extensions/types.ts +8 -1
- package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +8 -6
- package/src/internal-urls/local-protocol.ts +13 -0
- package/src/lsp/render.ts +8 -6
- package/src/mcp/oauth-flow.ts +3 -3
- package/src/mcp/render.ts +7 -1
- package/src/modes/components/custom-editor.ts +12 -6
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/oauth-selector.ts +4 -4
- package/src/modes/components/read-tool-group.ts +10 -3
- package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/types.ts +23 -8
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/transcript-container.ts +17 -10
- package/src/modes/components/user-message.ts +6 -3
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +143 -127
- package/src/modes/controllers/input-controller.ts +36 -10
- package/src/modes/controllers/mcp-command-controller.ts +28 -12
- package/src/modes/controllers/selector-controller.ts +4 -11
- package/src/modes/controllers/ssh-command-controller.ts +2 -2
- package/src/modes/image-references.ts +13 -7
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
- package/src/modes/theme/theme.ts +95 -1
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +14 -5
- package/src/prompts/system/tiny-title-system.md +1 -1
- package/src/prompts/system/title-system.md +16 -3
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/eval.md +6 -6
- package/src/sdk.ts +31 -14
- package/src/session/agent-session.ts +213 -155
- package/src/session/session-manager.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/system-prompt.ts +15 -9
- package/src/task/render.ts +20 -8
- package/src/tools/ask.ts +14 -5
- package/src/tools/bash-interactive.ts +1 -1
- package/src/tools/bash.ts +14 -2
- package/src/tools/browser/render.ts +5 -2
- package/src/tools/browser/tab-worker.ts +211 -91
- package/src/tools/debug.ts +5 -2
- package/src/tools/eval-render.ts +8 -5
- package/src/tools/eval.ts +2 -2
- package/src/tools/gh-renderer.ts +29 -15
- package/src/tools/index.ts +32 -0
- package/src/tools/inspect-image-renderer.ts +12 -5
- package/src/tools/job.ts +9 -6
- package/src/tools/memory-render.ts +19 -5
- package/src/tools/read.ts +165 -18
- package/src/tools/render-utils.ts +3 -1
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/ssh.ts +4 -1
- package/src/tools/todo.ts +8 -1
- package/src/tools/tool-timeouts.ts +1 -1
- package/src/tools/write.ts +1 -1
- package/src/tui/code-cell.ts +1 -1
- package/src/utils/block-context.ts +312 -0
- package/src/utils/image-loading.ts +31 -1
- package/src/utils/title-generator.ts +2 -2
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/render.ts +14 -6
- /package/dist/types/eval/__tests__/{llm-bridge.test.d.ts → completion-bridge.test.d.ts} +0 -0
|
@@ -24,6 +24,12 @@ export interface HelperOptions {
|
|
|
24
24
|
export interface HelperContext {
|
|
25
25
|
cwd(): string;
|
|
26
26
|
env: Map<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* On-disk roots for internal-URL schemes the helpers accept (e.g.
|
|
29
|
+
* `{ local: "/…/artifacts/local" }`). A path like `local://x.md` is rewritten
|
|
30
|
+
* to `<root>/x.md` before any filesystem op; unknown schemes are rejected.
|
|
31
|
+
*/
|
|
32
|
+
localRoots(): Record<string, string>;
|
|
27
33
|
emitStatus(event: JsStatusEvent): void;
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -66,7 +72,7 @@ export function createHelpers(ctx: HelperContext): HelperBundle {
|
|
|
66
72
|
if (!isWriteData(data)) {
|
|
67
73
|
throw new ToolError("write() expects string, Blob, ArrayBuffer, or TypedArray data");
|
|
68
74
|
}
|
|
69
|
-
const filePath =
|
|
75
|
+
const filePath = resolveHelperPath(ctx, rawPath, "write");
|
|
70
76
|
if (typeof data === "string" || data instanceof Blob || data instanceof ArrayBuffer) {
|
|
71
77
|
await Bun.write(filePath, data);
|
|
72
78
|
} else {
|
|
@@ -76,7 +82,7 @@ export function createHelpers(ctx: HelperContext): HelperBundle {
|
|
|
76
82
|
return filePath;
|
|
77
83
|
},
|
|
78
84
|
append: async (rawPath, content) => {
|
|
79
|
-
const target =
|
|
85
|
+
const target = resolveHelperPath(ctx, rawPath, "write");
|
|
80
86
|
await Bun.write(
|
|
81
87
|
target,
|
|
82
88
|
`${await Bun.file(target)
|
|
@@ -202,19 +208,60 @@ function getMergedEnv(ctx: HelperContext): Record<string, string> {
|
|
|
202
208
|
return merged;
|
|
203
209
|
}
|
|
204
210
|
|
|
211
|
+
const INTERNAL_URL_RE = /^([a-z][a-z0-9+.-]*):\/\/(.*)$/i;
|
|
212
|
+
|
|
205
213
|
function resolvePath(ctx: HelperContext, value: string): string {
|
|
206
214
|
if (path.isAbsolute(value)) return path.normalize(value);
|
|
207
215
|
return path.resolve(ctx.cwd(), value);
|
|
208
216
|
}
|
|
209
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Map a raw helper path to an absolute filesystem path. Plain paths resolve
|
|
220
|
+
* against the cwd; an internal-URL whose scheme has an injected root (e.g.
|
|
221
|
+
* `local://`) is rewritten under that root; any other `scheme://` is rejected
|
|
222
|
+
* so we never silently create a literal `scheme:/` directory.
|
|
223
|
+
*/
|
|
224
|
+
function resolveHelperPath(ctx: HelperContext, rawPath: string, op: "read" | "write"): string {
|
|
225
|
+
const match = INTERNAL_URL_RE.exec(rawPath);
|
|
226
|
+
if (!match) return resolvePath(ctx, rawPath);
|
|
227
|
+
const scheme = match[1].toLowerCase();
|
|
228
|
+
const root = ctx.localRoots()[scheme];
|
|
229
|
+
if (!root) {
|
|
230
|
+
throw new ToolError(`Protocol paths are not supported by ${op}(): ${rawPath}`);
|
|
231
|
+
}
|
|
232
|
+
return resolveUnderRoot(scheme, root, match[2], rawPath);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Resolve an internal-URL relative path under its root, mirroring the host
|
|
236
|
+
* local-protocol handler: decode, reject absolute/traversal, confine to root. */
|
|
237
|
+
function resolveUnderRoot(scheme: string, root: string, rawRelative: string, rawPath: string): string {
|
|
238
|
+
let relative: string;
|
|
239
|
+
try {
|
|
240
|
+
relative = decodeURIComponent(rawRelative.replaceAll("\\", "/"));
|
|
241
|
+
} catch {
|
|
242
|
+
throw new ToolError(`Invalid URL encoding in ${scheme}:// path: ${rawPath}`);
|
|
243
|
+
}
|
|
244
|
+
const rootPath = path.resolve(root);
|
|
245
|
+
if (relative === "") return rootPath;
|
|
246
|
+
if (path.isAbsolute(relative)) {
|
|
247
|
+
throw new ToolError(`Absolute paths are not allowed in ${scheme}:// URLs: ${rawPath}`);
|
|
248
|
+
}
|
|
249
|
+
const normalized = path.normalize(relative);
|
|
250
|
+
if (normalized.startsWith("..") || normalized.includes("/../") || normalized.includes("/..")) {
|
|
251
|
+
throw new ToolError(`Path traversal (..) is not allowed in ${scheme}:// URLs: ${rawPath}`);
|
|
252
|
+
}
|
|
253
|
+
const resolved = path.resolve(rootPath, normalized);
|
|
254
|
+
if (resolved !== rootPath && !resolved.startsWith(`${rootPath}${path.sep}`)) {
|
|
255
|
+
throw new ToolError(`${scheme}:// path escapes its root: ${rawPath}`);
|
|
256
|
+
}
|
|
257
|
+
return resolved;
|
|
258
|
+
}
|
|
259
|
+
|
|
210
260
|
async function resolveRegularFile(
|
|
211
261
|
ctx: HelperContext,
|
|
212
262
|
rawPath: string,
|
|
213
263
|
): Promise<{ filePath: string; file: Bun.BunFile; size: number }> {
|
|
214
|
-
|
|
215
|
-
throw new ToolError(`Protocol paths are not supported by read(): ${rawPath}`);
|
|
216
|
-
}
|
|
217
|
-
const filePath = resolvePath(ctx, rawPath);
|
|
264
|
+
const filePath = resolveHelperPath(ctx, rawPath, "read");
|
|
218
265
|
const file = Bun.file(filePath);
|
|
219
266
|
const stat = await file.stat();
|
|
220
267
|
if (stat.isDirectory()) {
|
|
@@ -57,9 +57,9 @@ if (!globalThis.__omp_js_prelude_loaded__) {
|
|
|
57
57
|
|
|
58
58
|
const hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
59
59
|
|
|
60
|
-
const
|
|
61
|
-
const o = optionsArg("
|
|
62
|
-
const res = await globalThis.__omp_call_tool__("
|
|
60
|
+
const completion = async (prompt, opts, ...rest) => {
|
|
61
|
+
const o = optionsArg("completion", opts, rest, "{ model, system, schema }");
|
|
62
|
+
const res = await globalThis.__omp_call_tool__("__completion__", { prompt, ...o });
|
|
63
63
|
const text = res && typeof res === "object" ? res.text : res;
|
|
64
64
|
return hasOwn(o, "schema") ? JSON.parse(text) : text;
|
|
65
65
|
};
|
|
@@ -164,7 +164,7 @@ if (!globalThis.__omp_js_prelude_loaded__) {
|
|
|
164
164
|
globalThis.print = consoleBridge.log;
|
|
165
165
|
globalThis.display = display;
|
|
166
166
|
globalThis.tool = tool;
|
|
167
|
-
globalThis.
|
|
167
|
+
globalThis.completion = completion;
|
|
168
168
|
globalThis.output = output;
|
|
169
169
|
globalThis.agent = agent;
|
|
170
170
|
globalThis.parallel = parallel;
|
|
@@ -42,6 +42,11 @@ export interface RuntimeOptions {
|
|
|
42
42
|
* via `setRunScope()` instead.
|
|
43
43
|
*/
|
|
44
44
|
extraGlobals?: Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* On-disk roots the helpers substitute for internal-URL schemes (e.g.
|
|
47
|
+
* `{ local: "/…/artifacts/local" }`). Stable for the worker's lifetime.
|
|
48
|
+
*/
|
|
49
|
+
localRoots?: Record<string, string>;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
// Strict base64: characters from the standard alphabet plus optional `=` padding, and a
|
|
@@ -126,15 +131,18 @@ export class JsRuntime {
|
|
|
126
131
|
#env: Map<string, string>;
|
|
127
132
|
#als = new AsyncLocalStorage<RunContext>();
|
|
128
133
|
#moduleLoader: LocalModuleLoader;
|
|
134
|
+
#localRoots: Record<string, string>;
|
|
129
135
|
|
|
130
136
|
constructor(opts: RuntimeOptions) {
|
|
131
137
|
this.#cwd = opts.initialCwd;
|
|
132
138
|
this.sessionId = opts.sessionId;
|
|
133
139
|
this.#env = new Map();
|
|
134
140
|
this.#moduleLoader = new LocalModuleLoader(this.sessionId);
|
|
141
|
+
this.#localRoots = opts.localRoots ?? {};
|
|
135
142
|
this.helpers = createHelpers({
|
|
136
143
|
cwd: () => this.#activeCwd(),
|
|
137
144
|
env: this.#env,
|
|
145
|
+
localRoots: () => this.#localRoots,
|
|
138
146
|
emitStatus: event => this.#activeHooks("emitStatus")?.onDisplay({ type: "status", event }),
|
|
139
147
|
});
|
|
140
148
|
this.#install(opts.extraGlobals);
|
|
@@ -3,8 +3,8 @@ import type { ToolSession } from "../../tools";
|
|
|
3
3
|
import { ToolError } from "../../tools/tool-errors";
|
|
4
4
|
import { EVAL_AGENT_BRIDGE_NAME, runEvalAgent } from "../agent-bridge";
|
|
5
5
|
import { EVAL_BUDGET_BRIDGE_NAME, type EvalBudgetResult, runEvalBudget } from "../budget-bridge";
|
|
6
|
+
import { EVAL_COMPLETION_BRIDGE_NAME, runEvalCompletion } from "../completion-bridge";
|
|
6
7
|
import { EVAL_CONCURRENCY_BRIDGE_NAME, type EvalConcurrencyResult, runEvalConcurrency } from "../concurrency-bridge";
|
|
7
|
-
import { EVAL_LLM_BRIDGE_NAME, runEvalLlm } from "../llm-bridge";
|
|
8
8
|
import type { JsStatusEvent } from "./shared/types";
|
|
9
9
|
|
|
10
10
|
export type { JsStatusEvent } from "./shared/types";
|
|
@@ -107,8 +107,8 @@ function summarizeToolResult(
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export async function callSessionTool(name: string, args: unknown, options: ToolBridgeOptions): Promise<ToolValue> {
|
|
110
|
-
if (name ===
|
|
111
|
-
return await
|
|
110
|
+
if (name === EVAL_COMPLETION_BRIDGE_NAME) {
|
|
111
|
+
return await runEvalCompletion(args, options);
|
|
112
112
|
}
|
|
113
113
|
if (name === EVAL_AGENT_BRIDGE_NAME) {
|
|
114
114
|
return await runEvalAgent(args, options);
|
|
@@ -18,6 +18,12 @@ const transport: Transport = {
|
|
|
18
18
|
} catch {
|
|
19
19
|
// Already closed.
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
// `parentPort.close()` only disconnects the channel in Bun; it does not
|
|
23
|
+
// make the Worker emit `close` or reap ref'ed user handles. Exit from
|
|
24
|
+
// inside the worker after `WorkerCore` has sent the `closed` ack so the
|
|
25
|
+
// host can observe real worker exit without calling `Worker.terminate()`.
|
|
26
|
+
setTimeout(() => process.exit(0), 0);
|
|
21
27
|
},
|
|
22
28
|
};
|
|
23
29
|
|
|
@@ -5,6 +5,12 @@ export type { JsDisplayOutput } from "./shared/types";
|
|
|
5
5
|
export interface SessionSnapshot {
|
|
6
6
|
cwd: string;
|
|
7
7
|
sessionId: string;
|
|
8
|
+
/**
|
|
9
|
+
* On-disk roots the helpers substitute for internal-URL schemes
|
|
10
|
+
* (e.g. `{ local: "/…/artifacts/local" }`). Lets `read`/`write`/`append`
|
|
11
|
+
* accept `local://…` paths instead of writing a literal `local:/` directory.
|
|
12
|
+
*/
|
|
13
|
+
localRoots?: Record<string, string>;
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
export interface RunErrorPayload {
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface PythonExecutorOptions {
|
|
|
56
56
|
/** Artifact path/id for full output storage */
|
|
57
57
|
artifactPath?: string;
|
|
58
58
|
artifactId?: string;
|
|
59
|
+
/**
|
|
60
|
+
* On-disk roots the prelude helpers (`read`/`write`/`append`) substitute for
|
|
61
|
+
* internal-URL schemes (e.g. `{ local: "/…/artifacts/local" }`). Exported to
|
|
62
|
+
* the kernel as `PI_EVAL_LOCAL_ROOTS` (JSON) so `write("local://x")` lands
|
|
63
|
+
* where `read local://x` resolves instead of a literal `local:/` directory.
|
|
64
|
+
*/
|
|
65
|
+
localRoots?: Record<string, string>;
|
|
59
66
|
/**
|
|
60
67
|
* ToolSession used to resolve host-side `tool.<name>(args)` calls made from
|
|
61
68
|
* the Python prelude's bridge proxy. When omitted, the bridge env vars are
|
|
@@ -275,6 +282,7 @@ const MANAGED_KERNEL_ENV_KEYS = [
|
|
|
275
282
|
"PI_TOOL_BRIDGE_URL",
|
|
276
283
|
"PI_TOOL_BRIDGE_TOKEN",
|
|
277
284
|
"PI_TOOL_BRIDGE_SESSION",
|
|
285
|
+
"PI_EVAL_LOCAL_ROOTS",
|
|
278
286
|
] as const;
|
|
279
287
|
|
|
280
288
|
function buildKernelEnvPatch(options: {
|
|
@@ -282,13 +290,16 @@ function buildKernelEnvPatch(options: {
|
|
|
282
290
|
artifactsDir?: string;
|
|
283
291
|
bridgeSessionId?: string;
|
|
284
292
|
bridge?: { url: string; token: string };
|
|
293
|
+
localRoots?: Record<string, string>;
|
|
285
294
|
}): KernelRuntimeEnv {
|
|
295
|
+
const localRoots = options.localRoots;
|
|
286
296
|
return {
|
|
287
297
|
PI_SESSION_FILE: options.sessionFile ?? null,
|
|
288
298
|
PI_ARTIFACTS_DIR: options.artifactsDir ?? null,
|
|
289
299
|
PI_TOOL_BRIDGE_URL: options.bridge?.url ?? null,
|
|
290
300
|
PI_TOOL_BRIDGE_TOKEN: options.bridge?.token ?? null,
|
|
291
301
|
PI_TOOL_BRIDGE_SESSION: options.bridge && options.bridgeSessionId ? options.bridgeSessionId : null,
|
|
302
|
+
PI_EVAL_LOCAL_ROOTS: localRoots && Object.keys(localRoots).length > 0 ? JSON.stringify(localRoots) : null,
|
|
292
303
|
};
|
|
293
304
|
}
|
|
294
305
|
|
|
@@ -297,6 +308,7 @@ function buildKernelEnv(options: {
|
|
|
297
308
|
artifactsDir?: string;
|
|
298
309
|
bridgeSessionId?: string;
|
|
299
310
|
bridge?: { url: string; token: string };
|
|
311
|
+
localRoots?: Record<string, string>;
|
|
300
312
|
}): Record<string, string> | undefined {
|
|
301
313
|
const patch = buildKernelEnvPatch(options);
|
|
302
314
|
const env: Record<string, string> = {};
|
package/src/eval/py/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { ToolSession } from "../../tools";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
type ExecutorBackend,
|
|
4
|
+
type ExecutorBackendExecOptions,
|
|
5
|
+
type ExecutorBackendResult,
|
|
6
|
+
resolveEvalUrlRoots,
|
|
7
|
+
} from "../backend";
|
|
3
8
|
import { executePython, type PythonExecutorOptions } from "./executor";
|
|
4
9
|
import { checkPythonKernelAvailability } from "./kernel";
|
|
5
10
|
|
|
@@ -34,6 +39,7 @@ export default {
|
|
|
34
39
|
kernelMode,
|
|
35
40
|
sessionFile: opts.sessionFile,
|
|
36
41
|
artifactsDir: opts.session.getArtifactsDir?.() ?? undefined,
|
|
42
|
+
localRoots: resolveEvalUrlRoots(opts.session),
|
|
37
43
|
kernelOwnerId: opts.kernelOwnerId,
|
|
38
44
|
reset: opts.reset,
|
|
39
45
|
artifactPath: opts.artifactPath,
|
package/src/eval/py/prelude.py
CHANGED
|
@@ -3,7 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
if "__omp_prelude_loaded__" not in globals():
|
|
4
4
|
__omp_prelude_loaded__ = True
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
import os, json, math
|
|
6
|
+
import os, json, math, re
|
|
7
|
+
from urllib.parse import unquote
|
|
7
8
|
|
|
8
9
|
# __omp_display is injected by runner.py before the prelude executes; it
|
|
9
10
|
# mirrors IPython's display() semantics with the same MIME bundle output.
|
|
@@ -53,9 +54,47 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
53
54
|
_emit_status("env", key=key, value=val, action="get")
|
|
54
55
|
return val
|
|
55
56
|
|
|
57
|
+
_OMP_INTERNAL_URL_RE = re.compile(r"^([a-z][a-z0-9+.-]*)://(.*)$", re.IGNORECASE)
|
|
58
|
+
|
|
59
|
+
def _resolve_omp_path(path: str | Path) -> Path:
|
|
60
|
+
"""Map a helper path to a real filesystem Path.
|
|
61
|
+
|
|
62
|
+
A `scheme://…` whose scheme has an injected on-disk root (e.g.
|
|
63
|
+
`local://`, via PI_EVAL_LOCAL_ROOTS) is rewritten under that root so it
|
|
64
|
+
lands where `read local://…` resolves — not a literal `local:/`
|
|
65
|
+
directory under the cwd (which `Path("local://x")` collapses to). Plain
|
|
66
|
+
paths pass through unchanged; any other `scheme://` is rejected."""
|
|
67
|
+
if not isinstance(path, str):
|
|
68
|
+
return Path(path)
|
|
69
|
+
match = _OMP_INTERNAL_URL_RE.match(path)
|
|
70
|
+
if not match:
|
|
71
|
+
return Path(path)
|
|
72
|
+
scheme = match.group(1).lower()
|
|
73
|
+
try:
|
|
74
|
+
roots = json.loads(os.environ.get("PI_EVAL_LOCAL_ROOTS") or "{}")
|
|
75
|
+
except (ValueError, TypeError):
|
|
76
|
+
roots = {}
|
|
77
|
+
root = roots.get(scheme) if isinstance(roots, dict) else None
|
|
78
|
+
if not root:
|
|
79
|
+
raise ValueError(f"Protocol paths are not supported by this helper: {path}")
|
|
80
|
+
relative = unquote(match.group(2).replace("\\", "/"))
|
|
81
|
+
# Mirror the host `path.resolve`/`resolveLocalUrlToPath`: normalize and
|
|
82
|
+
# make absolute WITHOUT realpath'ing symlinks (Path.resolve would turn
|
|
83
|
+
# /tmp into /private/tmp and diverge from the read-side resolution).
|
|
84
|
+
root_path = os.path.abspath(root)
|
|
85
|
+
if relative == "":
|
|
86
|
+
return Path(root_path)
|
|
87
|
+
rel_path = Path(relative)
|
|
88
|
+
if rel_path.is_absolute() or ".." in rel_path.parts:
|
|
89
|
+
raise ValueError(f"Unsafe {scheme}:// path (absolute or traversal): {path}")
|
|
90
|
+
resolved = os.path.abspath(os.path.join(root_path, relative))
|
|
91
|
+
if resolved != root_path and not resolved.startswith(root_path + os.sep):
|
|
92
|
+
raise ValueError(f"{scheme}:// path escapes its root: {path}")
|
|
93
|
+
return Path(resolved)
|
|
94
|
+
|
|
56
95
|
def read(path: str | Path, offset: int = 1, limit: int | None = None) -> str:
|
|
57
96
|
"""Read file contents. offset/limit are 1-indexed line numbers."""
|
|
58
|
-
p =
|
|
97
|
+
p = _resolve_omp_path(path)
|
|
59
98
|
data = p.read_text(encoding="utf-8")
|
|
60
99
|
lines = data.splitlines(keepends=True)
|
|
61
100
|
if offset > 1 or limit is not None:
|
|
@@ -69,7 +108,7 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
69
108
|
|
|
70
109
|
def write(path: str | Path, content: str) -> Path:
|
|
71
110
|
"""Write file contents (create parents)."""
|
|
72
|
-
p =
|
|
111
|
+
p = _resolve_omp_path(path)
|
|
73
112
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
74
113
|
p.write_text(content, encoding="utf-8")
|
|
75
114
|
_emit_status("write", path=str(p), chars=len(content))
|
|
@@ -77,7 +116,7 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
77
116
|
|
|
78
117
|
def append(path: str | Path, content: str) -> Path:
|
|
79
118
|
"""Append to file."""
|
|
80
|
-
p =
|
|
119
|
+
p = _resolve_omp_path(path)
|
|
81
120
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
82
121
|
with p.open("a", encoding="utf-8") as f:
|
|
83
122
|
f.write(content)
|
|
@@ -463,8 +502,8 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
463
502
|
|
|
464
503
|
tool = _ToolProxy()
|
|
465
504
|
|
|
466
|
-
def
|
|
467
|
-
"""Oneshot, stateless
|
|
505
|
+
def completion(prompt, *, model="default", system=None, schema=None):
|
|
506
|
+
"""Oneshot, stateless completion against a model tier.
|
|
468
507
|
|
|
469
508
|
`model` selects a tier: "smol", "default" (the session's active model),
|
|
470
509
|
or "slow". Pass `system` for a system prompt. Pass a JSON-Schema dict
|
|
@@ -476,7 +515,7 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
476
515
|
args["system"] = system
|
|
477
516
|
if schema is not None:
|
|
478
517
|
args["schema"] = schema
|
|
479
|
-
res = _bridge_call("
|
|
518
|
+
res = _bridge_call("__completion__", args)
|
|
480
519
|
text = res.get("text") if isinstance(res, dict) else res
|
|
481
520
|
return json.loads(text) if schema is not None else text
|
|
482
521
|
|
package/src/eval/py/runner.py
CHANGED
package/src/exa/render.ts
CHANGED
|
@@ -93,7 +93,7 @@ export function renderExaResult(
|
|
|
93
93
|
const cost = response.costDollars?.total;
|
|
94
94
|
const time = response.searchTime;
|
|
95
95
|
|
|
96
|
-
const icon =
|
|
96
|
+
const icon = resultCount > 0 ? uiTheme.styledSymbol("tool.exa", "accent") : formatStatusIcon("warning", uiTheme);
|
|
97
97
|
|
|
98
98
|
const metaParts = [formatCount("result", resultCount)];
|
|
99
99
|
if (cost !== undefined) metaParts.push(`cost:$${cost.toFixed(4)}`);
|
package/src/export/ttsr.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* the agent's output. When a match occurs, the stream is aborted, the rule is
|
|
6
6
|
* injected as a system reminder, and the request is retried.
|
|
7
7
|
*/
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { AstMatchStrictness, astMatch } from "@oh-my-pi/pi-natives";
|
|
8
10
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
9
11
|
import type { Rule } from "../capability/rule";
|
|
10
12
|
import type { TtsrSettings } from "../config/settings";
|
|
@@ -38,6 +40,8 @@ interface TtsrScope {
|
|
|
38
40
|
interface TtsrEntry {
|
|
39
41
|
rule: Rule;
|
|
40
42
|
conditions: RegExp[];
|
|
43
|
+
/** ast-grep pattern strings; matched only against edit/write tool snapshots. */
|
|
44
|
+
astConditions: string[];
|
|
41
45
|
scope: TtsrScope;
|
|
42
46
|
globalPathGlobs?: Bun.Glob[];
|
|
43
47
|
}
|
|
@@ -70,6 +74,8 @@ export class TtsrManager {
|
|
|
70
74
|
readonly #rules = new Map<string, TtsrEntry>();
|
|
71
75
|
readonly #injectionRecords = new Map<string, InjectionRecord>();
|
|
72
76
|
readonly #buffers = new Map<string, string>();
|
|
77
|
+
/** Last snapshot evaluated for AST conditions, keyed by stream key, to dedupe matcher runs. */
|
|
78
|
+
readonly #lastAstSnapshots = new Map<string, string>();
|
|
73
79
|
#messageCount = 0;
|
|
74
80
|
|
|
75
81
|
constructor(settings?: TtsrSettings) {
|
|
@@ -302,7 +308,8 @@ export class TtsrManager {
|
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
const conditions = this.#compileConditions(rule);
|
|
305
|
-
|
|
311
|
+
const astConditions = (rule.astCondition ?? []).map(pattern => pattern.trim()).filter(p => p.length > 0);
|
|
312
|
+
if (conditions.length === 0 && astConditions.length === 0) {
|
|
306
313
|
return false;
|
|
307
314
|
}
|
|
308
315
|
|
|
@@ -318,6 +325,7 @@ export class TtsrManager {
|
|
|
318
325
|
this.#rules.set(rule.name, {
|
|
319
326
|
rule,
|
|
320
327
|
conditions,
|
|
328
|
+
astConditions,
|
|
321
329
|
scope,
|
|
322
330
|
globalPathGlobs,
|
|
323
331
|
});
|
|
@@ -325,6 +333,7 @@ export class TtsrManager {
|
|
|
325
333
|
logger.debug("TTSR rule registered", {
|
|
326
334
|
ruleName: rule.name,
|
|
327
335
|
conditions: rule.condition,
|
|
336
|
+
astConditions: rule.astCondition,
|
|
328
337
|
scope: rule.scope,
|
|
329
338
|
globs: rule.globs,
|
|
330
339
|
});
|
|
@@ -359,6 +368,112 @@ export class TtsrManager {
|
|
|
359
368
|
return this.#matchBuffer(snapshot, context);
|
|
360
369
|
}
|
|
361
370
|
|
|
371
|
+
/** Derive an ast-grep language alias from candidate paths (bare extension, e.g. "ts"), if any. */
|
|
372
|
+
#deriveLang(filePaths: string[] | undefined): string | undefined {
|
|
373
|
+
for (const filePath of filePaths ?? []) {
|
|
374
|
+
const ext = path.extname(this.#normalizePath(filePath));
|
|
375
|
+
if (ext.length > 1) {
|
|
376
|
+
return ext.slice(1).toLowerCase();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Evaluate ast-grep `astCondition` rules against a reconstructed tool snapshot.
|
|
384
|
+
*
|
|
385
|
+
* Only edit/write tool streams reach here (AST conditions need a language, which
|
|
386
|
+
* we infer from the file extension on the tool's path argument). The snapshot is
|
|
387
|
+
* matched in memory by the native engine (`astMatch`), so this is async and
|
|
388
|
+
* intentionally throttled: identical consecutive snapshots (the common case when
|
|
389
|
+
* only non-source arguments change between deltas) are skipped.
|
|
390
|
+
*/
|
|
391
|
+
async checkAstSnapshot(snapshot: string, context: TtsrMatchContext): Promise<Rule[]> {
|
|
392
|
+
if (!this.#settings.enabled || context.source !== "tool") {
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const lang = this.#deriveLang(context.filePaths);
|
|
397
|
+
if (!lang) {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const candidates: TtsrEntry[] = [];
|
|
402
|
+
for (const [name, entry] of this.#rules) {
|
|
403
|
+
if (entry.astConditions.length === 0) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (
|
|
407
|
+
!this.#canTrigger(name) ||
|
|
408
|
+
!this.#matchesScope(entry, context) ||
|
|
409
|
+
!this.#matchesGlobalPaths(entry, context)
|
|
410
|
+
) {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
candidates.push(entry);
|
|
414
|
+
}
|
|
415
|
+
if (candidates.length === 0) {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Throttle: skip re-running the matcher when the source content is unchanged.
|
|
420
|
+
const bufferKey = this.#bufferKey(context);
|
|
421
|
+
if (this.#lastAstSnapshots.get(bufferKey) === snapshot) {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
this.#lastAstSnapshots.set(bufferKey, snapshot);
|
|
425
|
+
|
|
426
|
+
const matches: Rule[] = [];
|
|
427
|
+
for (const entry of candidates) {
|
|
428
|
+
if (await this.#astConditionsMatch(entry.astConditions, snapshot, lang)) {
|
|
429
|
+
matches.push(entry.rule);
|
|
430
|
+
logger.debug("TTSR ast condition matched", {
|
|
431
|
+
ruleName: entry.rule.name,
|
|
432
|
+
astConditions: entry.rule.astCondition,
|
|
433
|
+
toolName: context.toolName,
|
|
434
|
+
filePaths: context.filePaths,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return matches;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async #astConditionsMatch(patterns: string[], source: string, lang: string): Promise<boolean> {
|
|
442
|
+
try {
|
|
443
|
+
const result = await astMatch({
|
|
444
|
+
patterns,
|
|
445
|
+
source,
|
|
446
|
+
lang,
|
|
447
|
+
strictness: AstMatchStrictness.Smart,
|
|
448
|
+
limit: 1,
|
|
449
|
+
});
|
|
450
|
+
if (result.parseErrors && result.parseErrors.length > 0) {
|
|
451
|
+
logger.debug("TTSR ast match reported parse errors", { parseErrors: result.parseErrors });
|
|
452
|
+
}
|
|
453
|
+
return result.totalMatches > 0;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
logger.warn("TTSR ast match failed, treating as no match", {
|
|
456
|
+
patterns,
|
|
457
|
+
lang,
|
|
458
|
+
error: error instanceof Error ? error.message : String(error),
|
|
459
|
+
});
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** True when any registered rule carries ast-grep conditions. */
|
|
465
|
+
hasAstRules(): boolean {
|
|
466
|
+
if (!this.#settings.enabled) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
for (const entry of this.#rules.values()) {
|
|
470
|
+
if (entry.astConditions.length > 0) {
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
|
|
362
477
|
#matchBuffer(buffer: string, context: TtsrMatchContext): Rule[] {
|
|
363
478
|
if (!this.#settings.enabled) {
|
|
364
479
|
return [];
|
|
@@ -435,6 +550,7 @@ export class TtsrManager {
|
|
|
435
550
|
/** Reset stream buffers (called on new turn). */
|
|
436
551
|
resetBuffer(): void {
|
|
437
552
|
this.#buffers.clear();
|
|
553
|
+
this.#lastAstSnapshots.clear();
|
|
438
554
|
}
|
|
439
555
|
|
|
440
556
|
/** Check if any TTSR rules are registered. */
|
|
@@ -445,6 +561,11 @@ export class TtsrManager {
|
|
|
445
561
|
return this.#rules.size > 0;
|
|
446
562
|
}
|
|
447
563
|
|
|
564
|
+
/** All rules currently registered for TTSR monitoring, in registration order. */
|
|
565
|
+
getRules(): Rule[] {
|
|
566
|
+
return Array.from(this.#rules.values(), entry => entry.rule);
|
|
567
|
+
}
|
|
568
|
+
|
|
448
569
|
/** Increment message counter (call after each turn). */
|
|
449
570
|
incrementMessageCount(): void {
|
|
450
571
|
this.#messageCount++;
|
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
TextContent,
|
|
29
29
|
TSchema,
|
|
30
30
|
} from "@oh-my-pi/pi-ai";
|
|
31
|
-
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/
|
|
31
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
|
|
32
32
|
import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
33
33
|
import type { AutocompleteItem, Component, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui";
|
|
34
34
|
import type { logger as PiLogger } from "@oh-my-pi/pi-utils";
|
|
@@ -1134,6 +1134,13 @@ export interface ProviderConfig {
|
|
|
1134
1134
|
/** Optional model rewrite hook for credential-aware routing (e.g., enterprise URLs). */
|
|
1135
1135
|
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
|
|
1136
1136
|
};
|
|
1137
|
+
/**
|
|
1138
|
+
* Async factory that fetches the live model list from the provider endpoint.
|
|
1139
|
+
* Runs through the same SQLite model-cache as built-in providers (keyed by
|
|
1140
|
+
* provider name, default 24 h TTL). Receives the resolved API key (undefined
|
|
1141
|
+
* when unauthenticated). Mutually exclusive with `models`.
|
|
1142
|
+
*/
|
|
1143
|
+
fetchDynamicModels?: (apiKey: string | undefined) => Promise<readonly ProviderModelConfig[]>;
|
|
1137
1144
|
}
|
|
1138
1145
|
|
|
1139
1146
|
/** Configuration for a model within a provider. */
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* entrypoint. Legacy extensions still author parameter schemas as
|
|
9
9
|
* `Type.Object({ ... })`, so this file is served by `legacy-pi-compat.ts` in
|
|
10
10
|
* place of the real pi-ai entrypoint whenever a legacy extension imports the
|
|
11
|
-
* bare package root. Subpath imports (`@oh-my-pi/pi-ai/
|
|
11
|
+
* bare package root. Subpath imports (`@oh-my-pi/pi-ai/oauth`, etc.)
|
|
12
12
|
* continue to resolve directly against the bundled pi-ai package.
|
|
13
13
|
*
|
|
14
14
|
* The `Type` runtime is borrowed from the Zod-backed TypeBox shim that
|
|
@@ -48,7 +48,7 @@ export function formatDoctorResults(checks: DoctorCheck[]): string {
|
|
|
48
48
|
for (const check of checks) {
|
|
49
49
|
const icon =
|
|
50
50
|
check.status === "ok"
|
|
51
|
-
? theme.status.
|
|
51
|
+
? theme.status.enabled
|
|
52
52
|
: check.status === "warning"
|
|
53
53
|
? theme.status.warning
|
|
54
54
|
: theme.status.error;
|
|
@@ -33,10 +33,11 @@ const PI_PACKAGE_ALTERNATION = PI_PACKAGE_NAMES.join("|");
|
|
|
33
33
|
// bundled copy. Add new entries as `pkg/from -> pkg/to` whenever a plugin
|
|
34
34
|
// surfaces another upstream-only subpath that breaks resolution.
|
|
35
35
|
const PI_SUBPATH_REMAPS: ReadonlyMap<string, string> = new Map<string, string>([
|
|
36
|
-
// `@mariozechner/pi-ai/oauth` re-exported
|
|
37
|
-
// Our pi-ai
|
|
38
|
-
//
|
|
39
|
-
|
|
36
|
+
// (currently empty) Upstream `@mariozechner/pi-ai/oauth` re-exported
|
|
37
|
+
// `./utils/oauth/index.js`. Our pi-ai now exposes the same surface at the
|
|
38
|
+
// real `@oh-my-pi/pi-ai/oauth` export, so the legacy subpath canonicalizes
|
|
39
|
+
// straight to it with no rewrite. Add `from -> to` entries here whenever a
|
|
40
|
+
// future upstream-only subpath surfaces that breaks resolution.
|
|
40
41
|
]);
|
|
41
42
|
|
|
42
43
|
const LEGACY_PI_SPECIFIER_FILTER = new RegExp(`^@(?:${PI_SCOPE_ALTERNATION})/(?:${PI_PACKAGE_ALTERNATION})(?:/.*)?$`);
|
|
@@ -119,7 +120,7 @@ const TYPEBOX_SHIM_PATH = BUNFS_PACKAGE_ROOT
|
|
|
119
120
|
// longer satisfies those imports. The override below redirects only the bare
|
|
120
121
|
// pi-ai package root onto a sibling shim that re-exports the canonical surface
|
|
121
122
|
// plus the borrowed `Type` runtime from the Zod-backed TypeBox shim. Subpath
|
|
122
|
-
// imports such as `@oh-my-pi/pi-ai/
|
|
123
|
+
// imports such as `@oh-my-pi/pi-ai/oauth` continue to resolve directly
|
|
123
124
|
// against the bundled pi-ai package.
|
|
124
125
|
const LEGACY_PI_AI_SHIM_PATH = BUNFS_PACKAGE_ROOT
|
|
125
126
|
? bunfsPath("coding-agent", "src", "extensibility", "legacy-pi-ai-shim.js")
|
|
@@ -209,7 +209,7 @@ export const goalToolRenderer = {
|
|
|
209
209
|
|
|
210
210
|
const header = renderStatusLine(
|
|
211
211
|
{
|
|
212
|
-
|
|
212
|
+
iconOverride: uiTheme.styledSymbol("tool.goal", "accent"),
|
|
213
213
|
title: "Goal",
|
|
214
214
|
description,
|
|
215
215
|
badge: { label: goal.status, color: goalBadgeColor(goal.status) },
|