@oh-my-pi/pi-coding-agent 16.4.8 → 16.5.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 +63 -0
- package/dist/cli.js +3502 -3444
- package/dist/types/cli/args.d.ts +5 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +15 -0
- package/dist/types/config/model-resolver.d.ts +4 -3
- package/dist/types/config/model-roles.d.ts +8 -0
- package/dist/types/config/settings-schema.d.ts +46 -14
- package/dist/types/extensibility/extensions/types.d.ts +1 -1
- package/dist/types/launch/broker.d.ts +2 -0
- package/dist/types/launch/client.d.ts +22 -0
- package/dist/types/launch/paths.d.ts +4 -0
- package/dist/types/launch/presence.d.ts +8 -0
- package/dist/types/launch/protocol.d.ts +170 -0
- package/dist/types/launch/terminal-output.d.ts +7 -0
- package/dist/types/modes/components/agent-hub.d.ts +1 -1
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/model-browser.d.ts +17 -2
- package/dist/types/modes/components/model-hub.d.ts +0 -10
- package/dist/types/modes/components/model-picker.d.ts +44 -0
- package/dist/types/modes/components/status-line/types.d.ts +3 -0
- package/dist/types/modes/print-mode.d.ts +14 -8
- package/dist/types/modes/print-mode.test.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/sdk.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +42 -0
- package/dist/types/session/session-context.d.ts +9 -0
- package/dist/types/session/session-entries.d.ts +6 -0
- package/dist/types/thinking.d.ts +2 -2
- package/dist/types/tiny/models.d.ts +1 -1
- package/dist/types/tools/browser/launch.d.ts +1 -0
- package/dist/types/tools/browser/run-cancellation.d.ts +28 -2
- package/dist/types/tools/browser/tab-protocol.d.ts +6 -0
- package/dist/types/tools/browser/tab-worker.d.ts +6 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/launch.d.ts +121 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/terminal-output.d.ts +5 -0
- package/dist/types/vibe/runtime.d.ts +2 -2
- package/dist/types/web/search/types.d.ts +0 -8
- package/package.json +20 -20
- package/src/cli/args.ts +11 -0
- package/src/cli/flag-tables.ts +9 -0
- package/src/cli/gallery-fixtures/shell.ts +82 -1
- package/src/cli.ts +10 -0
- package/src/commands/launch.ts +17 -0
- package/src/config/model-resolver.ts +110 -31
- package/src/config/model-roles.ts +14 -0
- package/src/config/settings-schema.ts +71 -7
- package/src/edit/renderer.ts +13 -10
- package/src/eval/__tests__/agent-bridge.test.ts +2 -2
- package/src/eval/__tests__/completion-bridge.test.ts +1 -1
- package/src/eval/completion-bridge.ts +4 -4
- package/src/export/ttsr.ts +0 -3
- package/src/extensibility/extensions/model-api.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/internal-urls/docs-index.ts +4 -4
- package/src/launch/broker.ts +1017 -0
- package/src/launch/client.ts +344 -0
- package/src/launch/paths.ts +17 -0
- package/src/launch/presence.ts +82 -0
- package/src/launch/protocol.ts +386 -0
- package/src/launch/terminal-output.ts +46 -0
- package/src/main.ts +50 -1
- package/src/modes/acp/acp-agent.ts +8 -1
- package/src/modes/components/agent-hub.ts +101 -31
- package/src/modes/components/compaction-summary-message.ts +8 -2
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/model-browser.ts +111 -21
- package/src/modes/components/model-hub.ts +23 -116
- package/src/modes/components/model-picker.ts +233 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +7 -11
- package/src/modes/components/status-line/component.test.ts +41 -2
- package/src/modes/components/status-line/component.ts +4 -0
- package/src/modes/components/status-line/segments.ts +6 -0
- package/src/modes/components/status-line/types.ts +3 -0
- package/src/modes/controllers/command-controller.ts +9 -1
- package/src/modes/controllers/event-controller.ts +31 -32
- package/src/modes/controllers/selector-controller.ts +96 -22
- package/src/modes/controllers/tan-command-controller.ts +40 -1
- package/src/modes/interactive-mode.ts +7 -3
- package/src/modes/print-mode.test.ts +71 -0
- package/src/modes/print-mode.ts +51 -2
- package/src/modes/theme/theme.ts +9 -0
- package/src/modes/utils/ui-helpers.ts +25 -4
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/scout.md +1 -1
- package/src/prompts/system/plan-yolo-handoff.md +5 -0
- package/src/prompts/system/prewalk-checklist.md +7 -0
- package/src/prompts/system/prewalk-continue.md +1 -0
- package/src/prompts/system/prewalk-plan.md +13 -0
- package/src/prompts/system/system-prompt.md +9 -7
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/tools/bash.md +6 -4
- package/src/prompts/tools/browser.md +4 -4
- package/src/prompts/tools/launch.md +25 -0
- package/src/sdk.ts +8 -2
- package/src/session/agent-session.ts +550 -87
- package/src/session/session-context.test.ts +10 -5
- package/src/session/session-context.ts +25 -8
- package/src/session/session-entries.ts +6 -0
- package/src/slash-commands/builtin-registry.ts +25 -0
- package/src/task/agents.ts +2 -2
- package/src/thinking.ts +10 -3
- package/src/tiny/models.ts +7 -7
- package/src/tools/bash-interactive.ts +5 -8
- package/src/tools/bash.ts +38 -24
- package/src/tools/browser/cmux/cmux-tab.ts +17 -2
- package/src/tools/browser/launch.ts +8 -4
- package/src/tools/browser/run-cancellation.ts +66 -6
- package/src/tools/browser/tab-protocol.ts +6 -0
- package/src/tools/browser/tab-supervisor.ts +17 -1
- package/src/tools/browser/tab-worker.ts +140 -21
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/eval-render.ts +16 -14
- package/src/tools/index.ts +5 -0
- package/src/tools/inspect-image.ts +2 -2
- package/src/tools/launch.ts +643 -0
- package/src/tools/render-utils.ts +3 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/terminal-output.ts +141 -0
- package/src/tts/speech-enhancer.ts +2 -2
- package/src/utils/image-vision-fallback.ts +3 -3
- package/src/vibe/runtime.ts +2 -2
- package/src/web/search/provider.ts +0 -10
- package/src/web/search/providers/public.ts +2 -4
- package/src/web/search/types.ts +0 -10
- package/dist/types/web/search/providers/bing.d.ts +0 -14
- package/dist/types/web/search/providers/yahoo.d.ts +0 -14
- package/src/web/search/providers/bing.ts +0 -197
- package/src/web/search/providers/yahoo.ts +0 -179
|
@@ -131,6 +131,10 @@ const tabs = new Map<string, TabSession>();
|
|
|
131
131
|
// awaits) cannot interleave and leak a worker + browser refCount.
|
|
132
132
|
const acquireChains = new Map<string, Promise<void>>();
|
|
133
133
|
const GRACE_MS = 750;
|
|
134
|
+
// Names of tabs the supervisor force-killed (timeout past grace, failed recycle),
|
|
135
|
+
// mapped to the kill reason. Lets the next `run` on that name explain WHY the tab
|
|
136
|
+
// vanished instead of a bare "not alive". Cleared when the name is opened again.
|
|
137
|
+
const killedTabs = new Map<string, string>();
|
|
134
138
|
|
|
135
139
|
export function getTab(name: string): TabSession | undefined {
|
|
136
140
|
return tabs.get(name);
|
|
@@ -161,6 +165,7 @@ async function acquireTabImpl(
|
|
|
161
165
|
if (opts.signal?.aborted) {
|
|
162
166
|
throw new ToolAbortError("Browser tab open aborted");
|
|
163
167
|
}
|
|
168
|
+
killedTabs.delete(name);
|
|
164
169
|
// Temporary refCount hold so releasing an existing tab on the SAME browser
|
|
165
170
|
// below cannot drop it to refCount 0 and dispose the instance we are about
|
|
166
171
|
// to reuse (e.g. reopening the sole tab with a different dialogs policy).
|
|
@@ -386,7 +391,14 @@ async function runInTabWithSnapshot(
|
|
|
386
391
|
snapshot: SessionSnapshot,
|
|
387
392
|
): Promise<RunResultOk> {
|
|
388
393
|
const tab = tabs.get(name);
|
|
389
|
-
if (!tab || tab.state === "dead")
|
|
394
|
+
if (!tab || tab.state === "dead") {
|
|
395
|
+
const killed = killedTabs.get(name);
|
|
396
|
+
throw new ToolError(
|
|
397
|
+
killed
|
|
398
|
+
? `Tab ${JSON.stringify(name)} was killed: ${killed}. Reopen it.`
|
|
399
|
+
: `Tab ${JSON.stringify(name)} is not alive. Open it first with action:"open".`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
390
402
|
if (tab.pending.size > 0) throw new ToolError(`Tab ${JSON.stringify(name)} is busy`);
|
|
391
403
|
const id = Snowflake.next();
|
|
392
404
|
const { promise, resolve, reject } = Promise.withResolvers<RunResultOk>();
|
|
@@ -712,6 +724,9 @@ async function recycleTimedOutWorkerTab(tab: WorkerTabSession, timeoutMs: number
|
|
|
712
724
|
safeDir: getPuppeteerDir(),
|
|
713
725
|
targetId: tab.targetId,
|
|
714
726
|
dialogs: tab.dialogPolicy,
|
|
727
|
+
// Unblock a wedged page (open JS dialog, hung navigation) before adopting it —
|
|
728
|
+
// otherwise init stalls, times out, and the tab gets force-killed.
|
|
729
|
+
recover: true,
|
|
715
730
|
};
|
|
716
731
|
let worker = await spawnTabWorker();
|
|
717
732
|
try {
|
|
@@ -743,6 +758,7 @@ async function recycleTimedOutWorkerTab(tab: WorkerTabSession, timeoutMs: number
|
|
|
743
758
|
async function forceKillTab(name: string, reason: string): Promise<void> {
|
|
744
759
|
const tab = tabs.get(name);
|
|
745
760
|
if (!tab) return;
|
|
761
|
+
killedTabs.set(name, reason);
|
|
746
762
|
tab.state = "dead";
|
|
747
763
|
const error = postmortem.markExpectedCleanupError(new ToolError(reason));
|
|
748
764
|
for (const pending of tab.pending.values()) pending.reject(error);
|
|
@@ -6,6 +6,7 @@ import { postmortem, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
6
6
|
import type { HTMLElement } from "linkedom";
|
|
7
7
|
import type {
|
|
8
8
|
Browser,
|
|
9
|
+
CDPSession,
|
|
9
10
|
Dialog,
|
|
10
11
|
ElementHandle,
|
|
11
12
|
ElementScreenshotOptions,
|
|
@@ -35,7 +36,13 @@ import {
|
|
|
35
36
|
loadPuppeteerInWorker,
|
|
36
37
|
} from "./launch";
|
|
37
38
|
import { extractReadableFromHtml, type ReadableFormat } from "./readable";
|
|
38
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
CELL_BUDGET_SLACK_MS,
|
|
41
|
+
markHandled,
|
|
42
|
+
resolvePredicateTimeout,
|
|
43
|
+
type WaitPredicateOptions,
|
|
44
|
+
waitForBrowserRun,
|
|
45
|
+
} from "./run-cancellation";
|
|
39
46
|
import { cloneSafe, RunOutput } from "./run-output";
|
|
40
47
|
import type {
|
|
41
48
|
Observation,
|
|
@@ -50,6 +57,13 @@ import type {
|
|
|
50
57
|
WorkerInitPayload,
|
|
51
58
|
} from "./tab-protocol";
|
|
52
59
|
|
|
60
|
+
declare module "puppeteer-core" {
|
|
61
|
+
interface Frame {
|
|
62
|
+
/** Puppeteer's main JavaScript realm, retained by our pinned runtime patch. */
|
|
63
|
+
mainRealm(): Realm;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
declare global {
|
|
54
68
|
interface Element extends HTMLElement {}
|
|
55
69
|
function getComputedStyle(element: Element): Record<string, unknown>;
|
|
@@ -105,6 +119,11 @@ const PLAYWRIGHT_ONLY_SELECTOR_RE =
|
|
|
105
119
|
type DialogPolicy = "accept" | "dismiss";
|
|
106
120
|
type DragTarget = string | { readonly x: number; readonly y: number };
|
|
107
121
|
type ActionabilityResult = { ok: true; x: number; y: number } | { ok: false; reason: string };
|
|
122
|
+
/** Last JS dialog seen on the page; kept for timeout attribution until handled or navigation. */
|
|
123
|
+
interface OpenDialogInfo {
|
|
124
|
+
type: string;
|
|
125
|
+
message: string;
|
|
126
|
+
}
|
|
108
127
|
|
|
109
128
|
/**
|
|
110
129
|
* Per-op fail-fast ceilings for `tab.*` helpers. All are kept strictly under the cell
|
|
@@ -126,7 +145,7 @@ type ActionabilityResult = { ok: true; x: number; y: number } | { ok: false; rea
|
|
|
126
145
|
const QUICK_OP_TIMEOUT_MS = 20_000;
|
|
127
146
|
const ACTION_OP_TIMEOUT_MS = 8_000;
|
|
128
147
|
/** Headroom subtracted from the cell budget so a per-op deadline fires before it. */
|
|
129
|
-
const OP_DEADLINE_SLACK_MS =
|
|
148
|
+
const OP_DEADLINE_SLACK_MS = CELL_BUDGET_SLACK_MS;
|
|
130
149
|
/**
|
|
131
150
|
* A selector op whose selector has matched nothing for this long fails fast with the
|
|
132
151
|
* zero-match hint instead of burning the rest of its deadline: a wrong selector or a
|
|
@@ -595,6 +614,7 @@ export class WorkerCore {
|
|
|
595
614
|
#mode?: WorkerInitPayload["mode"];
|
|
596
615
|
#dialogPolicy?: DialogPolicy;
|
|
597
616
|
#dialogHandler?: (dialog: Dialog) => void;
|
|
617
|
+
#openDialog?: OpenDialogInfo;
|
|
598
618
|
|
|
599
619
|
constructor(transport: Transport) {
|
|
600
620
|
this.#transport = transport;
|
|
@@ -648,6 +668,7 @@ export class WorkerCore {
|
|
|
648
668
|
});
|
|
649
669
|
if (payload.mode === "headless") {
|
|
650
670
|
this.#page = await this.#browser.newPage();
|
|
671
|
+
this.#observeDialogs();
|
|
651
672
|
await applyStealthPatches(this.#browser, this.#page, { browserSession: null, override: null });
|
|
652
673
|
await applyViewport(this.#page, payload.viewport);
|
|
653
674
|
if (payload.dialogs) this.#applyDialogPolicy(payload.dialogs);
|
|
@@ -659,7 +680,15 @@ export class WorkerCore {
|
|
|
659
680
|
});
|
|
660
681
|
}
|
|
661
682
|
} else {
|
|
662
|
-
|
|
683
|
+
const target = await this.#findAttachedTarget(payload.targetId);
|
|
684
|
+
// Post-timeout recycle: unblock the target BEFORE adopting the page — an open
|
|
685
|
+
// modal dialog or hung navigation can stall `target.page()` / ready info, and a
|
|
686
|
+
// stalled init used to time out and force-kill the tab.
|
|
687
|
+
if (payload.recover) await this.#recoverAttachedTarget(target);
|
|
688
|
+
const page = await target.page();
|
|
689
|
+
if (!page) throw new ToolError(`Target ${payload.targetId} is no longer available on the attached browser`);
|
|
690
|
+
this.#page = page;
|
|
691
|
+
this.#observeDialogs();
|
|
663
692
|
if (payload.dialogs) this.#applyDialogPolicy(payload.dialogs);
|
|
664
693
|
}
|
|
665
694
|
this.#targetId = await targetIdForPage(this.#page);
|
|
@@ -669,17 +698,53 @@ export class WorkerCore {
|
|
|
669
698
|
}
|
|
670
699
|
}
|
|
671
700
|
|
|
672
|
-
async #
|
|
701
|
+
async #findAttachedTarget(targetId: string): Promise<Target> {
|
|
673
702
|
if (!this.#browser) throw new ToolError("Browser is not connected");
|
|
674
703
|
for (const target of this.#browser.targets()) {
|
|
675
704
|
if ((await targetIdForTarget(target).catch(() => "")) !== targetId) continue;
|
|
676
|
-
|
|
677
|
-
if (!page) break;
|
|
678
|
-
return page;
|
|
705
|
+
return target;
|
|
679
706
|
}
|
|
680
707
|
throw new ToolError(`Target ${targetId} is no longer available on the attached browser`);
|
|
681
708
|
}
|
|
682
709
|
|
|
710
|
+
/**
|
|
711
|
+
* Best-effort unblocking of a wedged target during post-timeout recovery: dismiss any
|
|
712
|
+
* open JS dialog and stop a pending navigation over a raw CDP session (created on the
|
|
713
|
+
* target, not the page, so it works while the page itself is unresponsive). Every step
|
|
714
|
+
* tolerates "nothing to do".
|
|
715
|
+
*/
|
|
716
|
+
async #recoverAttachedTarget(target: Target): Promise<void> {
|
|
717
|
+
let session: CDPSession | undefined;
|
|
718
|
+
try {
|
|
719
|
+
session = await target.createCDPSession();
|
|
720
|
+
await session.send("Page.enable").catch(() => undefined);
|
|
721
|
+
await session.send("Page.handleJavaScriptDialog", { accept: false }).catch(() => undefined);
|
|
722
|
+
await session.send("Page.stopLoading").catch(() => undefined);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
this.#log("debug", "Recovery CDP session failed; proceeding with attach", {
|
|
725
|
+
error: error instanceof Error ? error.message : String(error),
|
|
726
|
+
});
|
|
727
|
+
} finally {
|
|
728
|
+
await session?.detach().catch(() => undefined);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Record JS dialogs for timeout attribution without handling them (semantics of an
|
|
734
|
+
* unset `dialogs` policy are unchanged — the page stays blocked until user code or
|
|
735
|
+
* the policy handler acts). Cleared when the policy handler settles the dialog or a
|
|
736
|
+
* main-frame navigation proves the modal is gone.
|
|
737
|
+
*/
|
|
738
|
+
#observeDialogs(): void {
|
|
739
|
+
const page = this.#requirePage();
|
|
740
|
+
page.on("dialog", dialog => {
|
|
741
|
+
this.#openDialog = { type: dialog.type(), message: dialog.message() };
|
|
742
|
+
});
|
|
743
|
+
page.on("framenavigated", frame => {
|
|
744
|
+
if (frame === page.mainFrame()) this.#openDialog = undefined;
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
683
748
|
async #currentReadyInfo(): Promise<ReadyInfo> {
|
|
684
749
|
const page = this.#requirePage();
|
|
685
750
|
const targetId = this.#targetId ?? (await targetIdForPage(page));
|
|
@@ -698,11 +763,15 @@ export class WorkerCore {
|
|
|
698
763
|
if (this.#dialogHandler) page.off("dialog", this.#dialogHandler);
|
|
699
764
|
const handler = (dialog: Dialog): void => {
|
|
700
765
|
const action = policy === "accept" ? dialog.accept() : dialog.dismiss();
|
|
701
|
-
void action.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
766
|
+
void action.then(
|
|
767
|
+
() => {
|
|
768
|
+
this.#openDialog = undefined;
|
|
769
|
+
},
|
|
770
|
+
err =>
|
|
771
|
+
this.#log("debug", "Dialog auto-handler failed", {
|
|
772
|
+
policy,
|
|
773
|
+
error: err instanceof Error ? err.message : String(err),
|
|
774
|
+
}),
|
|
706
775
|
);
|
|
707
776
|
};
|
|
708
777
|
page.on("dialog", handler);
|
|
@@ -761,7 +830,20 @@ export class WorkerCore {
|
|
|
761
830
|
assert: (cond: unknown, text?: string): void => {
|
|
762
831
|
if (!cond) throw new ToolError(text ?? "Assertion failed");
|
|
763
832
|
},
|
|
764
|
-
wait
|
|
833
|
+
// Both wait forms register in the in-flight map so a cell that dies while
|
|
834
|
+
// sleeping/polling names the culprit instead of a bare whole-cell timeout.
|
|
835
|
+
wait: (msOrPredicate: number | (() => unknown), opts?: WaitPredicateOptions): Promise<unknown> => {
|
|
836
|
+
const label = typeof msOrPredicate === "number" ? `wait(${msOrPredicate}ms)` : "wait(predicate)";
|
|
837
|
+
const resolved =
|
|
838
|
+
typeof msOrPredicate === "number"
|
|
839
|
+
? undefined
|
|
840
|
+
: { timeout: resolvePredicateTimeout(msg.timeoutMs, opts?.timeout), interval: opts?.interval };
|
|
841
|
+
return markHandled(
|
|
842
|
+
this.#runOp(active, label, signal, Number.POSITIVE_INFINITY, sig =>
|
|
843
|
+
waitForBrowserRun(msOrPredicate, sig, resolved),
|
|
844
|
+
),
|
|
845
|
+
);
|
|
846
|
+
},
|
|
765
847
|
});
|
|
766
848
|
const { promise: cancelRejection, reject: rejectCancel } = Promise.withResolvers<never>();
|
|
767
849
|
const onCancel = (): void => {
|
|
@@ -771,9 +853,13 @@ export class WorkerCore {
|
|
|
771
853
|
: new ToolAbortError(undefined, { cause: signal.reason });
|
|
772
854
|
if (timeoutSignal.aborted) {
|
|
773
855
|
const stalled = describeInflight(active.inflight);
|
|
856
|
+
const dialog = this.#openDialog;
|
|
857
|
+
const dialogNote = dialog
|
|
858
|
+
? `; a ${dialog.type}(${JSON.stringify(dialog.message.slice(0, 80))}) dialog opened during this run and may still block the page — reopen the tab with dialogs:"accept"|"dismiss" or handle page.on('dialog')`
|
|
859
|
+
: "";
|
|
774
860
|
rejectCancel(
|
|
775
861
|
new ToolError(
|
|
776
|
-
`Browser code execution timed out after ${msg.timeoutMs}ms${stalled ? ` (stalled on ${stalled})` : ""}`,
|
|
862
|
+
`Browser code execution timed out after ${msg.timeoutMs}ms${stalled ? ` (stalled on ${stalled})` : ""}${dialogNote}`,
|
|
777
863
|
),
|
|
778
864
|
);
|
|
779
865
|
} else {
|
|
@@ -985,7 +1071,7 @@ export class WorkerCore {
|
|
|
985
1071
|
active: ActiveRun,
|
|
986
1072
|
): TabApi {
|
|
987
1073
|
const page = this.#requirePage();
|
|
988
|
-
const { quickOpMs, actionOpMs } = resolveOpTimeouts(timeoutMs);
|
|
1074
|
+
const { budgetBound, quickOpMs, actionOpMs } = resolveOpTimeouts(timeoutMs);
|
|
989
1075
|
const waitMs = (explicit?: number): number => resolveWaitTimeout(timeoutMs, explicit);
|
|
990
1076
|
const INF = Number.POSITIVE_INFINITY;
|
|
991
1077
|
const op = <T>(
|
|
@@ -1003,10 +1089,24 @@ export class WorkerCore {
|
|
|
1003
1089
|
goto: (url, opts) =>
|
|
1004
1090
|
op(`tab.goto(${JSON.stringify(url)})`, INF, async sig => {
|
|
1005
1091
|
this.#clearElementCache();
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1092
|
+
try {
|
|
1093
|
+
// Default to "load" because dev servers with HMR/WS never reach networkidle.
|
|
1094
|
+
// budgetBound (not the full cell) so a hung navigation fails named and
|
|
1095
|
+
// catchable inside the run instead of dying with the whole cell.
|
|
1096
|
+
await untilAborted(sig, () =>
|
|
1097
|
+
page.goto(url, { waitUntil: opts?.waitUntil ?? "load", timeout: budgetBound }),
|
|
1098
|
+
);
|
|
1099
|
+
} catch (err) {
|
|
1100
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
1101
|
+
// Abandon the hung navigation NOW — a still-pending load stalls every
|
|
1102
|
+
// later op on this page and cascades into more opaque timeouts.
|
|
1103
|
+
await this.#stopLoading();
|
|
1104
|
+
throw new ToolError(
|
|
1105
|
+
`tab.goto(${JSON.stringify(url)}) timed out after ${budgetBound}ms; pending navigation stopped — retry with a longer tool timeout or waitUntil:"domcontentloaded"`,
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
throw err;
|
|
1109
|
+
}
|
|
1010
1110
|
}),
|
|
1011
1111
|
observe: opts => op("tab.observe()", quickOpMs, sig => this.#collectObservation({ ...opts, signal: sig })),
|
|
1012
1112
|
ariaSnapshot: (selector, opts) =>
|
|
@@ -1164,8 +1264,11 @@ export class WorkerCore {
|
|
|
1164
1264
|
op("tab.evaluate()", INF, sig =>
|
|
1165
1265
|
untilAborted(sig, () =>
|
|
1166
1266
|
typeof fn === "string"
|
|
1167
|
-
? page.evaluate(fn)
|
|
1168
|
-
: page
|
|
1267
|
+
? page.mainFrame().mainRealm().evaluate(fn)
|
|
1268
|
+
: page
|
|
1269
|
+
.mainFrame()
|
|
1270
|
+
.mainRealm()
|
|
1271
|
+
.evaluate(fn as (...a: unknown[]) => unknown, ...args),
|
|
1169
1272
|
),
|
|
1170
1273
|
) as never,
|
|
1171
1274
|
scrollIntoView: selector =>
|
|
@@ -1553,6 +1656,22 @@ export class WorkerCore {
|
|
|
1553
1656
|
for (const handle of handles) void handle.dispose().catch(() => undefined);
|
|
1554
1657
|
}
|
|
1555
1658
|
|
|
1659
|
+
/** Best-effort `Page.stopLoading` so an abandoned navigation cannot stall later ops. */
|
|
1660
|
+
async #stopLoading(): Promise<void> {
|
|
1661
|
+
try {
|
|
1662
|
+
const session = await this.#requirePage().createCDPSession();
|
|
1663
|
+
try {
|
|
1664
|
+
await session.send("Page.stopLoading");
|
|
1665
|
+
} finally {
|
|
1666
|
+
await session.detach().catch(() => undefined);
|
|
1667
|
+
}
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
this.#log("debug", "Page.stopLoading failed", {
|
|
1670
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1556
1675
|
async #close(): Promise<void> {
|
|
1557
1676
|
this.#unsub();
|
|
1558
1677
|
this.#clearElementCache();
|
package/src/tools/eval-render.ts
CHANGED
|
@@ -414,37 +414,39 @@ function formatStatusEventExpanded(event: EvalStatusEvent, theme: Theme): string
|
|
|
414
414
|
return lines;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* Render status events as tree lines. Shows a tail window (newest events are
|
|
419
|
+
* the live edge for `log()` progress loops) behind an "… N earlier" marker,
|
|
420
|
+
* matching the code/output tail-window convention. Collapsed keeps a small
|
|
421
|
+
* fixed window; expanded widens to the viewport-sized preview window.
|
|
422
|
+
*/
|
|
418
423
|
function renderStatusEvents(events: EvalStatusEvent[], theme: Theme, expanded: boolean): string[] {
|
|
419
424
|
if (events.length === 0) return [];
|
|
420
425
|
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
const
|
|
426
|
+
const max = expanded ? Math.max(10, previewWindowRows()) : 3;
|
|
427
|
+
const hidden = Math.max(0, events.length - max);
|
|
428
|
+
const visible = hidden > 0 ? events.slice(hidden) : events;
|
|
424
429
|
|
|
425
430
|
const lines: string[] = [];
|
|
426
|
-
|
|
427
|
-
|
|
431
|
+
if (hidden > 0) {
|
|
432
|
+
lines.push(`${theme.fg("dim", theme.tree.branch)} ${theme.fg("dim", `… ${hidden} earlier`)}`);
|
|
433
|
+
}
|
|
434
|
+
for (let i = 0; i < visible.length; i++) {
|
|
435
|
+
const isLast = i === visible.length - 1;
|
|
428
436
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
429
437
|
|
|
430
438
|
if (expanded) {
|
|
431
|
-
const eventLines = formatStatusEventExpanded(
|
|
439
|
+
const eventLines = formatStatusEventExpanded(visible[i], theme);
|
|
432
440
|
lines.push(`${theme.fg("dim", branch)} ${eventLines[0]}`);
|
|
433
441
|
const continueBranch = isLast ? " " : `${theme.tree.vertical} `;
|
|
434
442
|
for (let j = 1; j < eventLines.length; j++) {
|
|
435
443
|
lines.push(`${theme.fg("dim", continueBranch)}${eventLines[j]}`);
|
|
436
444
|
}
|
|
437
445
|
} else {
|
|
438
|
-
lines.push(`${theme.fg("dim", branch)} ${formatStatusEvent(
|
|
446
|
+
lines.push(`${theme.fg("dim", branch)} ${formatStatusEvent(visible[i], theme)}`);
|
|
439
447
|
}
|
|
440
448
|
}
|
|
441
449
|
|
|
442
|
-
if (!expanded && events.length > maxCollapsed) {
|
|
443
|
-
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("dim", `… ${events.length - maxCollapsed} more`)}`);
|
|
444
|
-
} else if (expanded && events.length > maxExpanded) {
|
|
445
|
-
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("dim", `… ${events.length - maxExpanded} more`)}`);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
450
|
return lines;
|
|
449
451
|
}
|
|
450
452
|
|
package/src/tools/index.ts
CHANGED
|
@@ -50,6 +50,7 @@ import { GrepTool } from "./grep";
|
|
|
50
50
|
import { InspectImageTool } from "./inspect-image";
|
|
51
51
|
import { IrcTool, isIrcEnabled } from "./irc";
|
|
52
52
|
import { JobTool } from "./job";
|
|
53
|
+
import { LaunchTool } from "./launch";
|
|
53
54
|
import { LearnTool } from "./learn";
|
|
54
55
|
import { ManageSkillTool } from "./manage-skill";
|
|
55
56
|
import { MemoryEditTool } from "./memory-edit";
|
|
@@ -89,6 +90,7 @@ export * from "./image-gen";
|
|
|
89
90
|
export * from "./inspect-image";
|
|
90
91
|
export * from "./irc";
|
|
91
92
|
export * from "./job";
|
|
93
|
+
export * from "./launch";
|
|
92
94
|
export * from "./learn";
|
|
93
95
|
export * from "./manage-skill";
|
|
94
96
|
export * from "./memory-edit";
|
|
@@ -389,6 +391,7 @@ export type BuiltinToolLoadMode = "essential" | "discoverable";
|
|
|
389
391
|
export const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[] = [
|
|
390
392
|
"read",
|
|
391
393
|
"bash",
|
|
394
|
+
"launch",
|
|
392
395
|
"edit",
|
|
393
396
|
"write",
|
|
394
397
|
"glob",
|
|
@@ -448,6 +451,7 @@ export function filterInitialToolsForDiscoveryAll(
|
|
|
448
451
|
export const BUILTIN_TOOLS: Record<BuiltinToolName, ToolFactory> = {
|
|
449
452
|
read: s => new ReadTool(s),
|
|
450
453
|
bash: s => new BashTool(s),
|
|
454
|
+
launch: s => new LaunchTool(s),
|
|
451
455
|
edit: s => new EditTool(s),
|
|
452
456
|
ast_grep: s => new AstGrepTool(s),
|
|
453
457
|
ast_edit: s => new AstEditTool(s),
|
|
@@ -605,6 +609,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
605
609
|
if (name === "goal") return goalEnabled && goalModeActive;
|
|
606
610
|
if (name === "lsp") return enableLsp && session.settings.get("lsp.enabled");
|
|
607
611
|
if (name === "bash") return session.settings.get("bash.enabled");
|
|
612
|
+
if (name === "launch") return session.settings.get("launch.enabled");
|
|
608
613
|
if (name === "eval") return allowEval;
|
|
609
614
|
if (name === "debug") return session.settings.get("debug.enabled");
|
|
610
615
|
if (name === "todo") return !includeYield && session.settings.get("todo.enabled");
|
|
@@ -157,8 +157,8 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
157
157
|
|
|
158
158
|
const activeModelPattern = this.session.getActiveModelString?.() ?? this.session.getModelString?.();
|
|
159
159
|
const model =
|
|
160
|
-
resolvePattern("
|
|
161
|
-
resolvePattern("
|
|
160
|
+
resolvePattern("@vision") ??
|
|
161
|
+
resolvePattern("@default") ??
|
|
162
162
|
resolvePattern(activeModelPattern) ??
|
|
163
163
|
availableModels[0];
|
|
164
164
|
if (!model) {
|