@oh-my-pi/pi-coding-agent 16.2.9 → 16.2.12
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 +60 -0
- package/dist/cli.js +3464 -3306
- package/dist/types/cli/bench-cli.d.ts +0 -5
- package/dist/types/cli/dry-balance-cli.d.ts +0 -5
- package/dist/types/cli/models-cli.d.ts +1 -1
- package/dist/types/config/inline-tool-descriptors-mode.d.ts +1 -2
- package/dist/types/config/model-registry.d.ts +1 -23
- package/dist/types/config/model-resolver.d.ts +10 -15
- package/dist/types/config/models-config-schema.d.ts +0 -6
- package/dist/types/config/models-config.d.ts +0 -6
- package/dist/types/config/settings-schema.d.ts +31 -1
- package/dist/types/eval/py/__tests__/runner-shell-output.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +15 -0
- package/dist/types/extensibility/skills.d.ts +29 -0
- package/dist/types/lsp/client.d.ts +9 -3
- package/dist/types/modes/components/todo-reminder.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +0 -1
- package/dist/types/modes/skill-command.d.ts +1 -1
- package/dist/types/modes/types.d.ts +0 -1
- package/dist/types/stt/asr-client.d.ts +7 -3
- package/dist/types/task/spawn-policy.d.ts +17 -0
- package/dist/types/task/spawn-policy.test.d.ts +1 -0
- package/dist/types/task/types.d.ts +8 -2
- package/dist/types/tools/__tests__/eval-description.test.d.ts +1 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +2 -2
- package/dist/types/tools/browser/registry.d.ts +2 -0
- package/dist/types/tools/browser/run-cancellation.d.ts +4 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +30 -1
- package/dist/types/tools/eval.d.ts +3 -6
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/utils/image-vision-fallback.d.ts +1 -1
- package/package.json +14 -13
- package/scripts/bundle-dist.ts +23 -4
- package/scripts/generate-docs-index.ts +116 -24
- package/src/advisor/__tests__/advisor.test.ts +1 -1
- package/src/async/job-manager.ts +27 -3
- package/src/auto-thinking/classifier.ts +1 -1
- package/src/cli/bench-cli.ts +3 -12
- package/src/cli/dry-balance-cli.ts +1 -6
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/models-cli.ts +3 -81
- package/src/commands/models.ts +1 -2
- package/src/commit/model-selection.ts +4 -4
- package/src/config/inline-tool-descriptors-mode.ts +1 -2
- package/src/config/model-discovery.ts +150 -82
- package/src/config/model-registry.ts +11 -225
- package/src/config/model-resolver.ts +23 -135
- package/src/config/models-config-schema.ts +0 -22
- package/src/config/prompt-templates.ts +20 -0
- package/src/config/settings-schema.ts +35 -1
- package/src/config/settings.ts +60 -24
- package/src/eval/__tests__/agent-bridge.test.ts +17 -3
- package/src/eval/agent-bridge.ts +7 -9
- package/src/eval/completion-bridge.ts +1 -1
- package/src/eval/py/__tests__/runner-shell-output.test.ts +157 -0
- package/src/eval/py/runner.py +169 -18
- package/src/extensibility/extensions/model-api.ts +1 -3
- package/src/extensibility/extensions/runner.ts +62 -5
- package/src/extensibility/skills.ts +77 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/lsp/client.ts +162 -45
- package/src/lsp/config.ts +17 -3
- package/src/lsp/index.ts +31 -21
- package/src/main.ts +0 -1
- package/src/mcp/transports/http.ts +19 -17
- package/src/mcp/transports/stdio.test.ts +51 -1
- package/src/mcp/transports/stdio.ts +19 -9
- package/src/memories/index.ts +0 -1
- package/src/mnemopi/backend.ts +1 -1
- package/src/modes/acp/acp-agent.ts +6 -9
- package/src/modes/components/model-selector.ts +32 -186
- package/src/modes/components/todo-reminder.ts +5 -1
- package/src/modes/controllers/event-controller.ts +1 -16
- package/src/modes/controllers/selector-controller.ts +57 -36
- package/src/modes/interactive-mode.ts +0 -6
- package/src/modes/rpc/rpc-mode.ts +5 -8
- package/src/modes/skill-command.ts +8 -20
- package/src/modes/types.ts +0 -1
- package/src/prompts/system/subagent-system-prompt.md +2 -2
- package/src/prompts/system/tool-call-loop-redirect.md +8 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/task.md +1 -1
- package/src/sdk.ts +2 -9
- package/src/session/agent-session.ts +89 -8
- package/src/session/session-context.ts +2 -1
- package/src/session/session-manager.ts +2 -1
- package/src/session/unexpected-stop-classifier.ts +1 -1
- package/src/stt/asr-client.ts +87 -27
- package/src/stt/downloader.ts +8 -2
- package/src/task/executor.ts +1 -3
- package/src/task/index.ts +37 -32
- package/src/task/spawn-policy.test.ts +63 -0
- package/src/task/spawn-policy.ts +58 -0
- package/src/task/types.ts +77 -6
- package/src/tools/__tests__/eval-description.test.ts +19 -0
- package/src/tools/ast-grep.ts +34 -12
- package/src/tools/browser/cmux/cmux-tab.ts +66 -24
- package/src/tools/browser/registry.ts +25 -0
- package/src/tools/browser/run-cancellation.ts +47 -0
- package/src/tools/browser/tab-supervisor.ts +120 -7
- package/src/tools/browser/tab-worker.ts +22 -10
- package/src/tools/browser.ts +1 -0
- package/src/tools/eval.ts +15 -10
- package/src/tools/grep.ts +11 -8
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/write.ts +49 -9
- package/src/utils/commit-message-generator.ts +0 -1
- package/src/utils/git.ts +22 -1
- package/src/utils/image-vision-fallback.ts +2 -3
- package/src/utils/title-generator.ts +1 -1
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
loadPuppeteerInWorker,
|
|
37
37
|
} from "./launch";
|
|
38
38
|
import { extractReadableFromHtml, type ReadableFormat } from "./readable";
|
|
39
|
+
import { waitForBrowserRun } from "./run-cancellation";
|
|
39
40
|
import type {
|
|
40
41
|
Observation,
|
|
41
42
|
ObservationEntry,
|
|
@@ -481,13 +482,13 @@ async function clickQueryHandlerText(
|
|
|
481
482
|
const target = await resolveActionableQueryHandlerClickTarget(handles);
|
|
482
483
|
if (!target) {
|
|
483
484
|
lastReason = handles.length ? "no-visible-candidate" : "no-matches";
|
|
484
|
-
await Bun.sleep(100);
|
|
485
|
+
await untilAborted(clickSignal, () => Bun.sleep(100));
|
|
485
486
|
continue;
|
|
486
487
|
}
|
|
487
488
|
const actionability = await isClickActionable(target);
|
|
488
489
|
if (!actionability.ok) {
|
|
489
490
|
lastReason = actionability.reason;
|
|
490
|
-
await Bun.sleep(100);
|
|
491
|
+
await untilAborted(clickSignal, () => Bun.sleep(100));
|
|
491
492
|
continue;
|
|
492
493
|
}
|
|
493
494
|
try {
|
|
@@ -495,7 +496,7 @@ async function clickQueryHandlerText(
|
|
|
495
496
|
return;
|
|
496
497
|
} catch (err) {
|
|
497
498
|
lastReason = err instanceof Error ? err.message : String(err);
|
|
498
|
-
await Bun.sleep(100);
|
|
499
|
+
await untilAborted(clickSignal, () => Bun.sleep(100));
|
|
499
500
|
}
|
|
500
501
|
} finally {
|
|
501
502
|
await Promise.all(handles.map(async handle => handle.dispose().catch(() => undefined)));
|
|
@@ -515,6 +516,7 @@ export interface InflightOp {
|
|
|
515
516
|
interface ActiveRun {
|
|
516
517
|
id: string;
|
|
517
518
|
ac: AbortController;
|
|
519
|
+
signal: AbortSignal;
|
|
518
520
|
displays: RunResultOk["displays"];
|
|
519
521
|
screenshots: ScreenshotResult[];
|
|
520
522
|
pendingTools: Map<string, { resolve(value: unknown): void; reject(error: Error): void }>;
|
|
@@ -697,12 +699,14 @@ export class WorkerCore {
|
|
|
697
699
|
}
|
|
698
700
|
const timeoutSignal = AbortSignal.timeout(msg.timeoutMs);
|
|
699
701
|
const ac = new AbortController();
|
|
700
|
-
const
|
|
702
|
+
const runAc = new AbortController();
|
|
703
|
+
const signal = AbortSignal.any([timeoutSignal, ac.signal, runAc.signal]);
|
|
701
704
|
const displays: RunResultOk["displays"] = [];
|
|
702
705
|
const screenshots: ScreenshotResult[] = [];
|
|
703
706
|
const active: ActiveRun = {
|
|
704
707
|
id: msg.id,
|
|
705
708
|
ac,
|
|
709
|
+
signal,
|
|
706
710
|
displays,
|
|
707
711
|
screenshots,
|
|
708
712
|
pendingTools: new Map(),
|
|
@@ -724,7 +728,7 @@ export class WorkerCore {
|
|
|
724
728
|
assert: (cond: unknown, text?: string): void => {
|
|
725
729
|
if (!cond) throw new ToolError(text ?? "Assertion failed");
|
|
726
730
|
},
|
|
727
|
-
wait: (ms: number): Promise<void> =>
|
|
731
|
+
wait: (ms: number): Promise<void> => waitForBrowserRun(ms, signal),
|
|
728
732
|
});
|
|
729
733
|
const { promise: cancelRejection, reject: rejectCancel } = Promise.withResolvers<never>();
|
|
730
734
|
const onCancel = (): void => {
|
|
@@ -767,6 +771,7 @@ export class WorkerCore {
|
|
|
767
771
|
this.#transport.send({ type: "result", id: msg.id, ok: false, error: errorPayload(error) });
|
|
768
772
|
} finally {
|
|
769
773
|
if (this.#active?.id === msg.id) this.#active = null;
|
|
774
|
+
runAc.abort(new ToolAbortError("Browser run ended"));
|
|
770
775
|
}
|
|
771
776
|
}
|
|
772
777
|
|
|
@@ -783,11 +788,18 @@ export class WorkerCore {
|
|
|
783
788
|
const active = this.#active;
|
|
784
789
|
if (!active) return null;
|
|
785
790
|
return {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
+
onText: chunk => {
|
|
792
|
+
throwIfAborted(active.signal);
|
|
793
|
+
this.#log("debug", chunk.replace(/\n$/, ""));
|
|
794
|
+
},
|
|
795
|
+
onDisplay: output => {
|
|
796
|
+
throwIfAborted(active.signal);
|
|
797
|
+
this.#pushDisplay(active.displays, output);
|
|
798
|
+
},
|
|
799
|
+
callTool: (name, args) => {
|
|
800
|
+
throwIfAborted(active.signal);
|
|
801
|
+
return this.#callTool(active, name, args);
|
|
802
|
+
},
|
|
791
803
|
};
|
|
792
804
|
}
|
|
793
805
|
|
package/src/tools/browser.ts
CHANGED
package/src/tools/eval.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { defaultEvalSessionId } from "../eval/session-id";
|
|
|
10
10
|
import type { EvalCellResult, EvalDisplayOutput, EvalLanguage, EvalStatusEvent, EvalToolDetails } from "../eval/types";
|
|
11
11
|
import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
|
|
12
12
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
|
|
13
|
+
import { resolveSpawnPolicy } from "../task/spawn-policy";
|
|
13
14
|
import { webpExclusionForModel } from "../utils/image-loading";
|
|
14
15
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
15
16
|
import type { ToolSession } from ".";
|
|
@@ -164,13 +165,10 @@ export interface EvalToolDescriptionOptions {
|
|
|
164
165
|
rb?: boolean;
|
|
165
166
|
jl?: boolean;
|
|
166
167
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* compatibility — when the session forbids spawning, the prelude doc
|
|
170
|
-
* omits the `agent()` entry so the model does not promise itself a
|
|
171
|
-
* helper that will only ever throw "spawns disabled".
|
|
168
|
+
* Parent spawn policy (`getSessionSpawns`). `true`/omitted means unrestricted,
|
|
169
|
+
* `false`/`""` hides `agent()`, and a comma list drives the advertised default.
|
|
172
170
|
*/
|
|
173
|
-
spawns?: boolean;
|
|
171
|
+
spawns?: boolean | string | null;
|
|
174
172
|
}
|
|
175
173
|
|
|
176
174
|
export function getEvalToolDescription(options: EvalToolDescriptionOptions = {}): string {
|
|
@@ -178,8 +176,16 @@ export function getEvalToolDescription(options: EvalToolDescriptionOptions = {})
|
|
|
178
176
|
const js = options.js ?? true;
|
|
179
177
|
const rb = options.rb ?? false;
|
|
180
178
|
const jl = options.jl ?? false;
|
|
181
|
-
const
|
|
182
|
-
return prompt.render(evalDescription, {
|
|
179
|
+
const spawnPolicy = resolveSpawnPolicy(options.spawns ?? true);
|
|
180
|
+
return prompt.render(evalDescription, {
|
|
181
|
+
py,
|
|
182
|
+
js,
|
|
183
|
+
rb,
|
|
184
|
+
jl,
|
|
185
|
+
spawns: spawnPolicy.enabled,
|
|
186
|
+
spawnDefaultAgent: spawnPolicy.defaultAgent,
|
|
187
|
+
spawnAllowedAgentsText: spawnPolicy.allowedPromptText,
|
|
188
|
+
});
|
|
183
189
|
}
|
|
184
190
|
|
|
185
191
|
export interface EvalToolOptions {
|
|
@@ -294,13 +300,12 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
294
300
|
if (!this.session) return getEvalToolDescription();
|
|
295
301
|
const backends = resolveEvalBackends(this.session);
|
|
296
302
|
const sessionSpawns = this.session.getSessionSpawns?.() ?? "*";
|
|
297
|
-
const spawnsAllowed = sessionSpawns !== "" && sessionSpawns !== null;
|
|
298
303
|
return getEvalToolDescription({
|
|
299
304
|
py: backends.python,
|
|
300
305
|
js: backends.js,
|
|
301
306
|
rb: backends.ruby,
|
|
302
307
|
jl: backends.julia,
|
|
303
|
-
spawns:
|
|
308
|
+
spawns: sessionSpawns,
|
|
304
309
|
});
|
|
305
310
|
}
|
|
306
311
|
/** All reuse-chain examples; the `examples` getter filters by enabled languages. */
|
package/src/tools/grep.ts
CHANGED
|
@@ -122,8 +122,10 @@ export const SINGLE_FILE_MATCHES = 200;
|
|
|
122
122
|
* pagination headroom so the caller can see total file count. */
|
|
123
123
|
const INTERNAL_TOTAL_CAP = 2000;
|
|
124
124
|
/** Mirrors `MAX_FILE_BYTES` in `crates/pi-natives/src/grep.rs`. Native grep
|
|
125
|
-
*
|
|
126
|
-
*
|
|
125
|
+
* searches only the first `MAX_FILE_BYTES` of a larger file (a leading mmap
|
|
126
|
+
* window) and drops the rest; matches beyond the window are not returned. We
|
|
127
|
+
* surface a partial-coverage note when the caller explicitly targeted such a
|
|
128
|
+
* file so they know matches past the window are not shown. */
|
|
127
129
|
const NATIVE_GREP_MAX_FILE_BYTES = 4 * 1024 * 1024;
|
|
128
130
|
/** Wall-clock budget for a single native grep invocation. Without it, an
|
|
129
131
|
* aborted or runaway search (huge tree, network mount) keeps burning CPU on
|
|
@@ -1260,8 +1262,9 @@ export class GrepTool implements AgentTool<typeof searchSchema, GrepToolDetails>
|
|
|
1260
1262
|
const { record: recordFile, list: fileList } = createFileRecorder();
|
|
1261
1263
|
const fileMatchCounts = new Map<string, number>();
|
|
1262
1264
|
// Detect explicit file targets that exceed the native grep size cap.
|
|
1263
|
-
// Native
|
|
1264
|
-
//
|
|
1265
|
+
// Native searches only their first NATIVE_GREP_MAX_FILE_BYTES; without
|
|
1266
|
+
// this note the caller might miss that matches beyond the window
|
|
1267
|
+
// (or "no matches") reflect partial coverage, not the whole file.
|
|
1265
1268
|
const oversizedNote = await (async (): Promise<string | undefined> => {
|
|
1266
1269
|
const explicitFileTargets: string[] = [];
|
|
1267
1270
|
if (exactFilePaths) {
|
|
@@ -1285,13 +1288,13 @@ export class GrepTool implements AgentTool<typeof searchSchema, GrepToolDetails>
|
|
|
1285
1288
|
);
|
|
1286
1289
|
if (oversized.length === 0) return undefined;
|
|
1287
1290
|
const limitMb = Math.floor(NATIVE_GREP_MAX_FILE_BYTES / (1024 * 1024));
|
|
1288
|
-
return `
|
|
1291
|
+
return `Searched only the first ${limitMb}MB of large files (matches past the ${limitMb}MB window are not shown; use \`read\` for the rest): ${oversized.join(", ")}`;
|
|
1289
1292
|
})();
|
|
1290
|
-
// Directory/multi-target scopes: native
|
|
1291
|
-
//
|
|
1293
|
+
// Directory/multi-target scopes: native counts files it could not map
|
|
1294
|
+
// even a prefix of (rare mmap failures), but cannot name them.
|
|
1292
1295
|
const oversizedScanNote =
|
|
1293
1296
|
!oversizedNote && skippedOversizedCount > 0
|
|
1294
|
-
? `Skipped ${skippedOversizedCount}
|
|
1297
|
+
? `Skipped ${skippedOversizedCount} unreadable large file(s); target them directly with \`read\``
|
|
1295
1298
|
: undefined;
|
|
1296
1299
|
const archiveNote =
|
|
1297
1300
|
archiveUnreadable.length > 0
|
|
@@ -152,7 +152,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
152
152
|
const resolvePattern = (pattern: string | undefined): Model<Api> | undefined => {
|
|
153
153
|
if (!pattern) return undefined;
|
|
154
154
|
const expanded = expandRoleAlias(pattern, this.session.settings);
|
|
155
|
-
return resolveModelFromString(expanded, availableModels, matchPreferences
|
|
155
|
+
return resolveModelFromString(expanded, availableModels, matchPreferences);
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
const activeModelPattern = this.session.getActiveModelString?.() ?? this.session.getModelString?.();
|
package/src/tools/write.ts
CHANGED
|
@@ -52,6 +52,7 @@ import { enforcePlanModeWrite, resolvePlanPath, unwrapHashlineHeaderPath } from
|
|
|
52
52
|
import {
|
|
53
53
|
cachedRenderedString,
|
|
54
54
|
createRenderedStringCache,
|
|
55
|
+
Ellipsis,
|
|
55
56
|
formatDiagnostics,
|
|
56
57
|
formatErrorDetail,
|
|
57
58
|
formatExpandHint,
|
|
@@ -61,6 +62,8 @@ import {
|
|
|
61
62
|
type RenderedStringCache,
|
|
62
63
|
replaceTabs,
|
|
63
64
|
shortenPath,
|
|
65
|
+
TRUNCATE_LENGTHS,
|
|
66
|
+
truncateToWidth,
|
|
64
67
|
} from "./render-utils";
|
|
65
68
|
import {
|
|
66
69
|
deleteRowByKey,
|
|
@@ -165,6 +168,18 @@ function appendNoteToResult(result: AgentToolResult<WriteToolDetails>, note: str
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
function emitWriteProgress(
|
|
172
|
+
onUpdate: AgentToolUpdateCallback<WriteToolDetails> | undefined,
|
|
173
|
+
content: string,
|
|
174
|
+
displayPath: string,
|
|
175
|
+
resolvedPath?: string,
|
|
176
|
+
): void {
|
|
177
|
+
onUpdate?.({
|
|
178
|
+
content: [{ type: "text", text: `Writing ${content.length} bytes to ${shortenPath(displayPath)}...` }],
|
|
179
|
+
details: resolvedPath ? { resolvedPath } : {},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
168
183
|
/**
|
|
169
184
|
* If `content` begins with a `#!` shebang, ensure the file is executable.
|
|
170
185
|
*
|
|
@@ -781,7 +796,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
781
796
|
_toolCallId: string,
|
|
782
797
|
{ path: rawPath, content }: WriteParams,
|
|
783
798
|
signal?: AbortSignal,
|
|
784
|
-
|
|
799
|
+
onUpdate?: AgentToolUpdateCallback<WriteToolDetails>,
|
|
785
800
|
context?: AgentToolContext,
|
|
786
801
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
787
802
|
// Strip a hashline `[path#TAG]` wrapper up front so every downstream
|
|
@@ -807,6 +822,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
807
822
|
// Handler-owned writes (vault:// notes, host URIs) mutate user
|
|
808
823
|
// data outside the local sandbox — plan mode must reject them.
|
|
809
824
|
enforcePlanModeWrite(this.session, path, { op: "update" });
|
|
825
|
+
emitWriteProgress(onUpdate, cleanContent, path);
|
|
810
826
|
await handler.write(parsed, cleanContent, { cwd: this.session.cwd, signal });
|
|
811
827
|
let resultText = `Successfully wrote ${cleanContent.length} bytes to ${path}`;
|
|
812
828
|
if (stripped) {
|
|
@@ -826,6 +842,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
826
842
|
`Conflict URI scope '/${conflictUri.scope}' is read-only — read \`conflict://${conflictUri.id}/${conflictUri.scope}\` to inspect that side. To write, drop the scope (\`conflict://${conflictUri.id}\`) and put the chosen content (or shorthand like \`@${conflictUri.scope}\`) in \`content\`.`,
|
|
827
843
|
);
|
|
828
844
|
}
|
|
845
|
+
emitWriteProgress(onUpdate, cleanContent, path);
|
|
829
846
|
const result =
|
|
830
847
|
conflictUri.id === "*"
|
|
831
848
|
? await this.#resolveAllConflicts(cleanContent, stripped, signal)
|
|
@@ -844,6 +861,14 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
844
861
|
op: resolvedArchivePath.exists ? "update" : "create",
|
|
845
862
|
});
|
|
846
863
|
|
|
864
|
+
emitWriteProgress(
|
|
865
|
+
onUpdate,
|
|
866
|
+
cleanContent,
|
|
867
|
+
`${formatPathRelativeToCwd(resolvedArchivePath.absolutePath, this.session.cwd)}:${
|
|
868
|
+
resolvedArchivePath.archiveSubPath
|
|
869
|
+
}`,
|
|
870
|
+
resolvedArchivePath.absolutePath,
|
|
871
|
+
);
|
|
847
872
|
const archiveResult = await this.#writeArchiveEntry(cleanContent, resolvedArchivePath);
|
|
848
873
|
if (stripped) {
|
|
849
874
|
const firstText = archiveResult.content.find(
|
|
@@ -861,6 +886,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
861
886
|
if (resolvedSqlitePath) {
|
|
862
887
|
enforcePlanModeWrite(this.session, resolvedSqlitePath.sqlitePath, { op: "update" });
|
|
863
888
|
|
|
889
|
+
emitWriteProgress(onUpdate, cleanContent, path, resolvedSqlitePath.absolutePath);
|
|
864
890
|
const sqliteResult = await this.#writeSqliteRow(path, cleanContent, resolvedSqlitePath);
|
|
865
891
|
if (stripped) {
|
|
866
892
|
const firstText = sqliteResult.content.find(
|
|
@@ -883,11 +909,13 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
883
909
|
await assertEditableFile(absolutePath, path);
|
|
884
910
|
}
|
|
885
911
|
|
|
912
|
+
const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
913
|
+
emitWriteProgress(onUpdate, cleanContent, displayPath, absolutePath);
|
|
914
|
+
|
|
886
915
|
// Try ACP bridge first for editor-visible filesystem paths. Internal
|
|
887
916
|
// artifacts such as local:// plans are owned by OMP, not the editor.
|
|
888
917
|
if (await routeWriteThroughBridge(this.session, path, absolutePath, cleanContent)) {
|
|
889
918
|
const madeExecutable = await maybeMarkExecutableForShebang(absolutePath, cleanContent);
|
|
890
|
-
const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
891
919
|
const header = maybeWriteSnapshotHeader(this.session, absolutePath, cleanContent);
|
|
892
920
|
const writeLine = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
|
|
893
921
|
let resultText = header ? `${header}\n${writeLine}` : writeLine;
|
|
@@ -908,7 +936,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
908
936
|
this.session.bumpFileMutationVersion?.(absolutePath);
|
|
909
937
|
const madeExecutable = await maybeMarkExecutableForShebang(absolutePath, cleanContent);
|
|
910
938
|
|
|
911
|
-
const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
912
939
|
const header = maybeWriteSnapshotHeader(this.session, absolutePath, cleanContent);
|
|
913
940
|
const writeLine = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
|
|
914
941
|
let resultText = header ? `${header}\n${writeLine}` : writeLine;
|
|
@@ -1128,14 +1155,19 @@ export const writeToolRenderer = {
|
|
|
1128
1155
|
}));
|
|
1129
1156
|
}
|
|
1130
1157
|
|
|
1158
|
+
const isPartial = options.isPartial === true;
|
|
1159
|
+
const progressText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1131
1160
|
const lineCount = countLines(fileContent);
|
|
1132
1161
|
const lineSuffix = formatLineCountSuffix(lineCount, uiTheme);
|
|
1133
|
-
const execSuffix =
|
|
1134
|
-
|
|
1135
|
-
|
|
1162
|
+
const execSuffix =
|
|
1163
|
+
!isPartial && result.details?.madeExecutable
|
|
1164
|
+
? `${uiTheme.fg("dim", " · ")}${uiTheme.fg("success", "made executable!")}`
|
|
1165
|
+
: "";
|
|
1136
1166
|
const header = renderStatusLine(
|
|
1137
1167
|
{
|
|
1138
|
-
|
|
1168
|
+
icon: isPartial ? "running" : undefined,
|
|
1169
|
+
iconOverride: isPartial ? undefined : uiTheme.styledSymbol("tool.write", "accent"),
|
|
1170
|
+
spinnerFrame: options.spinnerFrame,
|
|
1139
1171
|
title: "Write",
|
|
1140
1172
|
description: `${langIcon} ${pathDisplay}${lineSuffix}${execSuffix}`,
|
|
1141
1173
|
},
|
|
@@ -1147,7 +1179,15 @@ export const writeToolRenderer = {
|
|
|
1147
1179
|
return framedBlock(uiTheme, width => {
|
|
1148
1180
|
const { expanded } = options;
|
|
1149
1181
|
let body = renderContentPreview(fileContent, expanded, lang, uiTheme, previewCache);
|
|
1150
|
-
if (
|
|
1182
|
+
if (isPartial && progressText) {
|
|
1183
|
+
const safeProgressText = truncateToWidth(
|
|
1184
|
+
replaceTabs(progressText),
|
|
1185
|
+
TRUNCATE_LENGTHS.LINE,
|
|
1186
|
+
Ellipsis.Unicode,
|
|
1187
|
+
);
|
|
1188
|
+
body = `${uiTheme.fg("muted", safeProgressText)}${body ? `\n${body}` : ""}`;
|
|
1189
|
+
}
|
|
1190
|
+
if (!isPartial && diagnostics) {
|
|
1151
1191
|
const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, fp =>
|
|
1152
1192
|
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
1153
1193
|
);
|
|
@@ -1162,7 +1202,7 @@ export const writeToolRenderer = {
|
|
|
1162
1202
|
return {
|
|
1163
1203
|
header,
|
|
1164
1204
|
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
1165
|
-
state: "success",
|
|
1205
|
+
state: isPartial ? "pending" : "success",
|
|
1166
1206
|
borderColor: "borderMuted",
|
|
1167
1207
|
width,
|
|
1168
1208
|
};
|
|
@@ -55,7 +55,6 @@ function getSmolModelCandidates(
|
|
|
55
55
|
const configuredSmol = resolveModelRoleValue(settings.getModelRole("smol"), availableModels, {
|
|
56
56
|
settings,
|
|
57
57
|
matchPreferences,
|
|
58
|
-
modelRegistry: registry,
|
|
59
58
|
});
|
|
60
59
|
addCandidate(configuredSmol.model, configuredSmol.thinkingLevel);
|
|
61
60
|
|
package/src/utils/git.ts
CHANGED
|
@@ -175,6 +175,14 @@ const SHORT_LIVED_GIT_CONFIG: readonly (readonly [key: string, value: string])[]
|
|
|
175
175
|
["core.untrackedCache", "false"],
|
|
176
176
|
];
|
|
177
177
|
const REMOTE_ALREADY_EXISTS = /remote .* already exists/i;
|
|
178
|
+
const AMBIENT_GIT_ENV = {
|
|
179
|
+
GIT_DIR: undefined,
|
|
180
|
+
GIT_COMMON_DIR: undefined,
|
|
181
|
+
GIT_WORK_TREE: undefined,
|
|
182
|
+
GIT_INDEX_FILE: undefined,
|
|
183
|
+
GIT_OBJECT_DIRECTORY: undefined,
|
|
184
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES: undefined,
|
|
185
|
+
} satisfies Record<string, undefined>;
|
|
178
186
|
|
|
179
187
|
interface CommandOptions {
|
|
180
188
|
readonly env?: Record<string, string | undefined>;
|
|
@@ -190,6 +198,15 @@ function normalizeStdin(input: CommandOptions["stdin"]): "ignore" | Uint8Array {
|
|
|
190
198
|
return new Uint8Array(input);
|
|
191
199
|
}
|
|
192
200
|
|
|
201
|
+
function buildGitEnv(overrides?: Record<string, string | undefined>): Record<string, string | undefined> {
|
|
202
|
+
return {
|
|
203
|
+
...process.env,
|
|
204
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
205
|
+
...AMBIENT_GIT_ENV,
|
|
206
|
+
...overrides,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
193
210
|
function ensureAvailable(): void {
|
|
194
211
|
if (!$which("git")) {
|
|
195
212
|
throw new Error("git is not installed.");
|
|
@@ -211,7 +228,7 @@ async function git(cwd: string, args: readonly string[], options: CommandOptions
|
|
|
211
228
|
const commandArgs = withShortLivedGitConfig(options.readOnly ? withNoOptionalLocks(args) : [...args]);
|
|
212
229
|
const child = Bun.spawn(["git", ...commandArgs], {
|
|
213
230
|
cwd,
|
|
214
|
-
env: options.env
|
|
231
|
+
env: buildGitEnv(options.env),
|
|
215
232
|
signal: options.signal,
|
|
216
233
|
stdin: normalizeStdin(options.stdin),
|
|
217
234
|
stdout: "pipe",
|
|
@@ -707,6 +724,7 @@ function resolveHeadStateReftableSync(repository: GitRepository): GitHeadState |
|
|
|
707
724
|
const symArgs = withShortLivedGitConfig(withNoOptionalLocks(["symbolic-ref", "HEAD"]));
|
|
708
725
|
const symResult = Bun.spawnSync(["git", ...symArgs], {
|
|
709
726
|
cwd: repository.repoRoot,
|
|
727
|
+
env: buildGitEnv(),
|
|
710
728
|
stdout: "pipe",
|
|
711
729
|
stderr: "pipe",
|
|
712
730
|
windowsHide: true,
|
|
@@ -715,6 +733,7 @@ function resolveHeadStateReftableSync(repository: GitRepository): GitHeadState |
|
|
|
715
733
|
const revArgs = withShortLivedGitConfig(withNoOptionalLocks(["rev-parse", "--verify", "HEAD"]));
|
|
716
734
|
const revResult = Bun.spawnSync(["git", ...revArgs], {
|
|
717
735
|
cwd: repository.repoRoot,
|
|
736
|
+
env: buildGitEnv(),
|
|
718
737
|
stdout: "pipe",
|
|
719
738
|
stderr: "pipe",
|
|
720
739
|
windowsHide: true,
|
|
@@ -748,6 +767,7 @@ function readRefSync(repository: GitRepository, targetRef: string): string | nul
|
|
|
748
767
|
const symArgs = withShortLivedGitConfig(withNoOptionalLocks(["symbolic-ref", targetRef]));
|
|
749
768
|
const symResult = Bun.spawnSync(["git", ...symArgs], {
|
|
750
769
|
cwd: repository.repoRoot,
|
|
770
|
+
env: buildGitEnv(),
|
|
751
771
|
stdout: "pipe",
|
|
752
772
|
stderr: "pipe",
|
|
753
773
|
windowsHide: true,
|
|
@@ -759,6 +779,7 @@ function readRefSync(repository: GitRepository, targetRef: string): string | nul
|
|
|
759
779
|
const revArgs = withShortLivedGitConfig(withNoOptionalLocks(["rev-parse", "--verify", targetRef]));
|
|
760
780
|
const revResult = Bun.spawnSync(["git", ...revArgs], {
|
|
761
781
|
cwd: repository.repoRoot,
|
|
782
|
+
env: buildGitEnv(),
|
|
762
783
|
stdout: "pipe",
|
|
763
784
|
stderr: "pipe",
|
|
764
785
|
windowsHide: true,
|
|
@@ -39,8 +39,7 @@ const DESCRIPTION_UNAVAILABLE_NOTE =
|
|
|
39
39
|
"[Image description unavailable: the vision model returned no usable text. The image was saved for further analysis.]";
|
|
40
40
|
|
|
41
41
|
/** Registry surface needed to resolve a vision model and authorize requests. */
|
|
42
|
-
export type VisionFallbackRegistry = Pick<ModelRegistry, "getAvailable" | "getApiKey" | "resolver"
|
|
43
|
-
Partial<Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">>;
|
|
42
|
+
export type VisionFallbackRegistry = Pick<ModelRegistry, "getAvailable" | "getApiKey" | "resolver">;
|
|
44
43
|
|
|
45
44
|
export interface DescribeAttachedImagesDeps {
|
|
46
45
|
/** Active (text-only) model the prompt is destined for. */
|
|
@@ -108,7 +107,7 @@ function resolveVisionModel(deps: DescribeAttachedImagesDeps): Model<Api> | unde
|
|
|
108
107
|
const resolvePattern = (pattern: string | undefined): Model<Api> | undefined => {
|
|
109
108
|
if (!pattern) return undefined;
|
|
110
109
|
const expanded = expandRoleAlias(pattern, deps.settings);
|
|
111
|
-
const model = resolveModelFromString(expanded, available, preferences
|
|
110
|
+
const model = resolveModelFromString(expanded, available, preferences);
|
|
112
111
|
return model?.input.includes("image") ? model : undefined;
|
|
113
112
|
};
|
|
114
113
|
return (
|
|
@@ -72,7 +72,7 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
|
|
|
72
72
|
const availableModels = registry.getAvailable();
|
|
73
73
|
if (availableModels.length === 0) return undefined;
|
|
74
74
|
|
|
75
|
-
const titleModel = resolveRoleSelection(["tiny", "commit", "smol"], settings, availableModels
|
|
75
|
+
const titleModel = resolveRoleSelection(["tiny", "commit", "smol"], settings, availableModels)?.model;
|
|
76
76
|
if (titleModel) return titleModel;
|
|
77
77
|
|
|
78
78
|
if (currentModel) return currentModel;
|