@oh-my-pi/pi-coding-agent 17.1.7 → 17.2.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 +127 -0
- package/dist/{CHANGELOG-5k4dq4g6.md → CHANGELOG-be1f2t8h.md} +127 -0
- package/dist/cli.js +9554 -12379
- package/dist/template-8vdv6xb4.js +1671 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/dist/types/advisor/advise-tool.d.ts +0 -7
- package/dist/types/advisor/runtime.d.ts +8 -11
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/async/job-manager.d.ts +11 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cleanse/agent.d.ts +19 -0
- package/dist/types/cleanse/balance.d.ts +7 -0
- package/dist/types/cleanse/checkers.d.ts +13 -0
- package/dist/types/cleanse/index.d.ts +16 -0
- package/dist/types/cleanse/loop.d.ts +16 -0
- package/dist/types/cleanse/parsers.d.ts +13 -0
- package/dist/types/cleanse/progress.d.ts +14 -0
- package/dist/types/cleanse/types.d.ts +64 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/cleanse.d.ts +23 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +63 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/export/html/index.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +8 -5
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/async-job-delivery.d.ts +8 -0
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/model-controls.d.ts +3 -3
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/isolation-ownership.d.ts +34 -0
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
- package/dist/types/tools/browser/tab-worker.d.ts +1 -4
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/tui/output-block.d.ts +5 -5
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +16 -12
- package/scripts/bundle-dist.ts +3 -1
- package/src/advisor/advise-tool.ts +0 -11
- package/src/advisor/runtime.ts +138 -24
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/async/job-manager.ts +30 -7
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli/update-cli.ts +3 -0
- package/src/cli/usage-cli.ts +29 -4
- package/src/cli/worktree-cli.ts +28 -11
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +11 -1
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-registry.ts +103 -25
- package/src/config/settings-schema.ts +65 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/discovery/claude-plugins.ts +144 -34
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/export/html/index.ts +17 -10
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/launch/broker.ts +14 -4
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +4 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +27 -18
- package/src/modes/acp/acp-event-mapper.ts +38 -4
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/settings-selector.ts +9 -1
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +67 -64
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/goals/guided-goal-interview.md +41 -6
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/system/vibe-mode-active.md +4 -1
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/grep.md +7 -6
- package/src/sdk.ts +178 -15
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +330 -179
- package/src/session/async-job-delivery.ts +8 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +15 -7
- package/src/session/session-advisors.ts +142 -23
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-history-format.ts +31 -6
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +136 -5
- package/src/system-prompt.ts +3 -2
- package/src/task/executor.ts +17 -4
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/render.ts +20 -4
- package/src/task/worktree.ts +8 -0
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/ask.ts +3 -3
- package/src/tools/ast-edit.ts +9 -2
- package/src/tools/browser/cmux/cmux-tab.ts +9 -14
- package/src/tools/browser/tab-worker.ts +12 -35
- package/src/tools/browser.ts +7 -2
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/gh-renderer.ts +3 -3
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +38 -5
- package/src/tools/path-utils.ts +88 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/write.ts +44 -3
- package/src/tools/xdev.ts +39 -14
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/output-block.ts +25 -8
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/utils/shell-snapshot-fn-env.sh +5 -2
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/render.ts +2 -2
- package/src/web/search/types.ts +1 -1
- package/dist/types/goals/guided-setup.d.ts +0 -30
- package/src/goals/guided-setup.ts +0 -171
- package/src/prompts/goals/guided-goal-system.md +0 -33
package/src/task/render.ts
CHANGED
|
@@ -49,6 +49,13 @@ interface TaskRenderContext {
|
|
|
49
49
|
* commit-eligible rows do not repaint after entering native scrollback.
|
|
50
50
|
*/
|
|
51
51
|
frozen?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Wall clock for time-derived rows (current-tool elapsed, retry countdown).
|
|
54
|
+
* The component freezes it once the block settles or any of its rows enter
|
|
55
|
+
* native scrollback, so identical-input rebuilds stay byte-identical with
|
|
56
|
+
* committed history. Absent: render with the live clock.
|
|
57
|
+
*/
|
|
58
|
+
nowMs?: number;
|
|
52
59
|
}
|
|
53
60
|
type TaskRenderOptions = RenderResultOptions & { renderContext?: TaskRenderContext };
|
|
54
61
|
|
|
@@ -888,6 +895,7 @@ function renderAgentProgress(
|
|
|
888
895
|
frozen = false,
|
|
889
896
|
seenNestedTasks?: WeakSet<object>,
|
|
890
897
|
nestedDepth = 0,
|
|
898
|
+
nowMs = Date.now(),
|
|
891
899
|
): string[] {
|
|
892
900
|
const lines: string[] = [];
|
|
893
901
|
|
|
@@ -964,7 +972,7 @@ function renderAgentProgress(
|
|
|
964
972
|
toolLine += `: ${theme.fg("dim", previewLine(sanitizeText(toolDetail), 40))}`;
|
|
965
973
|
}
|
|
966
974
|
if (progress.currentToolStartMs) {
|
|
967
|
-
const elapsed =
|
|
975
|
+
const elapsed = nowMs - progress.currentToolStartMs;
|
|
968
976
|
if (elapsed > 5000) {
|
|
969
977
|
toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`;
|
|
970
978
|
}
|
|
@@ -986,7 +994,7 @@ function renderAgentProgress(
|
|
|
986
994
|
// long until the next attempt. Without this, the parent UI would just
|
|
987
995
|
// keep spinning while a child sleeps on a 3-hour provider rate-limit.
|
|
988
996
|
if (progress.retryState && progress.status === "running") {
|
|
989
|
-
const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs -
|
|
997
|
+
const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - nowMs);
|
|
990
998
|
const waitLabel = remainingMs > 0 ? `in ${formatDuration(remainingMs)}` : "now";
|
|
991
999
|
const summary =
|
|
992
1000
|
`retrying ${progress.retryState.attempt}/${progress.retryState.maxAttempts} ${waitLabel}: ` +
|
|
@@ -1079,6 +1087,7 @@ function renderAgentProgress(
|
|
|
1079
1087
|
frozen,
|
|
1080
1088
|
seenNestedTasks,
|
|
1081
1089
|
nestedDepth,
|
|
1090
|
+
nowMs,
|
|
1082
1091
|
);
|
|
1083
1092
|
for (const line of nestedLines) {
|
|
1084
1093
|
lines.push(`${continuePrefix}${line}`);
|
|
@@ -1560,6 +1569,7 @@ export function renderResult(
|
|
|
1560
1569
|
return framedBlock(theme, width => {
|
|
1561
1570
|
const { expanded, isPartial, spinnerFrame } = options;
|
|
1562
1571
|
const frozen = options.renderContext?.frozen === true;
|
|
1572
|
+
const nowMs = options.renderContext?.nowMs ?? Date.now();
|
|
1563
1573
|
const lines: string[] = [];
|
|
1564
1574
|
|
|
1565
1575
|
// Result rows win once any exist; progress rows for spawns without a
|
|
@@ -1577,7 +1587,9 @@ export function renderResult(
|
|
|
1577
1587
|
lines.push(formatHiddenProgressLine(ordered.slice(0, ordered.length - visible.length), theme));
|
|
1578
1588
|
}
|
|
1579
1589
|
for (const progress of visible) {
|
|
1580
|
-
lines.push(
|
|
1590
|
+
lines.push(
|
|
1591
|
+
...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen, undefined, 0, nowMs),
|
|
1592
|
+
);
|
|
1581
1593
|
}
|
|
1582
1594
|
} else if (details.results && details.results.length > 0) {
|
|
1583
1595
|
const ordered = orderResultsForDisplay(details.results);
|
|
@@ -1602,7 +1614,9 @@ export function renderResult(
|
|
|
1602
1614
|
)
|
|
1603
1615
|
: [];
|
|
1604
1616
|
for (const progress of supplementalProgress) {
|
|
1605
|
-
lines.push(
|
|
1617
|
+
lines.push(
|
|
1618
|
+
...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen, undefined, 0, nowMs),
|
|
1619
|
+
);
|
|
1606
1620
|
}
|
|
1607
1621
|
|
|
1608
1622
|
const summaryParts: string[] = [];
|
|
@@ -1742,6 +1756,7 @@ function renderNestedTaskTree(
|
|
|
1742
1756
|
frozen = false,
|
|
1743
1757
|
seen: WeakSet<object> = new WeakSet<object>(),
|
|
1744
1758
|
depth = 0,
|
|
1759
|
+
nowMs = Date.now(),
|
|
1745
1760
|
): string[] {
|
|
1746
1761
|
const lines: string[] = [];
|
|
1747
1762
|
for (const details of detailsList) {
|
|
@@ -1788,6 +1803,7 @@ function renderNestedTaskTree(
|
|
|
1788
1803
|
frozen,
|
|
1789
1804
|
seen,
|
|
1790
1805
|
depth + 1,
|
|
1806
|
+
nowMs,
|
|
1791
1807
|
),
|
|
1792
1808
|
);
|
|
1793
1809
|
});
|
package/src/task/worktree.ts
CHANGED
|
@@ -6,6 +6,7 @@ import * as natives from "@oh-my-pi/pi-natives";
|
|
|
6
6
|
import { getWorktreeDir, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import * as git from "../utils/git";
|
|
8
8
|
import * as jj from "../utils/jj";
|
|
9
|
+
import { writeIsolationOwner } from "./isolation-ownership";
|
|
9
10
|
import { mapWithConcurrencyLimit } from "./parallel";
|
|
10
11
|
|
|
11
12
|
const { IsoBackendKind } = natives;
|
|
@@ -434,6 +435,13 @@ export async function ensureIsolation(
|
|
|
434
435
|
|
|
435
436
|
for (const candidate of candidates) {
|
|
436
437
|
await fs.rm(baseDir, { recursive: true, force: true });
|
|
438
|
+
// Claim ownership before the backend materialises `m`. Backends only
|
|
439
|
+
// create/replace `mergedDir` (and overlay upper/work), never the base
|
|
440
|
+
// dir, so the marker survives `isoStart` — and a concurrent
|
|
441
|
+
// `omp worktree clear` never sees this sandbox without a live owner,
|
|
442
|
+
// even while a large clone is still in progress.
|
|
443
|
+
await fs.mkdir(baseDir, { recursive: true });
|
|
444
|
+
await writeIsolationOwner(baseDir, id);
|
|
437
445
|
try {
|
|
438
446
|
await natives.isoStart(candidate, repoRoot, mergedDir);
|
|
439
447
|
// Sever the isolation's git metadata from the source checkout. Copy
|
package/src/thinking.ts
CHANGED
|
@@ -191,7 +191,7 @@ export interface ConfiguredThinkingLevelMetadata {
|
|
|
191
191
|
const AUTO_THINKING_METADATA: ConfiguredThinkingLevelMetadata = {
|
|
192
192
|
value: AUTO_THINKING,
|
|
193
193
|
label: "auto",
|
|
194
|
-
description: "Auto-detect per prompt
|
|
194
|
+
description: "Auto-detect per prompt",
|
|
195
195
|
};
|
|
196
196
|
|
|
197
197
|
/**
|
|
@@ -236,6 +236,12 @@ export function parseCliThinkingLevel(value: string | null | undefined): Configu
|
|
|
236
236
|
* above Low (falling back to the full supported set only when the model maxes
|
|
237
237
|
* out below Low). Within that pool the request snaps to the highest level not
|
|
238
238
|
* exceeding it, or the pool minimum when the request is below the pool.
|
|
239
|
+
* `ceiling` bounds the pool from above, so a policy ceiling survives the model
|
|
240
|
+
* clamp: a sparse ladder such as `["max"]` must not snap an `xhigh` request up
|
|
241
|
+
* to `max`. The Low floor is resolved against the model's own ladder *before*
|
|
242
|
+
* the ceiling applies — a ceiling that hides every tier at or above Low means
|
|
243
|
+
* there is nothing legal to pick (`undefined`), not a licence to fall through
|
|
244
|
+
* to a sub-Low tier the model happens to expose.
|
|
239
245
|
*
|
|
240
246
|
* Returns `undefined` for reasoning-capable models without a controllable
|
|
241
247
|
* effort surface (`thinking.efforts` empty — e.g. devin-agent models, where
|
|
@@ -244,12 +250,19 @@ export function parseCliThinkingLevel(value: string | null | undefined): Configu
|
|
|
244
250
|
* forward a concrete effort that would then trip {@link requireSupportedEffort}
|
|
245
251
|
* downstream.
|
|
246
252
|
*/
|
|
247
|
-
export function clampAutoThinkingEffort(
|
|
253
|
+
export function clampAutoThinkingEffort(
|
|
254
|
+
model: Model | undefined,
|
|
255
|
+
effort: Effort,
|
|
256
|
+
ceiling: Effort = Effort.Max,
|
|
257
|
+
): Effort | undefined {
|
|
248
258
|
const supported = model ? getSupportedEfforts(model) : THINKING_EFFORTS;
|
|
249
259
|
if (supported.length === 0) return undefined;
|
|
250
260
|
const lowIndex = THINKING_EFFORTS.indexOf(Effort.Low);
|
|
251
|
-
const
|
|
252
|
-
const
|
|
261
|
+
const ceilingIndex = THINKING_EFFORTS.indexOf(ceiling);
|
|
262
|
+
const atOrAboveLow = supported.filter(level => THINKING_EFFORTS.indexOf(level) >= lowIndex);
|
|
263
|
+
const floored = atOrAboveLow.length > 0 ? atOrAboveLow : supported;
|
|
264
|
+
const pool = floored.filter(level => THINKING_EFFORTS.indexOf(level) <= ceilingIndex);
|
|
265
|
+
if (pool.length === 0) return undefined;
|
|
253
266
|
const requestedIndex = THINKING_EFFORTS.indexOf(effort);
|
|
254
267
|
let chosen = pool[0];
|
|
255
268
|
for (const candidate of pool) {
|
|
@@ -351,14 +364,20 @@ export function modelSupportsEffortCeiling(model: Model, ceiling: Effort): boole
|
|
|
351
364
|
|
|
352
365
|
/**
|
|
353
366
|
* The provisional concrete level shown while `auto` is configured but before a
|
|
354
|
-
* turn has been classified
|
|
355
|
-
* clamped into the auto range.
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
367
|
+
* turn has been classified, and the fallback when classification fails. Prefers
|
|
368
|
+
* the model's `defaultLevel`, otherwise High, clamped into the auto range.
|
|
369
|
+
*
|
|
370
|
+
* Deliberately stays below {@link Effort.Max}: the placeholder must not bill the
|
|
371
|
+
* top tier for a turn nobody classified, so XHigh is passed as a hard ceiling
|
|
372
|
+
* rather than only capping the preferred level — otherwise a sparse `["max"]`
|
|
373
|
+
* ladder would snap straight back up. A model whose ladder offers nothing at or
|
|
374
|
+
* below XHigh therefore has no provisional level, and `auto` leaves the current
|
|
375
|
+
* one in place. Classification itself may still resolve Max on models that
|
|
376
|
+
* expose the tier when the user opts in. Returns `undefined` for non-reasoning
|
|
377
|
+
* models.
|
|
359
378
|
*/
|
|
360
379
|
export function resolveProvisionalAutoLevel(model: Model | undefined): Effort | undefined {
|
|
361
380
|
if (!model?.reasoning) return undefined;
|
|
362
381
|
const preferred = model.thinking?.defaultLevel ?? Effort.High;
|
|
363
|
-
return clampAutoThinkingEffort(model, preferred === Effort.Max ? Effort.XHigh : preferred);
|
|
382
|
+
return clampAutoThinkingEffort(model, preferred === Effort.Max ? Effort.XHigh : preferred, Effort.XHigh);
|
|
364
383
|
}
|
package/src/tools/acp-bridge.ts
CHANGED
|
@@ -40,15 +40,39 @@ export function shouldRouteWriteThroughBridge(
|
|
|
40
40
|
return absolutePath !== resolvePlanPath(session, state.planFilePath);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Result of a bridge-routed write: the content actually verified on disk
|
|
45
|
+
* after the client processed the write, plus whether that content diverges
|
|
46
|
+
* from what the tool asked to persist.
|
|
47
|
+
*
|
|
48
|
+
* ACP's `fs/write_text_file` has no "verbatim, no side effects" guarantee —
|
|
49
|
+
* a client (e.g. Zed with `format_on_save: on`) may reformat the buffer as
|
|
50
|
+
* part of handling the write before it settles on disk. Silently trusting
|
|
51
|
+
* the requested `content` as "what's now on disk" lets that drift poison
|
|
52
|
+
* every snapshot/tag/hash a caller derives from the write, which then reads
|
|
53
|
+
* back as unrelated whole-file corruption on the *next* edit. Reading the
|
|
54
|
+
* file back and reporting what's actually there keeps callers honest.
|
|
55
|
+
*/
|
|
56
|
+
export interface BridgeWriteResult {
|
|
57
|
+
/** Content actually present on disk immediately after the bridge write. */
|
|
58
|
+
text: string;
|
|
59
|
+
/** `true` when `text` differs from the content the tool asked to write. */
|
|
60
|
+
driftedFromRequest: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
43
63
|
/**
|
|
44
64
|
* Try to route a file write through the ACP client bridge.
|
|
45
65
|
*
|
|
46
66
|
* Performs the full guard check, bridge call (wrapped in {@link ToolError}),
|
|
47
|
-
*
|
|
67
|
+
* a post-write read-back to detect client-side transformation (e.g.
|
|
68
|
+
* format-on-save), FS-scan cache invalidation, and session mutation-version
|
|
69
|
+
* bump.
|
|
48
70
|
*
|
|
49
|
-
* Returns `
|
|
50
|
-
*
|
|
51
|
-
*
|
|
71
|
+
* Returns `undefined` when the bridge is unavailable or the path should not
|
|
72
|
+
* be routed through it — the caller must fall back to the writethrough path.
|
|
73
|
+
* Returns a {@link BridgeWriteResult} when the bridge was used; callers MUST
|
|
74
|
+
* use `result.text` (not the content they requested) for any snapshot, hash,
|
|
75
|
+
* or tag derived from this write.
|
|
52
76
|
*/
|
|
53
77
|
export async function routeWriteThroughBridge(
|
|
54
78
|
session: ToolSession,
|
|
@@ -56,11 +80,11 @@ export async function routeWriteThroughBridge(
|
|
|
56
80
|
absolutePath: string,
|
|
57
81
|
content: string,
|
|
58
82
|
signal?: AbortSignal,
|
|
59
|
-
): Promise<
|
|
60
|
-
if (!shouldRouteWriteThroughBridge(session, requestedPath, absolutePath)) return
|
|
83
|
+
): Promise<BridgeWriteResult | undefined> {
|
|
84
|
+
if (!shouldRouteWriteThroughBridge(session, requestedPath, absolutePath)) return undefined;
|
|
61
85
|
|
|
62
86
|
const bridge = session.getClientBridge?.();
|
|
63
|
-
if (!bridge?.capabilities.writeTextFile || !bridge.writeTextFile) return
|
|
87
|
+
if (!bridge?.capabilities.writeTextFile || !bridge.writeTextFile) return undefined;
|
|
64
88
|
|
|
65
89
|
const changeType = (await Bun.file(absolutePath).exists()) ? FileChangeType.Changed : FileChangeType.Created;
|
|
66
90
|
// The ACP protocol has no cancellation for fs writes; the most we can do is
|
|
@@ -77,5 +101,25 @@ export async function routeWriteThroughBridge(
|
|
|
77
101
|
}
|
|
78
102
|
invalidateFsScanAfterWrite(absolutePath);
|
|
79
103
|
session.bumpFileMutationVersion?.(absolutePath);
|
|
80
|
-
|
|
104
|
+
|
|
105
|
+
// Best-effort verification: the client already flushed the write (that's
|
|
106
|
+
// the whole point of `fs/write_text_file`), so the file on disk reflects
|
|
107
|
+
// whatever the client actually persisted, formatter and all. If the
|
|
108
|
+
// read-back itself fails, fall back to trusting `content` rather than
|
|
109
|
+
// failing an otherwise-successful write. This is a best-effort signal,
|
|
110
|
+
// not a guarantee: ACP defines no ordering between a client acking the
|
|
111
|
+
// write and its own async format-on-save settling, so a client that acks
|
|
112
|
+
// before its formatter runs will look verbatim here. That degrades to
|
|
113
|
+
// the pre-fix behavior for THIS write, but never corrupts state: the
|
|
114
|
+
// next `readText` still observes whatever the client eventually settles
|
|
115
|
+
// on, and a real desync there surfaces as an honest stale-tag error
|
|
116
|
+
// instead of a silently wrong tag.
|
|
117
|
+
let actualText = content;
|
|
118
|
+
try {
|
|
119
|
+
actualText = await Bun.file(absolutePath).text();
|
|
120
|
+
} catch {
|
|
121
|
+
// Unreadable right after a reported-successful write; nothing more we
|
|
122
|
+
// can verify here.
|
|
123
|
+
}
|
|
124
|
+
return { text: actualText, driftedFromRequest: actualText !== content };
|
|
81
125
|
}
|
package/src/tools/ask.ts
CHANGED
|
@@ -36,7 +36,7 @@ import type { ExtensionUISelectItem } from "../extensibility/extensions";
|
|
|
36
36
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
37
37
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
38
38
|
import { vocalizer } from "../tts/vocalizer";
|
|
39
|
-
import { framedBlock, renderStatusLine } from "../tui";
|
|
39
|
+
import { framedBlock, outputBlockContentWidth, renderStatusLine } from "../tui";
|
|
40
40
|
import type { ToolSession } from ".";
|
|
41
41
|
import { formatErrorMessage, formatMeta, formatTitle } from "./render-utils";
|
|
42
42
|
import { ToolAbortError } from "./tool-errors";
|
|
@@ -1276,7 +1276,7 @@ export const askToolRenderer = {
|
|
|
1276
1276
|
const mdTheme = getMarkdownTheme();
|
|
1277
1277
|
const accentStyle = { color: (t: string) => uiTheme.fg("accent", t) };
|
|
1278
1278
|
const md = (text: string, width: number) =>
|
|
1279
|
-
new Markdown(text, 1, 0, mdTheme, accentStyle).render(Math.max(1, width
|
|
1279
|
+
new Markdown(text, 1, 0, mdTheme, accentStyle).render(Math.max(1, outputBlockContentWidth(width) + 1));
|
|
1280
1280
|
|
|
1281
1281
|
// Multi-part questions: one divider-labelled section per question.
|
|
1282
1282
|
// Call args are untrusted (partially streamed or model-mangled) and a
|
|
@@ -1345,7 +1345,7 @@ export const askToolRenderer = {
|
|
|
1345
1345
|
const mdTheme = getMarkdownTheme();
|
|
1346
1346
|
const accentStyle = { color: (t: string) => uiTheme.fg("accent", t) };
|
|
1347
1347
|
const md = (text: string, width: number) =>
|
|
1348
|
-
new Markdown(text, 1, 0, mdTheme, accentStyle).render(Math.max(1, width
|
|
1348
|
+
new Markdown(text, 1, 0, mdTheme, accentStyle).render(Math.max(1, outputBlockContentWidth(width) + 1));
|
|
1349
1349
|
|
|
1350
1350
|
if (!details) {
|
|
1351
1351
|
const txt = result.content[0];
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -12,7 +12,14 @@ import { normalizeToLF } from "../edit/normalize";
|
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import type { Theme } from "../modes/theme/theme";
|
|
14
14
|
import astEditDescription from "../prompts/tools/ast-edit.md" with { type: "text" };
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
Ellipsis,
|
|
17
|
+
fileHyperlink,
|
|
18
|
+
framedBlock,
|
|
19
|
+
outputBlockContentWidth,
|
|
20
|
+
renderStatusLine,
|
|
21
|
+
truncateToWidth,
|
|
22
|
+
} from "../tui";
|
|
16
23
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
17
24
|
import type { ToolSession } from ".";
|
|
18
25
|
import { truncateForPrompt } from "./approval";
|
|
@@ -695,7 +702,7 @@ export const astEditToolRenderer = {
|
|
|
695
702
|
}
|
|
696
703
|
return framedBlock(uiTheme, width => {
|
|
697
704
|
const changeLines = buildChangeBody(changeGroups, Boolean(options.expanded), COLLAPSED_CHANGE_LIMIT, uiTheme);
|
|
698
|
-
const innerWidth =
|
|
705
|
+
const innerWidth = outputBlockContentWidth(width);
|
|
699
706
|
const bodyLines = [...changeLines, ...extraLines].map(l => truncateToWidth(l, innerWidth, Ellipsis.Omit));
|
|
700
707
|
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
701
708
|
return {
|
|
@@ -37,7 +37,6 @@ import type { CmuxSocketClient } from "./socket-client";
|
|
|
37
37
|
interface ScreenshotOptions {
|
|
38
38
|
selector?: string;
|
|
39
39
|
fullPage?: boolean;
|
|
40
|
-
save?: string;
|
|
41
40
|
silent?: boolean;
|
|
42
41
|
encoding?: "base64" | "binary";
|
|
43
42
|
}
|
|
@@ -487,7 +486,7 @@ export class CmuxTab {
|
|
|
487
486
|
return content;
|
|
488
487
|
}
|
|
489
488
|
|
|
490
|
-
async screenshot(opts: ScreenshotOptions = {}): Promise<
|
|
489
|
+
async screenshot(opts: ScreenshotOptions = {}): Promise<string> {
|
|
491
490
|
const context = this.#requireRunContext("tab.screenshot()");
|
|
492
491
|
// The cmux daemon's `browser.screenshot` captures the surface viewport
|
|
493
492
|
// only — it has no element-clip or full-page mode, and Bun.Image cannot
|
|
@@ -518,20 +517,16 @@ export class CmuxTab {
|
|
|
518
517
|
excludeWebP: context.session.excludeWebP,
|
|
519
518
|
},
|
|
520
519
|
);
|
|
521
|
-
const
|
|
522
|
-
const returnedPath = typeof result.path === "string" && result.path.length > 0 ? result.path : undefined;
|
|
523
|
-
const saveFullRes = !!(explicitPath || context.session.browserScreenshotDir || returnedPath);
|
|
520
|
+
const saveFullRes = !!context.session.browserScreenshotDir;
|
|
524
521
|
const savedBuffer = saveFullRes ? buffer : Buffer.from(resized.buffer);
|
|
525
522
|
const savedMimeType = saveFullRes ? captureMime : resized.mimeType;
|
|
526
523
|
const ext = savedMimeType === "image/webp" ? "webp" : savedMimeType === "image/jpeg" ? "jpg" : "png";
|
|
527
|
-
const dest =
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
)
|
|
534
|
-
: (returnedPath ?? path.join(os.tmpdir(), `omp-sshots-${Snowflake.next()}.${ext}`)));
|
|
524
|
+
const dest = context.session.browserScreenshotDir
|
|
525
|
+
? path.join(
|
|
526
|
+
context.session.browserScreenshotDir,
|
|
527
|
+
`screenshot-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}.${ext}`,
|
|
528
|
+
)
|
|
529
|
+
: path.join(os.tmpdir(), `omp-sshots-${Snowflake.next()}.${ext}`);
|
|
535
530
|
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
536
531
|
await Bun.write(dest, savedBuffer);
|
|
537
532
|
const info: ScreenshotResult = {
|
|
@@ -556,7 +551,7 @@ export class CmuxTab {
|
|
|
556
551
|
context.output.push({ type: "text", text: lines.join("\n") });
|
|
557
552
|
context.output.push({ type: "image", data: resized.data, mimeType: resized.mimeType });
|
|
558
553
|
}
|
|
559
|
-
return
|
|
554
|
+
return dest;
|
|
560
555
|
}
|
|
561
556
|
|
|
562
557
|
async waitForUrl(pattern: string | RegExp, opts?: { timeout?: number }): Promise<string> {
|
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
ElementHandle,
|
|
12
12
|
ElementScreenshotOptions,
|
|
13
13
|
HTTPResponse,
|
|
14
|
-
ImageFormat,
|
|
15
14
|
KeyInput,
|
|
16
15
|
Page,
|
|
17
16
|
SerializedAXNode,
|
|
@@ -217,7 +216,6 @@ export function resolveWaitTimeout(cellTimeoutMs: number, explicit?: number): nu
|
|
|
217
216
|
interface ScreenshotOptions {
|
|
218
217
|
selector?: string;
|
|
219
218
|
fullPage?: boolean;
|
|
220
|
-
save?: string;
|
|
221
219
|
silent?: boolean;
|
|
222
220
|
}
|
|
223
221
|
|
|
@@ -233,7 +231,7 @@ interface TabApi {
|
|
|
233
231
|
): Promise<void>;
|
|
234
232
|
observe(opts?: { includeAll?: boolean; viewportOnly?: boolean }): Promise<Observation>;
|
|
235
233
|
ariaSnapshot(selector?: string, opts?: AriaSnapshotOptions): Promise<string>;
|
|
236
|
-
screenshot(opts?: ScreenshotOptions): Promise<
|
|
234
|
+
screenshot(opts?: ScreenshotOptions): Promise<string>;
|
|
237
235
|
extract(format?: ReadableFormat): Promise<string>;
|
|
238
236
|
click(selector: string): Promise<void>;
|
|
239
237
|
type(selector: string, text: string): Promise<void>;
|
|
@@ -714,19 +712,6 @@ export function describeScreenshot(opts?: ScreenshotOptions): string {
|
|
|
714
712
|
return "tab.screenshot()";
|
|
715
713
|
}
|
|
716
714
|
|
|
717
|
-
/** Map an explicit save path's extension to a puppeteer capture format (default png). */
|
|
718
|
-
export function imageFormatForPath(filePath: string): ImageFormat {
|
|
719
|
-
switch (path.extname(filePath).toLowerCase()) {
|
|
720
|
-
case ".webp":
|
|
721
|
-
return "webp";
|
|
722
|
-
case ".jpg":
|
|
723
|
-
case ".jpeg":
|
|
724
|
-
return "jpeg";
|
|
725
|
-
default:
|
|
726
|
-
return "png";
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
715
|
/** Summarize still-running helpers (oldest first) so a cell timeout names what stalled. */
|
|
731
716
|
export function describeInflight(inflight: Map<number, InflightOp>): string {
|
|
732
717
|
const now = Date.now();
|
|
@@ -1532,7 +1517,7 @@ export class WorkerCore {
|
|
|
1532
1517
|
screenshots: ScreenshotResult[],
|
|
1533
1518
|
signal: AbortSignal | undefined,
|
|
1534
1519
|
opts: ScreenshotOptions = {},
|
|
1535
|
-
): Promise<
|
|
1520
|
+
): Promise<string> {
|
|
1536
1521
|
const page = this.#requirePage();
|
|
1537
1522
|
// Multiple tabs can share one Chromium (sibling headless tabs on a shared
|
|
1538
1523
|
// endpoint, cdp/app attach). CDP `Page.captureScreenshot` reads the
|
|
@@ -1542,12 +1527,8 @@ export class WorkerCore {
|
|
|
1542
1527
|
// already-active or freshly-closed target never fails the capture.
|
|
1543
1528
|
await untilAborted(signal, () => page.bringToFront()).catch(() => undefined);
|
|
1544
1529
|
const fullPage = opts.selector ? false : (opts.fullPage ?? false);
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
// of PNG bytes hiding behind a .webp name. Unknown/missing extensions stay PNG.
|
|
1548
|
-
const explicitPath = opts.save ? resolveToCwd(opts.save, session.cwd) : undefined;
|
|
1549
|
-
const captureType = explicitPath ? imageFormatForPath(explicitPath) : "png";
|
|
1550
|
-
const captureMime = `image/${captureType}` as const;
|
|
1530
|
+
const captureType = "png";
|
|
1531
|
+
const captureMime = "image/png" as const;
|
|
1551
1532
|
let buffer: Buffer;
|
|
1552
1533
|
if (opts.selector) {
|
|
1553
1534
|
const handle =
|
|
@@ -1581,20 +1562,16 @@ export class WorkerCore {
|
|
|
1581
1562
|
{ type: "image", data: buffer.toBase64(), mimeType: captureMime },
|
|
1582
1563
|
{ maxWidth: 1024, maxHeight: 1024, maxBytes: 150 * 1024, jpegQuality: 70, excludeWebP: session.excludeWebP },
|
|
1583
1564
|
);
|
|
1584
|
-
const saveFullRes = !!
|
|
1565
|
+
const saveFullRes = !!session.browserScreenshotDir;
|
|
1585
1566
|
const savedBuffer = saveFullRes ? buffer : resized.buffer;
|
|
1586
1567
|
const savedMimeType = saveFullRes ? captureMime : resized.mimeType;
|
|
1587
|
-
// Names must match the bytes we actually write: full-res follows the capture
|
|
1588
|
-
// format, the resized buffer is whichever of PNG/JPEG/WebP encoded smallest.
|
|
1589
1568
|
const ext = savedMimeType === "image/webp" ? "webp" : savedMimeType === "image/jpeg" ? "jpg" : "png";
|
|
1590
|
-
const dest =
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
)
|
|
1597
|
-
: path.join(os.tmpdir(), `omp-sshots-${Snowflake.next()}.${ext}`));
|
|
1569
|
+
const dest = session.browserScreenshotDir
|
|
1570
|
+
? path.join(
|
|
1571
|
+
session.browserScreenshotDir,
|
|
1572
|
+
`screenshot-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}.${ext}`,
|
|
1573
|
+
)
|
|
1574
|
+
: path.join(os.tmpdir(), `omp-sshots-${Snowflake.next()}.${ext}`);
|
|
1598
1575
|
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
1599
1576
|
await Bun.write(dest, savedBuffer);
|
|
1600
1577
|
const info: ScreenshotResult = {
|
|
@@ -1616,7 +1593,7 @@ export class WorkerCore {
|
|
|
1616
1593
|
output.push({ type: "text", text: lines.join("\n") });
|
|
1617
1594
|
output.push({ type: "image", data: resized.data, mimeType: resized.mimeType });
|
|
1618
1595
|
}
|
|
1619
|
-
return
|
|
1596
|
+
return dest;
|
|
1620
1597
|
}
|
|
1621
1598
|
|
|
1622
1599
|
async #drag(from: DragTarget, to: DragTarget, signal: AbortSignal): Promise<void> {
|
package/src/tools/browser.ts
CHANGED
|
@@ -95,6 +95,11 @@ function resolveBrowserKind(params: BrowserParams, session: ToolSession): Browse
|
|
|
95
95
|
const exe = resolveToCwd(app.path, session.cwd);
|
|
96
96
|
return { kind: "spawned", path: exe };
|
|
97
97
|
}
|
|
98
|
+
// A configured endpoint is a default, not an override: explicit app options win.
|
|
99
|
+
const configuredCdpUrl = (session.settings.get("browser.cdpUrl") as string | undefined)?.trim();
|
|
100
|
+
if (configuredCdpUrl) {
|
|
101
|
+
return { kind: "connected", cdpUrl: configuredCdpUrl.replace(/\/+$/, "") };
|
|
102
|
+
}
|
|
98
103
|
const cmuxKind = resolveCmuxKind({
|
|
99
104
|
settingEnabled: session.settings.get("browser.cmux") as boolean | undefined,
|
|
100
105
|
});
|
|
@@ -163,11 +168,11 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
163
168
|
},
|
|
164
169
|
},
|
|
165
170
|
{
|
|
166
|
-
caption: "
|
|
171
|
+
caption: "Capture a screenshot and return its saved path",
|
|
167
172
|
call: {
|
|
168
173
|
action: "run",
|
|
169
174
|
name: "docs",
|
|
170
|
-
code: "await tab.screenshot();",
|
|
175
|
+
code: "return await tab.screenshot();",
|
|
171
176
|
},
|
|
172
177
|
},
|
|
173
178
|
{
|
package/src/tools/checkpoint.ts
CHANGED
|
@@ -50,11 +50,6 @@ export interface RewindToolDetails {
|
|
|
50
50
|
meta?: OutputMeta;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function isTopLevelSession(session: ToolSession): boolean {
|
|
54
|
-
const depth = session.taskDepth;
|
|
55
|
-
return depth === undefined || depth === 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
53
|
export class CheckpointTool implements AgentTool<typeof checkpointSchema, CheckpointToolDetails> {
|
|
59
54
|
readonly name = "checkpoint";
|
|
60
55
|
readonly approval = "read" as const;
|
|
@@ -71,7 +66,6 @@ export class CheckpointTool implements AgentTool<typeof checkpointSchema, Checkp
|
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
static createIf(session: ToolSession): CheckpointTool | null {
|
|
74
|
-
if (!isTopLevelSession(session)) return null;
|
|
75
69
|
return new CheckpointTool(session);
|
|
76
70
|
}
|
|
77
71
|
|
|
@@ -82,9 +76,6 @@ export class CheckpointTool implements AgentTool<typeof checkpointSchema, Checkp
|
|
|
82
76
|
_onUpdate?: AgentToolUpdateCallback<CheckpointToolDetails>,
|
|
83
77
|
_context?: AgentToolContext,
|
|
84
78
|
): Promise<AgentToolResult<CheckpointToolDetails>> {
|
|
85
|
-
if (!isTopLevelSession(this.session)) {
|
|
86
|
-
throw new ToolError("Checkpoint not available in subagents.");
|
|
87
|
-
}
|
|
88
79
|
if (this.session.getCheckpointState?.()) {
|
|
89
80
|
throw new ToolError("Checkpoint already active.");
|
|
90
81
|
}
|
|
@@ -117,7 +108,6 @@ export class RewindTool implements AgentTool<typeof rewindSchema, RewindToolDeta
|
|
|
117
108
|
}
|
|
118
109
|
|
|
119
110
|
static createIf(session: ToolSession): RewindTool | null {
|
|
120
|
-
if (!isTopLevelSession(session)) return null;
|
|
121
111
|
return new RewindTool(session);
|
|
122
112
|
}
|
|
123
113
|
|
|
@@ -128,9 +118,6 @@ export class RewindTool implements AgentTool<typeof rewindSchema, RewindToolDeta
|
|
|
128
118
|
_onUpdate?: AgentToolUpdateCallback<RewindToolDetails>,
|
|
129
119
|
_context?: AgentToolContext,
|
|
130
120
|
): Promise<AgentToolResult<RewindToolDetails>> {
|
|
131
|
-
if (!isTopLevelSession(this.session)) {
|
|
132
|
-
throw new ToolError("Checkpoint not available in subagents.");
|
|
133
|
-
}
|
|
134
121
|
if (!this.session.getCheckpointState?.()) {
|
|
135
122
|
if (this.session.getLastCompletedRewind?.()) {
|
|
136
123
|
throw new ToolError(
|
package/src/tools/gh-renderer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Component, padding, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
3
3
|
import type { Theme, ThemeColor } from "../modes/theme/theme";
|
|
4
|
-
import { framedBlock, renderStatusLine } from "../tui";
|
|
4
|
+
import { framedBlock, outputBlockContentWidth, renderStatusLine } from "../tui";
|
|
5
5
|
import type {
|
|
6
6
|
GhRunWatchFailedLogDetails,
|
|
7
7
|
GhRunWatchJobDetails,
|
|
@@ -364,7 +364,7 @@ function renderFallbackComponent(
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
return framedBlock(theme, width => {
|
|
367
|
-
const lineWidth =
|
|
367
|
+
const lineWidth = outputBlockContentWidth(width || FALLBACK_WIDTH);
|
|
368
368
|
const expanded = options.expanded;
|
|
369
369
|
const limit = expanded ? allLines.length : Math.min(allLines.length, PREVIEW_LIMITS.OUTPUT_EXPANDED);
|
|
370
370
|
const visible = allLines.slice(0, limit);
|
|
@@ -464,7 +464,7 @@ export const githubToolRenderer = {
|
|
|
464
464
|
uiTheme,
|
|
465
465
|
);
|
|
466
466
|
return framedBlock(uiTheme, width => {
|
|
467
|
-
const innerWidth =
|
|
467
|
+
const innerWidth = outputBlockContentWidth(width || FALLBACK_WIDTH);
|
|
468
468
|
const sections = buildWatchSections(watch, uiTheme, options, innerWidth);
|
|
469
469
|
return {
|
|
470
470
|
header,
|