@oh-my-pi/pi-coding-agent 15.0.0 → 15.0.2
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 +79 -0
- package/examples/extensions/plan-mode.ts +0 -1
- package/package.json +10 -10
- package/scripts/build-binary.ts +5 -0
- package/src/autoresearch/helpers.ts +17 -0
- package/src/autoresearch/tools/log-experiment.ts +9 -17
- package/src/autoresearch/tools/run-experiment.ts +2 -17
- package/src/capability/skill.ts +7 -0
- package/src/cli/list-models.ts +1 -1
- package/src/cli/shell-cli.ts +3 -13
- package/src/cli/update-cli.ts +1 -1
- package/src/cli.ts +10 -29
- package/src/commands/commit.ts +10 -0
- package/src/commit/agentic/tools/propose-changelog.ts +8 -1
- package/src/commit/analysis/conventional.ts +8 -66
- package/src/commit/map-reduce/reduce-phase.ts +6 -65
- package/src/commit/pipeline.ts +2 -2
- package/src/commit/shared-llm.ts +89 -0
- package/src/config/config-file.ts +210 -0
- package/src/config/model-equivalence.ts +8 -11
- package/src/config/model-registry.ts +44 -3
- package/src/config/model-resolver.ts +1 -4
- package/src/config/settings-schema.ts +82 -1
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -219
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/edit/renderer.ts +7 -1
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/shared/rewrite-imports.ts +2 -2
- package/src/eval/py/executor.ts +5 -0
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/exa/factory.ts +2 -2
- package/src/exa/mcp-client.ts +74 -1
- package/src/exec/bash-executor.ts +5 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -11
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +89 -223
- package/src/extensibility/hooks/types.ts +89 -314
- package/src/extensibility/plugins/legacy-pi-compat.ts +48 -31
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +9 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +500 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +237 -0
- package/src/hashline/anchors.ts +2 -2
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/agent-protocol.ts +1 -20
- package/src/internal-urls/artifact-protocol.ts +1 -19
- package/src/internal-urls/docs-index.generated.ts +11 -12
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/main.ts +11 -2
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/modes/acp/acp-agent.ts +327 -95
- package/src/modes/components/assistant-message.ts +14 -8
- package/src/modes/components/bash-execution.ts +24 -63
- package/src/modes/components/custom-message.ts +14 -40
- package/src/modes/components/eval-execution.ts +27 -57
- package/src/modes/components/execution-shared.ts +102 -0
- package/src/modes/components/hook-message.ts +17 -49
- package/src/modes/components/mcp-add-wizard.ts +26 -5
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +6 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +93 -8
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +28 -10
- package/src/modes/components/tool-execution.ts +7 -8
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +13 -4
- package/src/modes/controllers/event-controller.ts +36 -7
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +13 -0
- package/src/modes/controllers/mcp-command-controller.ts +56 -61
- package/src/modes/controllers/ssh-command-controller.ts +18 -57
- package/src/modes/interactive-mode.ts +624 -52
- package/src/modes/print-mode.ts +16 -86
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-mode.ts +41 -88
- package/src/modes/rpc/rpc-types.ts +57 -0
- package/src/modes/runtime-init.ts +116 -0
- package/src/modes/theme/defaults/dark-poimandres.json +3 -0
- package/src/modes/theme/defaults/light-poimandres.json +3 -0
- package/src/modes/theme/theme.ts +24 -6
- package/src/modes/types.ts +14 -3
- package/src/modes/utils/context-usage.ts +13 -13
- package/src/modes/utils/ui-helpers.ts +10 -3
- package/src/plan-mode/approved-plan.ts +35 -1
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/system/plan-mode-active.md +5 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/tools/bash.md +6 -0
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/goal.md +13 -0
- package/src/prompts/tools/hashline.md +101 -117
- package/src/prompts/tools/read.md +55 -36
- package/src/prompts/tools/resolve.md +6 -5
- package/src/sdk.ts +12 -5
- package/src/session/agent-session.ts +428 -106
- package/src/session/blob-store.ts +36 -3
- package/src/session/messages.ts +67 -2
- package/src/session/session-manager.ts +131 -12
- package/src/session/session-storage.ts +33 -15
- package/src/session/streaming-output.ts +309 -13
- package/src/slash-commands/builtin-registry.ts +18 -0
- package/src/ssh/ssh-executor.ts +5 -0
- package/src/system-prompt.ts +4 -2
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +19 -8
- package/src/task/index.ts +3 -0
- package/src/task/render.ts +21 -15
- package/src/task/types.ts +4 -0
- package/src/tools/ast-edit.ts +21 -120
- package/src/tools/ast-grep.ts +21 -119
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash-interactive.ts +9 -1
- package/src/tools/bash.ts +66 -19
- package/src/tools/browser/attach.ts +3 -3
- package/src/tools/browser/launch.ts +81 -18
- package/src/tools/browser/registry.ts +1 -5
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +51 -14
- package/src/tools/conflict-detect.ts +15 -4
- package/src/tools/eval.ts +12 -2
- package/src/tools/find.ts +20 -38
- package/src/tools/gh.ts +44 -10
- package/src/tools/index.ts +22 -11
- package/src/tools/inspect-image.ts +3 -10
- package/src/tools/job.ts +16 -7
- package/src/tools/output-meta.ts +202 -37
- package/src/tools/path-utils.ts +125 -2
- package/src/tools/read.ts +548 -237
- package/src/tools/render-utils.ts +92 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +72 -44
- package/src/tools/search.ts +120 -186
- package/src/tools/ssh.ts +3 -2
- package/src/tools/write.ts +64 -9
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/image-loading.ts +7 -3
- package/src/utils/image-resize.ts +32 -43
- package/src/vim/parser.ts +0 -17
- package/src/vim/render.ts +1 -1
- package/src/vim/types.ts +1 -1
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +40 -95
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
- package/src/prompts/tools/exit-plan-mode.md +0 -6
- package/src/tools/exit-plan-mode.ts +0 -97
- package/src/utils/fuzzy.ts +0 -108
- package/src/utils/image-convert.ts +0 -27
package/src/tools/bash.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { type BashResult, executeBash } from "../exec/bash-executor";
|
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import { InternalUrlRouter } from "../internal-urls";
|
|
11
11
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
12
|
-
import type
|
|
12
|
+
import { highlightCode, type Theme } from "../modes/theme/theme";
|
|
13
13
|
import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
14
14
|
import type { ClientBridgeTerminalExitStatus, ClientBridgeTerminalOutput } from "../session/client-bridge";
|
|
15
15
|
import { DEFAULT_MAX_BYTES, streamTailUpdates, TailBuffer } from "../session/streaming-output";
|
|
@@ -17,10 +17,11 @@ import { renderStatusLine } from "../tui";
|
|
|
17
17
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
18
18
|
import { getSixelLineMask } from "../utils/sixel";
|
|
19
19
|
import type { ToolSession } from ".";
|
|
20
|
+
import { applyBashFixups, formatBashFixupNotice } from "./bash-command-fixup";
|
|
20
21
|
import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
|
|
21
22
|
import { checkBashInterception } from "./bash-interceptor";
|
|
22
23
|
import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-skill-urls";
|
|
23
|
-
import { formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
24
|
+
import { formatStyledTruncationWarning, type OutputMeta, stripOutputNotice } from "./output-meta";
|
|
24
25
|
import { resolveToCwd } from "./path-utils";
|
|
25
26
|
import { formatToolWorkingDirectory, replaceTabs } from "./render-utils";
|
|
26
27
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
@@ -245,6 +246,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
245
246
|
readonly #asyncEnabled: boolean;
|
|
246
247
|
readonly #autoBackgroundEnabled: boolean;
|
|
247
248
|
readonly #autoBackgroundThresholdMs: number;
|
|
249
|
+
#bashFixupNoticeEmitted = false;
|
|
248
250
|
|
|
249
251
|
constructor(private readonly session: ToolSession) {
|
|
250
252
|
this.#asyncEnabled = this.session.settings.get("async.enabled");
|
|
@@ -291,7 +293,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
291
293
|
#buildCompletedResult(
|
|
292
294
|
result: BashResult | BashInteractiveResult,
|
|
293
295
|
timeoutSec: number,
|
|
294
|
-
options: { requestedTimeoutSec?: number; notices?: string[]; terminalId?: string } = {},
|
|
296
|
+
options: { requestedTimeoutSec?: number; notices?: readonly string[]; terminalId?: string } = {},
|
|
295
297
|
): AgentToolResult<BashToolDetails> {
|
|
296
298
|
const outputLines = [this.#formatResultOutput(result)];
|
|
297
299
|
const notices = options.notices?.filter(Boolean) ?? [];
|
|
@@ -314,7 +316,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
314
316
|
label: string,
|
|
315
317
|
previewText: string,
|
|
316
318
|
timeoutSec: number,
|
|
317
|
-
options: { requestedTimeoutSec?: number; notices?: string[] } = {},
|
|
319
|
+
options: { requestedTimeoutSec?: number; notices?: readonly string[] } = {},
|
|
318
320
|
): AgentToolResult<BashToolDetails> {
|
|
319
321
|
const details: BashToolDetails = {
|
|
320
322
|
timeoutSeconds: timeoutSec,
|
|
@@ -352,7 +354,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
352
354
|
timeoutMs: number;
|
|
353
355
|
timeoutSec: number;
|
|
354
356
|
requestedTimeoutSec?: number;
|
|
355
|
-
|
|
357
|
+
notices?: readonly string[];
|
|
356
358
|
|
|
357
359
|
resolvedEnv?: Record<string, string>;
|
|
358
360
|
onUpdate?: AgentToolUpdateCallback<BashToolDetails>;
|
|
@@ -392,7 +394,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
392
394
|
});
|
|
393
395
|
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
394
396
|
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
395
|
-
notices:
|
|
397
|
+
notices: options.notices ?? [],
|
|
396
398
|
});
|
|
397
399
|
const finalText = this.#extractTextResult(finalResult);
|
|
398
400
|
latestText = finalText;
|
|
@@ -483,9 +485,23 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
483
485
|
let command = rawCommand;
|
|
484
486
|
const env = normalizeBashEnv(rawEnv);
|
|
485
487
|
|
|
488
|
+
// Apply conservative bash fixups (strip trailing `| head|tail` and redundant
|
|
489
|
+
// `2>&1`). The helper is single-line only and refuses anything that could
|
|
490
|
+
// change semantics.
|
|
491
|
+
let bashFixups: string[] = [];
|
|
492
|
+
if (this.session.settings.get("bash.stripTrailingHeadTail")) {
|
|
493
|
+
const fixup = applyBashFixups(command);
|
|
494
|
+
if (fixup.stripped.length > 0) {
|
|
495
|
+
command = fixup.command;
|
|
496
|
+
bashFixups = fixup.stripped;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
486
500
|
// Extract leading `cd <path> && ...` into cwd when the model ignores the cwd parameter.
|
|
501
|
+
// Constrained to a single line so a `&&` that sits on a later line of a multiline
|
|
502
|
+
// script can't pull the entire script into the "cwd" capture.
|
|
487
503
|
if (!cwd) {
|
|
488
|
-
const cdMatch = command.match(/^cd\
|
|
504
|
+
const cdMatch = command.match(/^cd[ \t]+((?:[^&\\\n\r]|\\.)+?)[ \t]*&&[ \t]*/);
|
|
489
505
|
if (cdMatch) {
|
|
490
506
|
cwd = cdMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
491
507
|
command = command.slice(cdMatch[0].length);
|
|
@@ -556,7 +572,14 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
556
572
|
const requestedTimeoutSec = rawTimeout;
|
|
557
573
|
const timeoutSec = clampTimeout("bash", requestedTimeoutSec);
|
|
558
574
|
const timeoutMs = timeoutSec * 1000;
|
|
575
|
+
const pendingNotices: string[] = [];
|
|
559
576
|
const timeoutClampNotice = formatTimeoutClampNotice(requestedTimeoutSec, timeoutSec);
|
|
577
|
+
if (timeoutClampNotice) pendingNotices.push(timeoutClampNotice);
|
|
578
|
+
const bashFixupNotice = this.#bashFixupNoticeEmitted ? undefined : formatBashFixupNotice(bashFixups);
|
|
579
|
+
if (bashFixupNotice) {
|
|
580
|
+
pendingNotices.push(bashFixupNotice);
|
|
581
|
+
this.#bashFixupNoticeEmitted = true;
|
|
582
|
+
}
|
|
560
583
|
|
|
561
584
|
if (asyncRequested) {
|
|
562
585
|
if (!AsyncJobManager.instance()) {
|
|
@@ -568,7 +591,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
568
591
|
timeoutMs,
|
|
569
592
|
timeoutSec,
|
|
570
593
|
requestedTimeoutSec,
|
|
571
|
-
|
|
594
|
+
notices: pendingNotices,
|
|
572
595
|
|
|
573
596
|
resolvedEnv,
|
|
574
597
|
onUpdate,
|
|
@@ -576,7 +599,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
576
599
|
});
|
|
577
600
|
return this.#buildBackgroundStartResult(job.jobId, job.label, "", timeoutSec, {
|
|
578
601
|
requestedTimeoutSec,
|
|
579
|
-
notices:
|
|
602
|
+
notices: pendingNotices,
|
|
580
603
|
});
|
|
581
604
|
}
|
|
582
605
|
|
|
@@ -590,7 +613,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
590
613
|
timeoutMs,
|
|
591
614
|
timeoutSec,
|
|
592
615
|
requestedTimeoutSec,
|
|
593
|
-
|
|
616
|
+
notices: pendingNotices,
|
|
594
617
|
|
|
595
618
|
resolvedEnv,
|
|
596
619
|
onUpdate,
|
|
@@ -599,7 +622,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
599
622
|
if (startBackgrounded) {
|
|
600
623
|
return this.#buildBackgroundStartResult(job.jobId, job.label, "", timeoutSec, {
|
|
601
624
|
requestedTimeoutSec,
|
|
602
|
-
notices:
|
|
625
|
+
notices: pendingNotices,
|
|
603
626
|
});
|
|
604
627
|
}
|
|
605
628
|
const waitResult = await this.#waitForManagedBashJob(job, autoBackgroundWaitMs, signal);
|
|
@@ -619,7 +642,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
619
642
|
job.setBackgrounded(true);
|
|
620
643
|
return this.#buildBackgroundStartResult(job.jobId, job.label, job.getLatestText(), timeoutSec, {
|
|
621
644
|
requestedTimeoutSec,
|
|
622
|
-
notices:
|
|
645
|
+
notices: pendingNotices,
|
|
623
646
|
});
|
|
624
647
|
}
|
|
625
648
|
|
|
@@ -720,7 +743,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
720
743
|
};
|
|
721
744
|
return this.#buildCompletedResult(timedOutResult, timeoutSec, {
|
|
722
745
|
requestedTimeoutSec,
|
|
723
|
-
notices:
|
|
746
|
+
notices: pendingNotices,
|
|
724
747
|
terminalId: handle.terminalId,
|
|
725
748
|
});
|
|
726
749
|
}
|
|
@@ -776,7 +799,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
776
799
|
|
|
777
800
|
const bridgeNotices: string[] = [];
|
|
778
801
|
if (finalOutput.truncated) bridgeNotices.push("(output truncated)");
|
|
779
|
-
|
|
802
|
+
for (const notice of pendingNotices) bridgeNotices.push(notice);
|
|
780
803
|
|
|
781
804
|
return this.#buildCompletedResult(bridgeResult, timeoutSec, {
|
|
782
805
|
requestedTimeoutSec,
|
|
@@ -831,7 +854,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
831
854
|
}
|
|
832
855
|
return this.#buildCompletedResult(result, timeoutSec, {
|
|
833
856
|
requestedTimeoutSec,
|
|
834
|
-
notices:
|
|
857
|
+
notices: pendingNotices,
|
|
835
858
|
});
|
|
836
859
|
}
|
|
837
860
|
}
|
|
@@ -892,6 +915,27 @@ export function formatBashCommand(args: BashRenderArgs): string {
|
|
|
892
915
|
return displayWorkdir ? `${prompt} cd ${displayWorkdir} && ${renderedCommand}` : `${prompt} ${renderedCommand}`;
|
|
893
916
|
}
|
|
894
917
|
|
|
918
|
+
/**
|
|
919
|
+
* Returns the bash command formatted for the result body: the dim `$ cd … &&`
|
|
920
|
+
* prefix joined with syntax-highlighted command lines. The prefix is applied
|
|
921
|
+
* only to the first line so multi-line commands display cleanly — terminals
|
|
922
|
+
* reset SGR state at line boundaries, which made the previous single-string
|
|
923
|
+
* `theme.fg("dim", ...)` form render only the first line as dim.
|
|
924
|
+
*/
|
|
925
|
+
export function formatBashCommandLines(args: BashRenderArgs, uiTheme: Theme): string[] {
|
|
926
|
+
const command = replaceTabs(args.command || "…");
|
|
927
|
+
const cwd = getProjectDir();
|
|
928
|
+
const displayWorkdir = formatToolWorkingDirectory(args.cwd, cwd);
|
|
929
|
+
const envAssignments = formatBashEnvAssignments(getBashEnvForDisplay(args));
|
|
930
|
+
const prefixParts = ["$"];
|
|
931
|
+
if (displayWorkdir) prefixParts.push(`cd ${displayWorkdir} &&`);
|
|
932
|
+
if (envAssignments) prefixParts.push(envAssignments);
|
|
933
|
+
const prefix = uiTheme.fg("dim", `${prefixParts.join(" ")} `);
|
|
934
|
+
const highlightedLines = highlightCode(command, "bash");
|
|
935
|
+
if (highlightedLines.length === 0) return [prefix.trimEnd()];
|
|
936
|
+
return highlightedLines.map((line, i) => (i === 0 ? `${prefix}${line}` : line));
|
|
937
|
+
}
|
|
938
|
+
|
|
895
939
|
function toBashRenderArgs<TArgs>(args: TArgs | undefined, config: ShellRendererConfig<TArgs>): BashRenderArgs {
|
|
896
940
|
return {
|
|
897
941
|
command: config.resolveCommand?.(args),
|
|
@@ -922,7 +966,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
922
966
|
args?: TArgs,
|
|
923
967
|
): Component {
|
|
924
968
|
const renderArgs = toBashRenderArgs(args, config);
|
|
925
|
-
const
|
|
969
|
+
const cmdLines = args ? formatBashCommandLines(renderArgs, uiTheme) : undefined;
|
|
926
970
|
const isError = result.isError === true;
|
|
927
971
|
const icon = options.isPartial ? "pending" : isError ? "error" : "success";
|
|
928
972
|
const title = config.resolveTitle(args, options);
|
|
@@ -937,8 +981,11 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
937
981
|
const expanded = renderContext?.expanded ?? options.expanded;
|
|
938
982
|
const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
|
|
939
983
|
|
|
940
|
-
// Get output from context (preferred) or fall back to result content
|
|
941
|
-
|
|
984
|
+
// Get output from context (preferred) or fall back to result content.
|
|
985
|
+
// Strip the LLM-facing notice appended by wrappedExecute so we don't
|
|
986
|
+
// double-print it alongside the styled warning line below.
|
|
987
|
+
const rawOutput = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
988
|
+
const output = stripOutputNotice(rawOutput, details?.meta);
|
|
942
989
|
const displayOutput = output.trimEnd();
|
|
943
990
|
const showingFullOutput = expanded && renderContext?.isFullOutput === true;
|
|
944
991
|
|
|
@@ -1000,7 +1047,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1000
1047
|
header,
|
|
1001
1048
|
state: options.isPartial ? "pending" : isError ? "error" : "success",
|
|
1002
1049
|
sections: [
|
|
1003
|
-
{ lines:
|
|
1050
|
+
{ lines: cmdLines ?? [] },
|
|
1004
1051
|
{ label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
|
|
1005
1052
|
],
|
|
1006
1053
|
width,
|
|
@@ -3,7 +3,7 @@ import { Process, ProcessStatus } from "@oh-my-pi/pi-natives";
|
|
|
3
3
|
import type { Browser, Page } from "puppeteer-core";
|
|
4
4
|
import { ToolError, throwIfAborted } from "../tool-errors";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const ATTACH_TARGET_SKIP_PATTERN =
|
|
7
7
|
/request[\s_-]?handler|devtools|background[\s_-]?(?:page|host)|service[\s_-]?worker/i;
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -62,7 +62,7 @@ export async function waitForCdp(cdpUrl: string, timeoutMs: number, signal?: Abo
|
|
|
62
62
|
* accepts both `--flag=value` and `--flag value`). Returns null if absent or
|
|
63
63
|
* malformed.
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
function findCdpPortInArgs(args: string[]): number | null {
|
|
66
66
|
for (const arg of args) {
|
|
67
67
|
const m = /^--remote-debugging-port=(\d+)$/.exec(arg);
|
|
68
68
|
if (m) {
|
|
@@ -80,7 +80,7 @@ export function findCdpPortInArgs(args: string[]): number | null {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/** One-shot probe: returns true when `/json/version` answers 200 within the timeout. */
|
|
83
|
-
|
|
83
|
+
async function probeCdpAt(port: number, signal?: AbortSignal): Promise<boolean> {
|
|
84
84
|
const probeTimeout = AbortSignal.timeout(1500);
|
|
85
85
|
const probeSignal = signal ? AbortSignal.any([signal, probeTimeout]) : probeTimeout;
|
|
86
86
|
try {
|
|
@@ -3,7 +3,7 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { $which, getPuppeteerDir, logger } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import * as browsers from "@puppeteer/browsers";
|
|
6
|
-
import type { Browser, CDPSession, Page, default as Puppeteer } from "puppeteer-core";
|
|
6
|
+
import type { Browser, CDPSession, Page, default as Puppeteer, Target } from "puppeteer-core";
|
|
7
7
|
import { PUPPETEER_REVISIONS } from "puppeteer-core/internal/revisions.js";
|
|
8
8
|
import stealthTamperingScript from "../puppeteer/00_stealth_tampering.txt" with { type: "text" };
|
|
9
9
|
import stealthActivityScript from "../puppeteer/01_stealth_activity.txt" with { type: "text" };
|
|
@@ -30,13 +30,15 @@ export const DEFAULT_VIEWPORT = { width: 1365, height: 768, deviceScaleFactor: 1
|
|
|
30
30
|
* connection dropped, etc.).
|
|
31
31
|
*/
|
|
32
32
|
export const BROWSER_PROTOCOL_TIMEOUT_MS = 60_000;
|
|
33
|
-
|
|
33
|
+
const STEALTH_IGNORE_DEFAULT_ARGS = [
|
|
34
34
|
"--disable-extensions",
|
|
35
35
|
"--disable-default-apps",
|
|
36
36
|
"--disable-component-extensions-with-background-pages",
|
|
37
37
|
];
|
|
38
|
-
|
|
38
|
+
const STEALTH_ACCEPT_LANGUAGE = "en-US,en";
|
|
39
39
|
|
|
40
|
+
const USER_AGENT_TARGET_TIMEOUT_MS = 5_000;
|
|
41
|
+
const USER_AGENT_TARGET_TYPES = new Set(["page", "webview", "background_page"]);
|
|
40
42
|
const PUPPETEER_SOURCE_URL_SUFFIX = "//# sourceURL=__puppeteer_evaluation_script__";
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -82,7 +84,7 @@ export async function loadPuppeteerInWorker(safeDir: string): Promise<typeof Pup
|
|
|
82
84
|
* The browser is cached under ~/.omp/puppeteer (getPuppeteerDir).
|
|
83
85
|
*/
|
|
84
86
|
let chromiumExecutablePromise: Promise<string | undefined> | undefined;
|
|
85
|
-
|
|
87
|
+
async function ensureChromiumExecutable(): Promise<string | undefined> {
|
|
86
88
|
const sysChrome = resolveSystemChromium();
|
|
87
89
|
if (sysChrome) return sysChrome;
|
|
88
90
|
const envPath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
@@ -138,7 +140,7 @@ export async function ensureChromiumExecutable(): Promise<string | undefined> {
|
|
|
138
140
|
return chromiumExecutablePromise;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
let
|
|
143
|
+
let resolvedChromium: string | null | undefined; // undefined = unchecked; null = not found
|
|
142
144
|
|
|
143
145
|
function isExecutableFile(p: string): boolean {
|
|
144
146
|
try {
|
|
@@ -209,19 +211,19 @@ function systemChromiumCandidates(): string[] {
|
|
|
209
211
|
return candidates;
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
+
function resolveSystemChromium(): string | undefined {
|
|
215
|
+
if (resolvedChromium !== undefined) return resolvedChromium ?? undefined;
|
|
214
216
|
const seen = new Set<string>();
|
|
215
217
|
for (const candidate of systemChromiumCandidates()) {
|
|
216
218
|
if (!candidate || seen.has(candidate)) continue;
|
|
217
219
|
seen.add(candidate);
|
|
218
220
|
if (isExecutableFile(candidate)) {
|
|
219
|
-
|
|
221
|
+
resolvedChromium = candidate;
|
|
220
222
|
logger.debug("Using system Chrome/Chromium", { path: candidate });
|
|
221
223
|
return candidate;
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
|
-
|
|
226
|
+
resolvedChromium = null;
|
|
225
227
|
return undefined;
|
|
226
228
|
}
|
|
227
229
|
|
|
@@ -463,6 +465,7 @@ export interface UserAgentSession {
|
|
|
463
465
|
async function configureUserAgentTargets(
|
|
464
466
|
browser: Browser,
|
|
465
467
|
state: { browserSession: CDPSession | null; override: UserAgentOverride },
|
|
468
|
+
targetTimeoutMs = USER_AGENT_TARGET_TIMEOUT_MS,
|
|
466
469
|
): Promise<void> {
|
|
467
470
|
if (!state.browserSession) {
|
|
468
471
|
state.browserSession = await browser.target().createCDPSession();
|
|
@@ -471,23 +474,72 @@ async function configureUserAgentTargets(
|
|
|
471
474
|
waitForDebuggerOnStart: false,
|
|
472
475
|
flatten: true,
|
|
473
476
|
});
|
|
474
|
-
state.browserSession.on(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
477
|
+
state.browserSession.on(
|
|
478
|
+
"Target.attachedToTarget",
|
|
479
|
+
async (event: { sessionId: string; targetInfo?: { type?: string } }) => {
|
|
480
|
+
if (!targetInfoSupportsUserAgentOverride(event.targetInfo)) return;
|
|
481
|
+
const connection = state.browserSession?.connection();
|
|
482
|
+
const session = connection?.session(event.sessionId);
|
|
483
|
+
if (!session) return;
|
|
484
|
+
await withSoftTimeout(
|
|
485
|
+
sendUserAgentOverride(wrapSession(session), state.override),
|
|
486
|
+
targetTimeoutMs,
|
|
487
|
+
"new target user-agent override",
|
|
488
|
+
);
|
|
489
|
+
},
|
|
490
|
+
);
|
|
480
491
|
}
|
|
481
492
|
|
|
482
|
-
const targets = browser.targets();
|
|
493
|
+
const targets = browser.targets().filter(targetSupportsUserAgentOverride);
|
|
483
494
|
await Promise.all(
|
|
484
495
|
targets.map(async target => {
|
|
485
|
-
|
|
486
|
-
|
|
496
|
+
await withSoftTimeout(
|
|
497
|
+
applyTargetUserAgentOverride(target, state.override),
|
|
498
|
+
targetTimeoutMs,
|
|
499
|
+
"target user-agent override",
|
|
500
|
+
);
|
|
487
501
|
}),
|
|
488
502
|
);
|
|
489
503
|
}
|
|
490
504
|
|
|
505
|
+
function targetSupportsUserAgentOverride(target: Target): boolean {
|
|
506
|
+
return targetInfoSupportsUserAgentOverride({ type: target.type() });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function targetInfoSupportsUserAgentOverride(targetInfo: { type?: string } | undefined): boolean {
|
|
510
|
+
return Boolean(targetInfo?.type && USER_AGENT_TARGET_TYPES.has(targetInfo.type));
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function applyTargetUserAgentOverride(target: Target, override: UserAgentOverride): Promise<void> {
|
|
514
|
+
const session = await target.createCDPSession();
|
|
515
|
+
try {
|
|
516
|
+
await sendUserAgentOverride(wrapSession(session), override);
|
|
517
|
+
} finally {
|
|
518
|
+
await session.detach().catch(() => undefined);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function withSoftTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T | undefined> {
|
|
523
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
524
|
+
const timeoutPromise = new Promise<undefined>(resolve => {
|
|
525
|
+
timeout = setTimeout(() => {
|
|
526
|
+
logger.debug(`Timed out applying ${label}`);
|
|
527
|
+
resolve(undefined);
|
|
528
|
+
}, timeoutMs);
|
|
529
|
+
});
|
|
530
|
+
try {
|
|
531
|
+
return await Promise.race([
|
|
532
|
+
promise.catch(error => {
|
|
533
|
+
logger.debug(`Failed to apply ${label}`, { error: error instanceof Error ? error.message : String(error) });
|
|
534
|
+
return undefined;
|
|
535
|
+
}),
|
|
536
|
+
timeoutPromise,
|
|
537
|
+
]);
|
|
538
|
+
} finally {
|
|
539
|
+
if (timeout) clearTimeout(timeout);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
491
543
|
async function injectStealthScripts(page: Page): Promise<void> {
|
|
492
544
|
const scripts = [
|
|
493
545
|
stealthTamperingScript,
|
|
@@ -574,3 +626,14 @@ export async function applyStealthPatches(
|
|
|
574
626
|
state.browserSession = targetState.browserSession;
|
|
575
627
|
await injectStealthScripts(page);
|
|
576
628
|
}
|
|
629
|
+
|
|
630
|
+
export function targetSupportsUserAgentOverrideForTest(target: Target): boolean {
|
|
631
|
+
return targetSupportsUserAgentOverride(target);
|
|
632
|
+
}
|
|
633
|
+
export async function configureUserAgentTargetsForTest(
|
|
634
|
+
browser: Browser,
|
|
635
|
+
state: { browserSession: CDPSession | null; override: UserAgentOverride },
|
|
636
|
+
targetTimeoutMs?: number,
|
|
637
|
+
): Promise<void> {
|
|
638
|
+
await configureUserAgentTargets(browser, state, targetTimeoutMs);
|
|
639
|
+
}
|
|
@@ -26,10 +26,6 @@ export interface BrowserHandle {
|
|
|
26
26
|
|
|
27
27
|
const browsers = new Map<string, BrowserHandle>();
|
|
28
28
|
|
|
29
|
-
export function listBrowsers(): BrowserHandle[] {
|
|
30
|
-
return [...browsers.values()];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
function browserKey(kind: BrowserKind): string {
|
|
34
30
|
switch (kind.kind) {
|
|
35
31
|
case "headless":
|
|
@@ -166,7 +162,7 @@ export async function releaseBrowser(handle: BrowserHandle, opts: { kill: boolea
|
|
|
166
162
|
}
|
|
167
163
|
}
|
|
168
164
|
|
|
169
|
-
|
|
165
|
+
async function disposeBrowserHandle(handle: BrowserHandle, opts: { kill: boolean }): Promise<void> {
|
|
170
166
|
if (handle.kind.kind === "headless") {
|
|
171
167
|
if (handle.browser.connected) {
|
|
172
168
|
try {
|
|
@@ -11,7 +11,7 @@ import type { RenderResultOptions } from "../../extensibility/custom-tools/types
|
|
|
11
11
|
import type { Theme } from "../../modes/theme/theme";
|
|
12
12
|
import { Hasher, renderCodeCell, renderStatusLine } from "../../tui";
|
|
13
13
|
import type { BrowserToolDetails } from "../browser";
|
|
14
|
-
import { formatStyledTruncationWarning } from "../output-meta";
|
|
14
|
+
import { formatStyledTruncationWarning, stripOutputNotice } from "../output-meta";
|
|
15
15
|
import { replaceTabs, shortenPath } from "../render-utils";
|
|
16
16
|
|
|
17
17
|
const BROWSER_DEFAULT_PREVIEW_LINES = 10;
|
|
@@ -195,7 +195,7 @@ export const browserToolRenderer = {
|
|
|
195
195
|
const details = result.details;
|
|
196
196
|
const action = details?.action ?? argsObj.action;
|
|
197
197
|
const isError = result.isError === true;
|
|
198
|
-
const output = extractTextOutput(result.content);
|
|
198
|
+
const output = stripOutputNotice(extractTextOutput(result.content), details?.meta);
|
|
199
199
|
|
|
200
200
|
if (action === "run") {
|
|
201
201
|
let component = renderRunCell(argsObj, details, options, output, isError, theme);
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
interface WorkerHandle {
|
|
31
31
|
send(msg: WorkerInbound, transferList?: Transferable[]): void;
|
|
32
32
|
onMessage(handler: (msg: WorkerOutbound) => void): () => void;
|
|
33
|
+
onError(handler: (error: Error) => void): () => void;
|
|
33
34
|
terminate(): Promise<void>;
|
|
34
35
|
readonly mode: "worker" | "inline";
|
|
35
36
|
}
|
|
@@ -89,10 +90,6 @@ export function getTab(name: string): TabSession | undefined {
|
|
|
89
90
|
return tabs.get(name);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
export function listTabs(): TabSession[] {
|
|
93
|
-
return [...tabs.values()];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
93
|
export async function acquireTab(
|
|
97
94
|
name: string,
|
|
98
95
|
browser: BrowserHandle,
|
|
@@ -124,23 +121,14 @@ export async function acquireTab(
|
|
|
124
121
|
|
|
125
122
|
const initPayload = await buildInitPayload(browser, opts);
|
|
126
123
|
const worker = await spawnTabWorker();
|
|
127
|
-
const { promise, resolve, reject } = Promise.withResolvers<ReadyInfo>();
|
|
128
|
-
const unlisten = worker.onMessage(msg => {
|
|
129
|
-
if (msg.type === "ready") resolve(msg.info);
|
|
130
|
-
else if (msg.type === "init-failed") reject(errorFromPayload(msg.error));
|
|
131
|
-
else if (msg.type === "log") logWorkerMessage(msg);
|
|
132
|
-
});
|
|
133
124
|
let info: ReadyInfo;
|
|
134
125
|
try {
|
|
135
|
-
|
|
136
|
-
info = await raceWithTimeout(promise, opts.timeoutMs + GRACE_MS, "Timed out initializing browser tab worker");
|
|
126
|
+
info = await initializeTabWorker(worker, initPayload, opts.timeoutMs + GRACE_MS);
|
|
137
127
|
} catch (error) {
|
|
138
|
-
unlisten();
|
|
139
128
|
await worker.terminate().catch(() => undefined);
|
|
140
129
|
if (browser.refCount === 0) await releaseBrowser(browser, { kill: false });
|
|
141
130
|
throw error;
|
|
142
131
|
}
|
|
143
|
-
unlisten();
|
|
144
132
|
|
|
145
133
|
holdBrowser(browser);
|
|
146
134
|
const tab: TabSession = {
|
|
@@ -477,6 +465,17 @@ function wrapBunWorker(worker: Worker): WorkerHandle {
|
|
|
477
465
|
worker.addEventListener("message", wrap);
|
|
478
466
|
return () => worker.removeEventListener("message", wrap);
|
|
479
467
|
},
|
|
468
|
+
onError(handler) {
|
|
469
|
+
const onError = (event: ErrorEvent): void => handler(errorFromWorkerEvent(event));
|
|
470
|
+
const onMessageError = (event: MessageEvent): void =>
|
|
471
|
+
handler(new ToolError(`Tab worker message error: ${String(event.data)}`));
|
|
472
|
+
worker.addEventListener("error", onError);
|
|
473
|
+
worker.addEventListener("messageerror", onMessageError);
|
|
474
|
+
return () => {
|
|
475
|
+
worker.removeEventListener("error", onError);
|
|
476
|
+
worker.removeEventListener("messageerror", onMessageError);
|
|
477
|
+
};
|
|
478
|
+
},
|
|
480
479
|
async terminate() {
|
|
481
480
|
worker.terminate();
|
|
482
481
|
},
|
|
@@ -515,6 +514,44 @@ async function spawnInlineWorker(): Promise<WorkerHandle> {
|
|
|
515
514
|
hostListeners.add(handler);
|
|
516
515
|
return () => hostListeners.delete(handler);
|
|
517
516
|
},
|
|
517
|
+
onError: () => () => {},
|
|
518
518
|
async terminate() {},
|
|
519
519
|
};
|
|
520
520
|
}
|
|
521
|
+
|
|
522
|
+
async function initializeTabWorker(
|
|
523
|
+
worker: WorkerHandle,
|
|
524
|
+
payload: WorkerInitPayload,
|
|
525
|
+
timeoutMs: number,
|
|
526
|
+
): Promise<ReadyInfo> {
|
|
527
|
+
const { promise, resolve, reject } = Promise.withResolvers<ReadyInfo>();
|
|
528
|
+
const unlisten = worker.onMessage(msg => {
|
|
529
|
+
if (msg.type === "ready") resolve(msg.info);
|
|
530
|
+
else if (msg.type === "init-failed") reject(errorFromPayload(msg.error));
|
|
531
|
+
else if (msg.type === "log") logWorkerMessage(msg);
|
|
532
|
+
});
|
|
533
|
+
const unlistenError = worker.onError(error => {
|
|
534
|
+
reject(new ToolError(`Tab worker failed during startup: ${error.message}`));
|
|
535
|
+
});
|
|
536
|
+
try {
|
|
537
|
+
worker.send({ type: "init", payload });
|
|
538
|
+
return await raceWithTimeout(promise, timeoutMs, "Timed out initializing browser tab worker");
|
|
539
|
+
} finally {
|
|
540
|
+
unlisten();
|
|
541
|
+
unlistenError();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export function initializeTabWorkerForTest(
|
|
546
|
+
worker: WorkerHandle,
|
|
547
|
+
payload: WorkerInitPayload,
|
|
548
|
+
timeoutMs: number,
|
|
549
|
+
): Promise<ReadyInfo> {
|
|
550
|
+
return initializeTabWorker(worker, payload, timeoutMs);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function errorFromWorkerEvent(event: ErrorEvent): Error {
|
|
554
|
+
if (event.error instanceof Error) return event.error;
|
|
555
|
+
if (event.message) return new Error(event.message);
|
|
556
|
+
return new Error("Unknown tab worker error");
|
|
557
|
+
}
|
|
@@ -250,9 +250,19 @@ export interface ParsedConflictUri {
|
|
|
250
250
|
/** `"*"` selects every currently-registered conflict (bulk write only). */
|
|
251
251
|
id: number | "*";
|
|
252
252
|
scope?: ConflictScope;
|
|
253
|
+
/**
|
|
254
|
+
* When `raw` was a malformed `<file-prefix>:conflict://…` path, the
|
|
255
|
+
* stripped prefix is preserved here so callers can surface a gentle
|
|
256
|
+
* "you don't need the file path" note. `undefined` for clean URIs.
|
|
257
|
+
*/
|
|
258
|
+
recoveredPrefix?: string;
|
|
253
259
|
}
|
|
254
260
|
|
|
255
|
-
|
|
261
|
+
// Accept an optional `<prefix>:` before the scheme so paths like
|
|
262
|
+
// `path/to/file.ts:conflict://3` (where the agent mixed the `:conflicts`
|
|
263
|
+
// read selector with the `conflict://` scheme) still resolve. The prefix
|
|
264
|
+
// is greedy so the LAST `:conflict://` wins for multi-colon inputs.
|
|
265
|
+
const CONFLICT_URI_RE = /^(?:(.+):)?conflict:\/\/(.+)$/;
|
|
256
266
|
|
|
257
267
|
/**
|
|
258
268
|
* Parse a `conflict://<N>`, `conflict://<N>/<scope>`, or `conflict://*` URI.
|
|
@@ -269,7 +279,8 @@ const CONFLICT_URI_RE = /^conflict:\/\/(.+)$/;
|
|
|
269
279
|
export function parseConflictUri(raw: string): ParsedConflictUri | null {
|
|
270
280
|
const match = raw.match(CONFLICT_URI_RE);
|
|
271
281
|
if (!match) return null;
|
|
272
|
-
const
|
|
282
|
+
const recoveredPrefix = match[1];
|
|
283
|
+
const tail = match[2];
|
|
273
284
|
const slashIdx = tail.indexOf("/");
|
|
274
285
|
const idPart = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
|
|
275
286
|
const scopePart = slashIdx === -1 ? undefined : tail.slice(slashIdx + 1);
|
|
@@ -280,7 +291,7 @@ export function parseConflictUri(raw: string): ParsedConflictUri | null {
|
|
|
280
291
|
`Invalid conflict URI '${raw}': wildcard 'conflict://*' does not accept a scope segment. Drop '/${scopePart}' or use a numeric id.`,
|
|
281
292
|
);
|
|
282
293
|
}
|
|
283
|
-
return { id: "*" };
|
|
294
|
+
return recoveredPrefix !== undefined ? { id: "*", recoveredPrefix } : { id: "*" };
|
|
284
295
|
}
|
|
285
296
|
|
|
286
297
|
if (!/^\d+$/.test(idPart)) {
|
|
@@ -303,7 +314,7 @@ export function parseConflictUri(raw: string): ParsedConflictUri | null {
|
|
|
303
314
|
scope = scopePart as ConflictScope;
|
|
304
315
|
}
|
|
305
316
|
|
|
306
|
-
return { id, scope };
|
|
317
|
+
return recoveredPrefix !== undefined ? { id, scope, recoveredPrefix } : { id, scope };
|
|
307
318
|
}
|
|
308
319
|
|
|
309
320
|
/**
|
package/src/tools/eval.ts
CHANGED
|
@@ -16,7 +16,12 @@ import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
|
|
|
16
16
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
|
|
17
17
|
import { getTreeBranch, getTreeContinuePrefix, renderCodeCell } from "../tui";
|
|
18
18
|
import { resolveEvalBackends, type ToolSession } from ".";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
formatStyledTruncationWarning,
|
|
21
|
+
resolveOutputMaxColumns,
|
|
22
|
+
resolveOutputSinkHeadBytes,
|
|
23
|
+
stripOutputNotice,
|
|
24
|
+
} from "./output-meta";
|
|
20
25
|
import { formatTitle, replaceTabs, shortenPath, truncateToWidth, wrapBrackets } from "./render-utils";
|
|
21
26
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
22
27
|
import { toolResult } from "./tool-result";
|
|
@@ -358,6 +363,8 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
358
363
|
outputSink = new OutputSink({
|
|
359
364
|
artifactPath,
|
|
360
365
|
artifactId,
|
|
366
|
+
headBytes: resolveOutputSinkHeadBytes(session.settings),
|
|
367
|
+
maxColumns: resolveOutputMaxColumns(session.settings),
|
|
361
368
|
onChunk: chunk => {
|
|
362
369
|
appendTail(chunk);
|
|
363
370
|
pushUpdate();
|
|
@@ -920,8 +927,11 @@ export const evalToolRenderer = {
|
|
|
920
927
|
): Component {
|
|
921
928
|
const details = result.details;
|
|
922
929
|
|
|
923
|
-
const
|
|
930
|
+
const rawOutput =
|
|
924
931
|
options.renderContext?.output ?? (result.content?.find(c => c.type === "text")?.text ?? "").trimEnd();
|
|
932
|
+
// Strip the LLM-facing notice (appended by wrappedExecute) before display;
|
|
933
|
+
// the styled `warningLine` below carries the same text in ⟨…⟩ form.
|
|
934
|
+
const output = stripOutputNotice(rawOutput, details?.meta).trimEnd();
|
|
925
935
|
|
|
926
936
|
const jsonOutputs = details?.jsonOutputs ?? [];
|
|
927
937
|
const jsonLines = jsonOutputs.flatMap((value, index) => {
|