@oh-my-pi/pi-coding-agent 15.10.11 → 15.11.0
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 +103 -2
- package/dist/cli.js +5790 -5731
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +85 -34
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/modes/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +5 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +49 -32
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +46 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +12 -2
- package/dist/types/task/index.d.ts +13 -6
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +63 -51
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli-commands.ts +29 -0
- package/src/cli.ts +28 -15
- package/src/commands/launch.ts +4 -0
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/commit/model-selection.ts +3 -2
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/keybindings.ts +6 -1
- package/src/config/model-registry.ts +97 -30
- package/src/config/model-resolver.ts +60 -0
- package/src/config/settings-schema.ts +99 -55
- package/src/config/settings.ts +68 -3
- package/src/edit/hashline/execute.ts +39 -2
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/completion-bridge.ts +1 -0
- package/src/eval/js/shared/prelude.txt +1 -1
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -1
- package/src/eval/py/kernel.ts +31 -11
- package/src/eval/py/prelude.py +5 -6
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +82 -3
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +38 -13
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/shared-events.ts +2 -2
- package/src/hindsight/bank.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +26 -66
- package/src/memories/index.ts +2 -0
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +151 -4
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +19 -21
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/footer.ts +3 -1
- package/src/modes/components/status-line/component.ts +118 -34
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +65 -0
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +19 -2
- package/src/modes/controllers/mcp-command-controller.ts +38 -3
- package/src/modes/controllers/selector-controller.ts +21 -17
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +47 -22
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/theme/theme.ts +18 -5
- package/src/modes/types.ts +5 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +38 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +37 -10
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +422 -291
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +226 -10
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/system-prompt.ts +14 -0
- package/src/task/executor.ts +851 -461
- package/src/task/index.ts +721 -796
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +148 -63
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +82 -66
- package/src/thinking.ts +7 -0
- package/src/tiny/title-client.ts +34 -5
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +61 -10
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/image-gen.ts +11 -4
- package/src/tools/index.ts +17 -13
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
package/src/thinking.ts
CHANGED
|
@@ -71,6 +71,13 @@ export function toReasoningEffort(level: ThinkingLevel | undefined): Effort | un
|
|
|
71
71
|
return level;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* True when a selector explicitly requests provider-side reasoning disablement.
|
|
76
|
+
*/
|
|
77
|
+
export function shouldDisableReasoning(level: ThinkingLevel | undefined): boolean {
|
|
78
|
+
return level === ThinkingLevel.Off;
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
/**
|
|
75
82
|
* Resolves a selector against the current model while preserving explicit "off".
|
|
76
83
|
*/
|
package/src/tiny/title-client.ts
CHANGED
|
@@ -39,6 +39,17 @@ export interface TinyTitleDownloadOptions {
|
|
|
39
39
|
onProgress?: (event: TinyTitleProgressEvent) => void;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Per-request controls for {@link TinyTitleClient.generate}.
|
|
44
|
+
*
|
|
45
|
+
* Carries the optional abort signal and title-system-prompt override used by
|
|
46
|
+
* callers that customize automatic session-title generation.
|
|
47
|
+
*/
|
|
48
|
+
export interface TinyTitleGenerateOptions {
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
systemPrompt?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
// Cold-starting the worker subprocess from a compiled binary (decompress + module
|
|
43
54
|
// graph load) is slow on contended CI runners — the macos-15-intel release smoke
|
|
44
55
|
// blew past 5s while arm64/linux/win passed. The probe only needs to prove the
|
|
@@ -46,6 +57,14 @@ export interface TinyTitleDownloadOptions {
|
|
|
46
57
|
// generous bound removes the flake without weakening the check.
|
|
47
58
|
const SMOKE_TEST_TIMEOUT_MS = 30_000;
|
|
48
59
|
|
|
60
|
+
function normalizeTinyTitleGenerateOptions(
|
|
61
|
+
options: AbortSignal | TinyTitleGenerateOptions | undefined,
|
|
62
|
+
): TinyTitleGenerateOptions {
|
|
63
|
+
if (!options) return {};
|
|
64
|
+
if ("aborted" in options && "addEventListener" in options) return { signal: options };
|
|
65
|
+
return options;
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
/**
|
|
50
69
|
* Hidden subcommand on the main CLI that boots the tiny-model worker in the
|
|
51
70
|
* spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
|
|
@@ -295,9 +314,16 @@ export class TinyTitleClient {
|
|
|
295
314
|
return () => this.#progressListeners.delete(listener);
|
|
296
315
|
}
|
|
297
316
|
|
|
298
|
-
async generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null
|
|
317
|
+
async generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null>;
|
|
318
|
+
async generate(modelKey: string, message: string, options?: TinyTitleGenerateOptions): Promise<string | null>;
|
|
319
|
+
async generate(
|
|
320
|
+
modelKey: string,
|
|
321
|
+
message: string,
|
|
322
|
+
optionsOrSignal?: AbortSignal | TinyTitleGenerateOptions,
|
|
323
|
+
): Promise<string | null> {
|
|
324
|
+
const options = normalizeTinyTitleGenerateOptions(optionsOrSignal);
|
|
299
325
|
if (!isTinyTitleLocalModelKey(modelKey)) return null;
|
|
300
|
-
if (signal?.aborted) return null;
|
|
326
|
+
if (options.signal?.aborted) return null;
|
|
301
327
|
|
|
302
328
|
try {
|
|
303
329
|
const worker = this.#ensureWorker();
|
|
@@ -310,12 +336,15 @@ export class TinyTitleClient {
|
|
|
310
336
|
this.#pending.delete(id);
|
|
311
337
|
pending.resolve(null);
|
|
312
338
|
};
|
|
313
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
339
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
314
340
|
try {
|
|
315
|
-
|
|
341
|
+
const request: TinyTitleWorkerInbound = options.systemPrompt
|
|
342
|
+
? { type: "generate", id, modelKey, message, systemPrompt: options.systemPrompt }
|
|
343
|
+
: { type: "generate", id, modelKey, message };
|
|
344
|
+
worker.send(request);
|
|
316
345
|
return await promise;
|
|
317
346
|
} finally {
|
|
318
|
-
signal?.removeEventListener("abort", abort);
|
|
347
|
+
options.signal?.removeEventListener("abort", abort);
|
|
319
348
|
this.#pending.delete(id);
|
|
320
349
|
}
|
|
321
350
|
} catch (error) {
|
|
@@ -29,7 +29,7 @@ export interface TinyTitleProgressEvent {
|
|
|
29
29
|
|
|
30
30
|
export type TinyTitleWorkerInbound =
|
|
31
31
|
| { type: "ping"; id: string }
|
|
32
|
-
| { type: "generate"; id: string; modelKey: TinyTitleLocalModelKey; message: string }
|
|
32
|
+
| { type: "generate"; id: string; modelKey: TinyTitleLocalModelKey; message: string; systemPrompt?: string }
|
|
33
33
|
| { type: "complete"; id: string; modelKey: TinyLocalModelKey; prompt: string; maxTokens?: number }
|
|
34
34
|
| { type: "download"; id: string; modelKey: TinyLocalModelKey };
|
|
35
35
|
|
package/src/tiny/worker.ts
CHANGED
|
@@ -436,9 +436,10 @@ async function loadPipeline(
|
|
|
436
436
|
return loaded;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
function buildPrompt(generator: TextGenerationPipeline, message: string): string {
|
|
439
|
+
function buildPrompt(generator: TextGenerationPipeline, message: string, systemPrompt?: string): string {
|
|
440
|
+
const selectedSystemPrompt = systemPrompt?.trim() || TINY_TITLE_SYSTEM_PROMPT;
|
|
440
441
|
const chat = [
|
|
441
|
-
{ role: "system", content:
|
|
442
|
+
{ role: "system", content: selectedSystemPrompt },
|
|
442
443
|
{ role: "user", content: formatTitleUserMessage(message) },
|
|
443
444
|
];
|
|
444
445
|
const chatTemplateOptions = {
|
|
@@ -464,9 +465,10 @@ async function generateTitle(
|
|
|
464
465
|
requestId: string,
|
|
465
466
|
modelKey: TinyTitleLocalModelKey,
|
|
466
467
|
message: string,
|
|
468
|
+
systemPrompt?: string,
|
|
467
469
|
): Promise<string | null> {
|
|
468
470
|
const generator = await loadPipeline(modelKey, transport, requestId);
|
|
469
|
-
const promptText = buildPrompt(generator, message);
|
|
471
|
+
const promptText = buildPrompt(generator, message, systemPrompt);
|
|
470
472
|
const transformers = await loadTransformers(transport, requestId, modelKey);
|
|
471
473
|
const output = (await generator(promptText, {
|
|
472
474
|
max_new_tokens: TITLE_MAX_NEW_TOKENS,
|
|
@@ -548,7 +550,7 @@ async function handleQueuedRequest(
|
|
|
548
550
|
transport.send({ type: "completion", id: request.id, text });
|
|
549
551
|
return;
|
|
550
552
|
}
|
|
551
|
-
const title = await generateTitle(transport, request.id, request.modelKey, request.message);
|
|
553
|
+
const title = await generateTitle(transport, request.id, request.modelKey, request.message, request.systemPrompt);
|
|
552
554
|
transport.send({ type: "title", id: request.id, title });
|
|
553
555
|
} catch (error) {
|
|
554
556
|
transport.send({ type: "error", id: request.id, error: errorText(error) });
|
package/src/tools/ask.ts
CHANGED
|
@@ -104,7 +104,7 @@ const RECOMMENDED_SUFFIX = " (Recommended)";
|
|
|
104
104
|
const TIMEOUT_DETECTION_TOLERANCE_MS = 1_000;
|
|
105
105
|
|
|
106
106
|
function getDoneOptionLabel(): string {
|
|
107
|
-
return `${theme.
|
|
107
|
+
return `${theme.status.success} Done selecting`;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/** Add "(Recommended)" suffix to the option at the given index if not already present */
|
|
@@ -694,7 +694,9 @@ function normalizeRenderQuestions(raw: unknown): NonNullable<AskRenderArgs["ques
|
|
|
694
694
|
/** Render a custom free-text answer as a status line plus indented continuation rows. */
|
|
695
695
|
function renderCustomInputLines(uiTheme: Theme, customInput: string): string[] {
|
|
696
696
|
const lines = customInput.split("\n");
|
|
697
|
-
const out: string[] = [
|
|
697
|
+
const out: string[] = [
|
|
698
|
+
` ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", lines[0] ?? "")}`,
|
|
699
|
+
];
|
|
698
700
|
for (let i = 1; i < lines.length; i++) out.push(` ${uiTheme.fg("toolOutput", lines[i])}`);
|
|
699
701
|
return out;
|
|
700
702
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
|
17
17
|
import { highlightCode, type Theme } from "../modes/theme/theme";
|
|
18
18
|
import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
19
19
|
import type { ClientBridgeTerminalExitStatus, ClientBridgeTerminalOutput } from "../session/client-bridge";
|
|
20
|
-
import { DEFAULT_MAX_BYTES, streamTailUpdates, TailBuffer } from "../session/streaming-output";
|
|
20
|
+
import { DEFAULT_MAX_BYTES, enforceInlineByteCap, streamTailUpdates, TailBuffer } from "../session/streaming-output";
|
|
21
21
|
import { renderStatusLine } from "../tui";
|
|
22
22
|
import { CachedOutputBlock, markFramedBlockComponent } from "../tui/output-block";
|
|
23
23
|
import { getSixelLineMask } from "../utils/sixel";
|
|
@@ -429,7 +429,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
#buildCompletedResult(
|
|
432
|
+
async #buildCompletedResult(
|
|
433
433
|
result: BashResult | BashInteractiveResult,
|
|
434
434
|
timeoutSec: number,
|
|
435
435
|
options: {
|
|
@@ -438,7 +438,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
438
438
|
terminalId?: string;
|
|
439
439
|
wallTimeMs?: number;
|
|
440
440
|
} = {},
|
|
441
|
-
): AgentToolResult<BashToolDetails
|
|
441
|
+
): Promise<AgentToolResult<BashToolDetails>> {
|
|
442
442
|
const exitCode = result.exitCode;
|
|
443
443
|
const failedExit = exitCode !== undefined && exitCode !== 0;
|
|
444
444
|
|
|
@@ -472,7 +472,17 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
472
472
|
if (failedExit) {
|
|
473
473
|
details.exitCode = exitCode;
|
|
474
474
|
}
|
|
475
|
-
|
|
475
|
+
// Final defense at the tool-result boundary: no bash path (client bridge,
|
|
476
|
+
// head-retention spill, minimizer miss) may emit more than
|
|
477
|
+
// ~DEFAULT_MAX_BYTES inline. No-op for already-bounded output.
|
|
478
|
+
const cappedOutputText = await enforceInlineByteCap(outputText, {
|
|
479
|
+
label: "bash output",
|
|
480
|
+
saveArtifact: full => saveBashOriginalArtifact(this.session, full),
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const resultBuilder = toolResult(details)
|
|
484
|
+
.text(cappedOutputText)
|
|
485
|
+
.truncationFromSummary(result, { direction: "tail" });
|
|
476
486
|
if (failedExit) resultBuilder.error();
|
|
477
487
|
return resultBuilder.done();
|
|
478
488
|
}
|
|
@@ -560,7 +570,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
560
570
|
onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
|
|
561
571
|
});
|
|
562
572
|
const wallTimeMs = performance.now() - wallTimeStart;
|
|
563
|
-
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
573
|
+
const finalResult = await this.#buildCompletedResult(result, options.timeoutSec, {
|
|
564
574
|
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
565
575
|
notices: options.notices ?? [],
|
|
566
576
|
wallTimeMs,
|
|
@@ -1212,6 +1222,22 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1212
1222
|
const details = result.details;
|
|
1213
1223
|
const outputBlock = new CachedOutputBlock();
|
|
1214
1224
|
|
|
1225
|
+
// Per-instance cache for the expensive inner lines computation. Mirrors
|
|
1226
|
+
// the eval-renderer pattern (`eval-render.ts:709-752`): without this,
|
|
1227
|
+
// every TUI repaint (one per keystroke when a long transcript is on
|
|
1228
|
+
// screen) re-runs `split` / `replaceTabs` / `truncateToVisualLines` over
|
|
1229
|
+
// the whole stored output for every bash row in scrollback. With a
|
|
1230
|
+
// 50KB-tail bash result times hundreds of rows, that re-rendering is
|
|
1231
|
+
// what pinned the main thread in issue #2081 and made keystrokes feel
|
|
1232
|
+
// like the CPU was at 100%. The cache key includes every render input
|
|
1233
|
+
// that materially affects the produced lines.
|
|
1234
|
+
let cachedWidth: number | undefined;
|
|
1235
|
+
let cachedPreviewLines: number | undefined;
|
|
1236
|
+
let cachedExpanded: boolean | undefined;
|
|
1237
|
+
let cachedRawOutput: string | undefined;
|
|
1238
|
+
let cachedIsPartial: boolean | undefined;
|
|
1239
|
+
let cachedLines: readonly string[] | undefined;
|
|
1240
|
+
|
|
1215
1241
|
return markFramedBlockComponent({
|
|
1216
1242
|
render: (width: number): readonly string[] => {
|
|
1217
1243
|
// REACTIVE: read mutable options at render time
|
|
@@ -1223,6 +1249,19 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1223
1249
|
// Strip the LLM-facing notice appended by wrappedExecute so we don't
|
|
1224
1250
|
// double-print it alongside the styled warning line below.
|
|
1225
1251
|
const rawOutput = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1252
|
+
|
|
1253
|
+
const isPartial = options.isPartial === true;
|
|
1254
|
+
|
|
1255
|
+
if (
|
|
1256
|
+
cachedLines !== undefined &&
|
|
1257
|
+
cachedWidth === width &&
|
|
1258
|
+
cachedPreviewLines === previewLines &&
|
|
1259
|
+
cachedExpanded === expanded &&
|
|
1260
|
+
cachedRawOutput === rawOutput &&
|
|
1261
|
+
cachedIsPartial === isPartial
|
|
1262
|
+
) {
|
|
1263
|
+
return cachedLines;
|
|
1264
|
+
}
|
|
1226
1265
|
const strippedOutput = stripOutputNotice(rawOutput, details?.meta);
|
|
1227
1266
|
const withoutExit = stripExitCodeNotice(strippedOutput, details?.exitCode);
|
|
1228
1267
|
const withoutWall = stripWallTimeNotice(withoutExit, details?.wallTimeMs);
|
|
@@ -1299,15 +1338,13 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1299
1338
|
if (timeoutLine) outputLines.push(timeoutLine);
|
|
1300
1339
|
if (warningLine) outputLines.push(warningLine);
|
|
1301
1340
|
|
|
1302
|
-
|
|
1341
|
+
const framed = outputBlock.render(
|
|
1303
1342
|
{
|
|
1304
1343
|
header,
|
|
1305
|
-
state:
|
|
1344
|
+
state: isPartial ? "pending" : isError ? "error" : "success",
|
|
1306
1345
|
sections: [
|
|
1307
1346
|
{
|
|
1308
|
-
lines:
|
|
1309
|
-
? capPreviewLines(cmdLines ?? [], uiTheme, { expanded })
|
|
1310
|
-
: (cmdLines ?? []),
|
|
1347
|
+
lines: isPartial ? capPreviewLines(cmdLines ?? [], uiTheme, { expanded }) : (cmdLines ?? []),
|
|
1311
1348
|
},
|
|
1312
1349
|
{ label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
|
|
1313
1350
|
],
|
|
@@ -1315,9 +1352,23 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1315
1352
|
},
|
|
1316
1353
|
uiTheme,
|
|
1317
1354
|
);
|
|
1355
|
+
|
|
1356
|
+
cachedWidth = width;
|
|
1357
|
+
cachedPreviewLines = previewLines;
|
|
1358
|
+
cachedExpanded = expanded;
|
|
1359
|
+
cachedRawOutput = rawOutput;
|
|
1360
|
+
cachedIsPartial = isPartial;
|
|
1361
|
+
cachedLines = framed;
|
|
1362
|
+
return framed;
|
|
1318
1363
|
},
|
|
1319
1364
|
invalidate: () => {
|
|
1320
1365
|
outputBlock.invalidate();
|
|
1366
|
+
cachedLines = undefined;
|
|
1367
|
+
cachedWidth = undefined;
|
|
1368
|
+
cachedPreviewLines = undefined;
|
|
1369
|
+
cachedExpanded = undefined;
|
|
1370
|
+
cachedRawOutput = undefined;
|
|
1371
|
+
cachedIsPartial = undefined;
|
|
1321
1372
|
},
|
|
1322
1373
|
});
|
|
1323
1374
|
},
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ElementHandle,
|
|
11
11
|
ElementScreenshotOptions,
|
|
12
12
|
HTTPResponse,
|
|
13
|
+
ImageFormat,
|
|
13
14
|
KeyInput,
|
|
14
15
|
Page,
|
|
15
16
|
SerializedAXNode,
|
|
@@ -436,6 +437,19 @@ export function describeScreenshot(opts?: ScreenshotOptions): string {
|
|
|
436
437
|
return "tab.screenshot()";
|
|
437
438
|
}
|
|
438
439
|
|
|
440
|
+
/** Map an explicit save path's extension to a puppeteer capture format (default png). */
|
|
441
|
+
export function imageFormatForPath(filePath: string): ImageFormat {
|
|
442
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
443
|
+
case ".webp":
|
|
444
|
+
return "webp";
|
|
445
|
+
case ".jpg":
|
|
446
|
+
case ".jpeg":
|
|
447
|
+
return "jpeg";
|
|
448
|
+
default:
|
|
449
|
+
return "png";
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
439
453
|
/** Summarize still-running helpers (oldest first) so a cell timeout names what stalled. */
|
|
440
454
|
export function describeInflight(inflight: Map<number, InflightOp>): string {
|
|
441
455
|
const now = Date.now();
|
|
@@ -931,6 +945,12 @@ export class WorkerCore {
|
|
|
931
945
|
): Promise<ScreenshotResult> {
|
|
932
946
|
const page = this.#requirePage();
|
|
933
947
|
const fullPage = opts.selector ? false : (opts.fullPage ?? false);
|
|
948
|
+
// An explicit save path picks the full-res capture format: puppeteer encodes
|
|
949
|
+
// png/jpeg/webp natively, so `save: "shot.webp"` gets real WebP bytes instead
|
|
950
|
+
// of PNG bytes hiding behind a .webp name. Unknown/missing extensions stay PNG.
|
|
951
|
+
const explicitPath = opts.save ? resolveToCwd(opts.save, session.cwd) : undefined;
|
|
952
|
+
const captureType = explicitPath ? imageFormatForPath(explicitPath) : "png";
|
|
953
|
+
const captureMime = `image/${captureType}` as const;
|
|
934
954
|
let buffer: Buffer;
|
|
935
955
|
if (opts.selector) {
|
|
936
956
|
const handle = (await untilAborted(signal, () =>
|
|
@@ -951,24 +971,23 @@ export class WorkerCore {
|
|
|
951
971
|
).catch(() => undefined);
|
|
952
972
|
// scrollIntoView:false skips the same IntersectionObserver check inside screenshot();
|
|
953
973
|
// captureBeyondViewport (puppeteer's default) still renders the clipped region.
|
|
954
|
-
const shotOpts: ElementScreenshotOptions = { type:
|
|
974
|
+
const shotOpts: ElementScreenshotOptions = { type: captureType, scrollIntoView: false };
|
|
955
975
|
buffer = (await untilAborted(signal, () => handle.screenshot(shotOpts))) as Buffer;
|
|
956
976
|
} finally {
|
|
957
977
|
await handle.dispose().catch(() => undefined);
|
|
958
978
|
}
|
|
959
979
|
} else {
|
|
960
|
-
buffer = (await untilAborted(signal, () => page.screenshot({ type:
|
|
980
|
+
buffer = (await untilAborted(signal, () => page.screenshot({ type: captureType, fullPage }))) as Buffer;
|
|
961
981
|
}
|
|
962
982
|
const resized = await resizeImage(
|
|
963
|
-
{ type: "image", data: buffer.toBase64(), mimeType:
|
|
983
|
+
{ type: "image", data: buffer.toBase64(), mimeType: captureMime },
|
|
964
984
|
{ maxWidth: 1024, maxHeight: 1024, maxBytes: 150 * 1024, jpegQuality: 70 },
|
|
965
985
|
);
|
|
966
|
-
const explicitPath = opts.save ? resolveToCwd(opts.save, session.cwd) : undefined;
|
|
967
986
|
const saveFullRes = !!(explicitPath || session.browserScreenshotDir);
|
|
968
987
|
const savedBuffer = saveFullRes ? buffer : resized.buffer;
|
|
969
|
-
const savedMimeType = saveFullRes ?
|
|
970
|
-
//
|
|
971
|
-
//
|
|
988
|
+
const savedMimeType = saveFullRes ? captureMime : resized.mimeType;
|
|
989
|
+
// Names must match the bytes we actually write: full-res follows the capture
|
|
990
|
+
// format, the resized buffer is whichever of PNG/JPEG/WebP encoded smallest.
|
|
972
991
|
const ext = savedMimeType === "image/webp" ? "webp" : savedMimeType === "image/jpeg" ? "jpg" : "png";
|
|
973
992
|
const dest =
|
|
974
993
|
explicitPath ??
|
package/src/tools/browser.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
5
5
|
import type { ToolSession } from "../sdk";
|
|
6
|
+
import { enforceInlineByteCap } from "../session/streaming-output";
|
|
6
7
|
import { truncateForPrompt } from "./approval";
|
|
7
8
|
import { acquireBrowser, type BrowserHandle, type BrowserKind, type BrowserKindTag } from "./browser/registry";
|
|
8
9
|
import type { Observation, ScreenshotResult } from "./browser/tab-protocol";
|
|
@@ -271,11 +272,37 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
271
272
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
272
273
|
.map(c => c.text)
|
|
273
274
|
.join("\n");
|
|
274
|
-
|
|
275
|
+
// Final defense at the tool-result boundary: a single run can display
|
|
276
|
+
// tens of KB (large JSON returns, dumped observations). Cap the combined
|
|
277
|
+
// text inline; the full text stays recoverable via the artifact footer
|
|
278
|
+
// when allocation succeeds.
|
|
279
|
+
const cappedText = await enforceInlineByteCap(textOnly, {
|
|
280
|
+
label: "browser output",
|
|
281
|
+
saveArtifact: full => saveBrowserOutputArtifact(this.session, full),
|
|
282
|
+
});
|
|
283
|
+
details.result = cappedText;
|
|
284
|
+
if (cappedText !== textOnly) {
|
|
285
|
+
const nonText = content.filter(c => c.type !== "text");
|
|
286
|
+
return toolResult(details)
|
|
287
|
+
.content([...nonText, { type: "text", text: cappedText }])
|
|
288
|
+
.done();
|
|
289
|
+
}
|
|
275
290
|
return toolResult(details).content(content).done();
|
|
276
291
|
}
|
|
277
292
|
}
|
|
278
293
|
|
|
294
|
+
/** Persist over-cap browser run output as a session artifact; mirrors the bash minimizer's save path. */
|
|
295
|
+
async function saveBrowserOutputArtifact(session: ToolSession, fullText: string): Promise<string | undefined> {
|
|
296
|
+
try {
|
|
297
|
+
const alloc = await session.allocateOutputArtifact?.("browser-original");
|
|
298
|
+
if (!alloc?.path || !alloc.id) return undefined;
|
|
299
|
+
await Bun.write(alloc.path, fullText);
|
|
300
|
+
return alloc.id;
|
|
301
|
+
} catch {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
279
306
|
function describeBrowser(handle: BrowserHandle): string {
|
|
280
307
|
switch (handle.kind.kind) {
|
|
281
308
|
case "headless":
|
package/src/tools/find.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
4
4
|
import * as natives from "@oh-my-pi/pi-natives";
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
7
|
-
import { isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { formatGroupedPaths, isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import * as z from "zod/v4";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import { InternalUrlRouter } from "../internal-urls";
|
|
@@ -13,7 +13,6 @@ import findDescription from "../prompts/tools/find.md" with { type: "text" };
|
|
|
13
13
|
import { type TruncationResult, truncateHead } from "../session/streaming-output";
|
|
14
14
|
import { Ellipsis, fileHyperlink, renderFileList, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
|
-
import { buildPathTree, walkPathTree } from "./grouped-file-output";
|
|
17
16
|
import { applyListLimit } from "./list-limit";
|
|
18
17
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
19
18
|
import {
|
|
@@ -54,30 +53,6 @@ const DEFAULT_GLOB_TIMEOUT_MS = 5000;
|
|
|
54
53
|
const MIN_GLOB_TIMEOUT_MS = 500;
|
|
55
54
|
const MAX_GLOB_TIMEOUT_MS = 60_000;
|
|
56
55
|
|
|
57
|
-
/**
|
|
58
|
-
* Group find matches into a multi-level directory tree so the model doesn't pay
|
|
59
|
-
* repeated tokens for shared path prefixes. Single-child directory chains fold
|
|
60
|
-
* into one header (`# a/b/c/`), so a common prefix — including an absolute root
|
|
61
|
-
* for out-of-cwd results — collapses to a single line. Each level adds one `#`;
|
|
62
|
-
* files are listed bare under the deepest directory header that owns them.
|
|
63
|
-
*
|
|
64
|
-
* Order follows the input (mtime-desc for native glob): a directory appears when
|
|
65
|
-
* its first member is emitted, and a node's own files precede its subdirectories.
|
|
66
|
-
*/
|
|
67
|
-
export function formatFindGroupedOutput(paths: readonly string[]): string {
|
|
68
|
-
if (paths.length === 0) return "";
|
|
69
|
-
const tree = buildPathTree(paths.map(entry => ({ path: entry, isDir: entry.endsWith("/") })));
|
|
70
|
-
const lines: string[] = [];
|
|
71
|
-
for (const event of walkPathTree(tree)) {
|
|
72
|
-
if (event.kind === "dir") {
|
|
73
|
-
lines.push(`${"#".repeat(event.depth + 1)} ${event.name}/`);
|
|
74
|
-
} else {
|
|
75
|
-
lines.push(event.name);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return lines.join("\n");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
56
|
export interface FindToolDetails {
|
|
82
57
|
truncation?: TruncationResult;
|
|
83
58
|
resultLimitReached?: number;
|
|
@@ -270,7 +245,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
270
245
|
const listLimit = applyListLimit(files, { limit: effectiveLimit });
|
|
271
246
|
const limited = listLimit.items;
|
|
272
247
|
const limitMeta = listLimit.meta;
|
|
273
|
-
const baseOutput =
|
|
248
|
+
const baseOutput = formatGroupedPaths(limited);
|
|
274
249
|
const trailingNotes: string[] = [];
|
|
275
250
|
if (notice) trailingNotes.push(notice);
|
|
276
251
|
if (missingPathsNote) trailingNotes.push(missingPathsNote);
|
|
@@ -1,123 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function isUrlLikePath(filePath: string): boolean {
|
|
6
|
-
return URL_LIKE_PATH_RE.test(filePath);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// =============================================================================
|
|
10
|
-
// Multi-level path tree
|
|
11
|
-
// =============================================================================
|
|
12
|
-
//
|
|
13
|
-
// File listings (grep / ast-grep / ast-edit / lsp diagnostics / find) used to
|
|
14
|
-
// group by the *immediate* parent directory and print the full directory path in
|
|
15
|
-
// every header. For results spread across a deep tree — or rooted outside cwd,
|
|
16
|
-
// where paths stay absolute — that repeated the shared prefix on every line. The
|
|
17
|
-
// tree below folds single-child directory chains (so the common prefix collapses
|
|
18
|
-
// into one header) and nests the rest, charging the model one token per path
|
|
19
|
-
// segment instead of one per file.
|
|
20
|
-
|
|
21
|
-
interface PathTreeNode {
|
|
22
|
-
/** Direct file leaves, in first-seen order. */
|
|
23
|
-
files: Array<{ name: string; key: string }>;
|
|
24
|
-
/** Dedup set for `files` (a glob can surface the same path twice on retry). */
|
|
25
|
-
fileNames: Set<string>;
|
|
26
|
-
/** Child directories, in first-seen order. */
|
|
27
|
-
subdirs: Array<{ name: string; node: PathTreeNode }>;
|
|
28
|
-
/** Dedup index for `subdirs`. */
|
|
29
|
-
dirIndex: Map<string, PathTreeNode>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface PathTreeInput {
|
|
33
|
-
/** Path string; absolute, cwd-relative, or url-like. Backslashes are normalized. */
|
|
34
|
-
path: string;
|
|
35
|
-
/** Whether the leaf itself is a directory (trailing-slash match from find). */
|
|
36
|
-
isDir: boolean;
|
|
37
|
-
/** Opaque key carried onto file events for section lookup. Defaults to `path`. */
|
|
38
|
-
key?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** One node emitted while walking the tree: a folded directory or a file leaf. */
|
|
42
|
-
export interface GroupedTreeEvent {
|
|
43
|
-
kind: "dir" | "file";
|
|
44
|
-
/** 0-based nesting depth (root children are depth 0). */
|
|
45
|
-
depth: number;
|
|
46
|
-
/** Folded chain for dirs (e.g. `a/b/c`, no trailing slash); basename for files. */
|
|
47
|
-
name: string;
|
|
48
|
-
/** File key for `kind === "file"`; empty string for directories. */
|
|
49
|
-
key: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function createNode(): PathTreeNode {
|
|
53
|
-
return { files: [], fileNames: new Set(), subdirs: [], dirIndex: new Map() };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function addFile(node: PathTreeNode, name: string, key: string): void {
|
|
57
|
-
if (node.fileNames.has(name)) return;
|
|
58
|
-
node.fileNames.add(name);
|
|
59
|
-
node.files.push({ name, key });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Build a directory tree from a flat list of paths. URL-like entries are kept
|
|
64
|
-
* whole as root-level file leaves (they have no meaningful directory structure).
|
|
65
|
-
* Absolute paths carry a leading empty segment so they share a common `/` root
|
|
66
|
-
* and fold like any other prefix.
|
|
67
|
-
*/
|
|
68
|
-
export function buildPathTree(entries: Iterable<PathTreeInput>): PathTreeNode {
|
|
69
|
-
const root = createNode();
|
|
70
|
-
for (const { path: rawPath, isDir, key } of entries) {
|
|
71
|
-
const normalized = rawPath.replace(/\\/g, "/");
|
|
72
|
-
const fileKey = key ?? rawPath;
|
|
73
|
-
if (isUrlLikePath(normalized)) {
|
|
74
|
-
addFile(root, normalized, fileKey);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const trimmed = normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
78
|
-
if (trimmed.length === 0) continue;
|
|
79
|
-
const segments = trimmed.split("/");
|
|
80
|
-
const dirCount = isDir ? segments.length : segments.length - 1;
|
|
81
|
-
let node = root;
|
|
82
|
-
for (let i = 0; i < dirCount; i++) {
|
|
83
|
-
const segment = segments[i]!;
|
|
84
|
-
let child = node.dirIndex.get(segment);
|
|
85
|
-
if (!child) {
|
|
86
|
-
child = createNode();
|
|
87
|
-
node.dirIndex.set(segment, child);
|
|
88
|
-
node.subdirs.push({ name: segment, node: child });
|
|
89
|
-
}
|
|
90
|
-
node = child;
|
|
91
|
-
}
|
|
92
|
-
if (!isDir) {
|
|
93
|
-
addFile(node, segments[segments.length - 1]!, fileKey);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return root;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Depth-first walk yielding directory and file events. Directories collapse their
|
|
101
|
-
* single-child chains (`a` → `a/b` → `a/b/c`) so a shared prefix becomes one
|
|
102
|
-
* header. Each node's direct files are emitted before its subdirectories, keeping
|
|
103
|
-
* a file unambiguously attached to the header above it.
|
|
104
|
-
*/
|
|
105
|
-
export function* walkPathTree(node: PathTreeNode, depth = 0): Generator<GroupedTreeEvent> {
|
|
106
|
-
for (const file of node.files) {
|
|
107
|
-
yield { kind: "file", depth, name: file.name, key: file.key };
|
|
108
|
-
}
|
|
109
|
-
for (const subdir of node.subdirs) {
|
|
110
|
-
let dirNode = subdir.node;
|
|
111
|
-
const parts = [subdir.name];
|
|
112
|
-
while (dirNode.files.length === 0 && dirNode.subdirs.length === 1) {
|
|
113
|
-
const only = dirNode.subdirs[0]!;
|
|
114
|
-
parts.push(only.name);
|
|
115
|
-
dirNode = only.node;
|
|
116
|
-
}
|
|
117
|
-
yield { kind: "dir", depth, name: parts.join("/"), key: "" };
|
|
118
|
-
yield* walkPathTree(dirNode, depth + 1);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
3
|
+
import { buildPathTree, isUrlLikePath, type PathTreeInput, walkPathTree } from "@oh-my-pi/pi-utils";
|
|
121
4
|
|
|
122
5
|
// =============================================================================
|
|
123
6
|
// Grouped file output (grep / ast-grep / ast-edit / lsp diagnostics)
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -472,8 +472,13 @@ function parseAntigravityCredentials(raw: string): ParsedAntigravityCredentials
|
|
|
472
472
|
return null;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
async function findAntigravityCredentials(
|
|
476
|
-
|
|
475
|
+
async function findAntigravityCredentials(
|
|
476
|
+
modelRegistry: ModelRegistry,
|
|
477
|
+
sessionId?: string,
|
|
478
|
+
): Promise<ImageApiKey | null> {
|
|
479
|
+
const apiKey = await modelRegistry.getApiKeyForProvider("google-antigravity", sessionId, {
|
|
480
|
+
modelId: DEFAULT_ANTIGRAVITY_MODEL,
|
|
481
|
+
});
|
|
477
482
|
if (!apiKey) return null;
|
|
478
483
|
|
|
479
484
|
const parsed = parseAntigravityCredentials(apiKey);
|
|
@@ -523,7 +528,7 @@ async function findImageApiKey(
|
|
|
523
528
|
if (openAI) return openAI;
|
|
524
529
|
// Fall through to auto-detect if preferred provider key not found.
|
|
525
530
|
} else if (preferredImageProvider === "antigravity" && modelRegistry) {
|
|
526
|
-
const antigravity = await findAntigravityCredentials(modelRegistry);
|
|
531
|
+
const antigravity = await findAntigravityCredentials(modelRegistry, sessionId);
|
|
527
532
|
if (antigravity) return antigravity;
|
|
528
533
|
// Fall through to auto-detect if preferred provider key not found.
|
|
529
534
|
} else if (preferredImageProvider === "gemini") {
|
|
@@ -547,7 +552,7 @@ async function findImageApiKey(
|
|
|
547
552
|
if (openAI) return openAI;
|
|
548
553
|
|
|
549
554
|
if (modelRegistry) {
|
|
550
|
-
const antigravity = await findAntigravityCredentials(modelRegistry);
|
|
555
|
+
const antigravity = await findAntigravityCredentials(modelRegistry, sessionId);
|
|
551
556
|
if (antigravity) return antigravity;
|
|
552
557
|
}
|
|
553
558
|
|
|
@@ -1052,6 +1057,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1052
1057
|
const hostedKey: ApiKey = ctx.modelRegistry.resolver(hostedModel.provider, {
|
|
1053
1058
|
sessionId,
|
|
1054
1059
|
baseUrl: hostedModel.baseUrl,
|
|
1060
|
+
modelId: hostedModel.id,
|
|
1055
1061
|
});
|
|
1056
1062
|
|
|
1057
1063
|
const parsed = await withAuth(
|
|
@@ -1113,6 +1119,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1113
1119
|
const prompt = assemblePrompt(params);
|
|
1114
1120
|
const antigravityKey: ApiKey = ctx.modelRegistry.resolver("google-antigravity", {
|
|
1115
1121
|
sessionId,
|
|
1122
|
+
modelId: DEFAULT_ANTIGRAVITY_MODEL,
|
|
1116
1123
|
});
|
|
1117
1124
|
|
|
1118
1125
|
const response = await withAuth(
|