@oh-my-pi/pi-coding-agent 16.1.9 → 16.1.10
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 +15 -0
- package/dist/cli.js +3076 -3050
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/edit/streaming.d.ts +5 -5
- package/dist/types/modes/controllers/selector-controller.d.ts +0 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/tools/browser/aria/aria-snapshot.d.ts +39 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -0
- package/dist/types/tools/browser.d.ts +1 -0
- package/dist/types/tools/find.d.ts +1 -2
- package/dist/types/tools/write.d.ts +1 -2
- package/package.json +12 -12
- package/scripts/generate-aria-snapshot.ts +134 -0
- package/src/config/settings-schema.ts +12 -0
- package/src/edit/streaming.ts +5 -5
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/selector-controller.ts +3 -5
- package/src/prompts/system/project-prompt.md +2 -0
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/browser.md +2 -0
- package/src/sdk.ts +8 -1
- package/src/system-prompt.ts +15 -3
- package/src/tools/bash.ts +3 -3
- package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
- package/src/tools/browser/aria/aria-snapshot.ts +103 -0
- package/src/tools/browser/cmux/cmux-tab.ts +36 -1
- package/src/tools/browser/tab-worker.ts +89 -17
- package/src/tools/browser.ts +5 -0
- package/src/tools/find.ts +1 -2
- package/src/tools/index.ts +1 -1
- package/src/tools/write.ts +1 -2
- package/src/utils/git.ts +3 -2
|
@@ -22,6 +22,12 @@ import { resizeImage } from "../../utils/image-resize";
|
|
|
22
22
|
import { resolveToCwd } from "../path-utils";
|
|
23
23
|
import { formatScreenshot } from "../render-utils";
|
|
24
24
|
import { ToolAbortError, ToolError, throwIfAborted } from "../tool-errors";
|
|
25
|
+
import {
|
|
26
|
+
type AriaSnapshotOptions,
|
|
27
|
+
captureAriaSnapshot,
|
|
28
|
+
parseAriaRefSelector,
|
|
29
|
+
resolveAriaRefHandle,
|
|
30
|
+
} from "./aria/aria-snapshot";
|
|
25
31
|
import {
|
|
26
32
|
applyStealthPatches,
|
|
27
33
|
applyViewport,
|
|
@@ -106,6 +112,7 @@ interface TabApi {
|
|
|
106
112
|
opts?: { waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2" },
|
|
107
113
|
): Promise<void>;
|
|
108
114
|
observe(opts?: { includeAll?: boolean; viewportOnly?: boolean }): Promise<Observation>;
|
|
115
|
+
ariaSnapshot(selector?: string, opts?: AriaSnapshotOptions): Promise<string>;
|
|
109
116
|
screenshot(opts?: ScreenshotOptions): Promise<ScreenshotResult>;
|
|
110
117
|
extract(format?: ReadableFormat): Promise<string>;
|
|
111
118
|
click(selector: string): Promise<void>;
|
|
@@ -128,6 +135,7 @@ interface TabApi {
|
|
|
128
135
|
opts?: { timeout?: number },
|
|
129
136
|
): Promise<HTTPResponse>;
|
|
130
137
|
id(n: number): Promise<ElementHandle>;
|
|
138
|
+
ref(id: string): Promise<ElementHandle>;
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
function normalizeSelector(selector: string): string {
|
|
@@ -792,6 +800,28 @@ export class WorkerCore {
|
|
|
792
800
|
);
|
|
793
801
|
}),
|
|
794
802
|
observe: opts => op("tab.observe()", quickOpMs, sig => this.#collectObservation({ ...opts, signal: sig })),
|
|
803
|
+
ariaSnapshot: (selector, opts) =>
|
|
804
|
+
op(
|
|
805
|
+
selector ? `tab.ariaSnapshot(${JSON.stringify(selector)})` : "tab.ariaSnapshot()",
|
|
806
|
+
quickOpMs,
|
|
807
|
+
async sig => {
|
|
808
|
+
let root: ElementHandle | null = null;
|
|
809
|
+
if (selector) {
|
|
810
|
+
root = (await untilAborted(sig, () =>
|
|
811
|
+
page.$(normalizeSelector(selector)),
|
|
812
|
+
)) as ElementHandle | null;
|
|
813
|
+
if (!root)
|
|
814
|
+
throw new ToolError(
|
|
815
|
+
`tab.ariaSnapshot: selector ${JSON.stringify(selector)} matched no element`,
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
return await untilAborted(sig, () => captureAriaSnapshot(page, root, opts));
|
|
820
|
+
} finally {
|
|
821
|
+
await root?.dispose().catch(() => undefined);
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
),
|
|
795
825
|
screenshot: opts =>
|
|
796
826
|
op(describeScreenshot(opts), quickOpMs, sig =>
|
|
797
827
|
this.#captureScreenshot(session, displays, screenshots, sig, opts),
|
|
@@ -815,25 +845,50 @@ export class WorkerCore {
|
|
|
815
845
|
}),
|
|
816
846
|
click: selector =>
|
|
817
847
|
op(`tab.click(${JSON.stringify(selector)})`, INF, async sig => {
|
|
848
|
+
if (parseAriaRefSelector(selector) !== null) {
|
|
849
|
+
const handle = await this.#resolveAriaRef(selector);
|
|
850
|
+
try {
|
|
851
|
+
await untilAborted(sig, () => handle.click());
|
|
852
|
+
} finally {
|
|
853
|
+
await handle.dispose().catch(() => undefined);
|
|
854
|
+
}
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
818
857
|
const resolved = normalizeSelector(selector);
|
|
819
858
|
if (resolved.startsWith("text/")) await clickQueryHandlerText(page, resolved, timeoutMs, sig);
|
|
820
859
|
else await untilAborted(sig, () => page.locator(resolved).setTimeout(timeoutMs).click());
|
|
821
860
|
}),
|
|
822
861
|
type: (selector, text) =>
|
|
823
862
|
op(`tab.type(${JSON.stringify(selector)})`, INF, async sig => {
|
|
824
|
-
const handle =
|
|
825
|
-
page.locator(normalizeSelector(selector)).setTimeout(timeoutMs).waitHandle(),
|
|
826
|
-
)) as ElementHandle;
|
|
863
|
+
const handle = await this.#resolveActionHandle(selector, timeoutMs, sig);
|
|
827
864
|
try {
|
|
828
865
|
await untilAborted(sig, () => handle.type(text, { delay: 0 }));
|
|
829
866
|
} finally {
|
|
830
|
-
await handle.dispose();
|
|
867
|
+
await handle.dispose().catch(() => undefined);
|
|
831
868
|
}
|
|
832
869
|
}),
|
|
833
870
|
fill: (selector, value) =>
|
|
834
|
-
op(`tab.fill(${JSON.stringify(selector)})`, INF, sig =>
|
|
835
|
-
|
|
836
|
-
|
|
871
|
+
op(`tab.fill(${JSON.stringify(selector)})`, INF, async sig => {
|
|
872
|
+
if (parseAriaRefSelector(selector) !== null) {
|
|
873
|
+
const handle = await this.#resolveAriaRef(selector);
|
|
874
|
+
try {
|
|
875
|
+
await untilAborted(sig, () =>
|
|
876
|
+
handle.evaluate(el => {
|
|
877
|
+
const node = el as unknown as { value?: string; focus?: () => void };
|
|
878
|
+
node.focus?.();
|
|
879
|
+
if ("value" in node) node.value = "";
|
|
880
|
+
}),
|
|
881
|
+
);
|
|
882
|
+
await untilAborted(sig, () => handle.type(value, { delay: 0 }));
|
|
883
|
+
} finally {
|
|
884
|
+
await handle.dispose().catch(() => undefined);
|
|
885
|
+
}
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
await untilAborted(sig, () =>
|
|
889
|
+
page.locator(normalizeSelector(selector)).setTimeout(timeoutMs).fill(value),
|
|
890
|
+
);
|
|
891
|
+
}),
|
|
837
892
|
press: (key, opts) =>
|
|
838
893
|
op(`tab.press(${JSON.stringify(key)})`, INF, async sig => {
|
|
839
894
|
const selector = opts?.selector;
|
|
@@ -844,13 +899,8 @@ export class WorkerCore {
|
|
|
844
899
|
op("tab.scroll()", INF, sig => untilAborted(sig, () => page.mouse.wheel({ deltaX, deltaY }))),
|
|
845
900
|
drag: (from, to) => op("tab.drag()", INF, sig => this.#drag(from, to, sig)),
|
|
846
901
|
waitFor: selector =>
|
|
847
|
-
op(
|
|
848
|
-
|
|
849
|
-
INF,
|
|
850
|
-
async sig =>
|
|
851
|
-
(await untilAborted(sig, () =>
|
|
852
|
-
page.locator(normalizeSelector(selector)).setTimeout(timeoutMs).waitHandle(),
|
|
853
|
-
)) as ElementHandle,
|
|
902
|
+
op(`tab.waitFor(${JSON.stringify(selector)})`, INF, sig =>
|
|
903
|
+
this.#resolveActionHandle(selector, timeoutMs, sig),
|
|
854
904
|
),
|
|
855
905
|
evaluate: (fn, ...args) =>
|
|
856
906
|
op("tab.evaluate()", INF, sig =>
|
|
@@ -862,9 +912,7 @@ export class WorkerCore {
|
|
|
862
912
|
) as never,
|
|
863
913
|
scrollIntoView: selector =>
|
|
864
914
|
op(`tab.scrollIntoView(${JSON.stringify(selector)})`, INF, async sig => {
|
|
865
|
-
const handle =
|
|
866
|
-
page.locator(normalizeSelector(selector)).setTimeout(timeoutMs).waitHandle(),
|
|
867
|
-
)) as ElementHandle;
|
|
915
|
+
const handle = await this.#resolveActionHandle(selector, timeoutMs, sig);
|
|
868
916
|
try {
|
|
869
917
|
await untilAborted(sig, () =>
|
|
870
918
|
handle.evaluate(el => {
|
|
@@ -889,6 +937,7 @@ export class WorkerCore {
|
|
|
889
937
|
waitForResponse: (pattern, opts) =>
|
|
890
938
|
op("tab.waitForResponse()", INF, sig => this.#waitForResponse(pattern, opts?.timeout ?? timeoutMs, sig)),
|
|
891
939
|
id: id => this.#resolveCachedHandle(id),
|
|
940
|
+
ref: id => this.#resolveAriaRef(id),
|
|
892
941
|
};
|
|
893
942
|
}
|
|
894
943
|
|
|
@@ -1188,6 +1237,29 @@ export class WorkerCore {
|
|
|
1188
1237
|
}
|
|
1189
1238
|
return handle;
|
|
1190
1239
|
}
|
|
1240
|
+
|
|
1241
|
+
async #resolveAriaRef(id: string): Promise<ElementHandle> {
|
|
1242
|
+
const ref = parseAriaRefSelector(id) ?? id.trim();
|
|
1243
|
+
const handle = await resolveAriaRefHandle(this.#requirePage(), ref);
|
|
1244
|
+
if (!handle) {
|
|
1245
|
+
throw new ToolError(
|
|
1246
|
+
`Unknown ARIA ref ${JSON.stringify(ref)}. Run tab.ariaSnapshot() to refresh refs (they renumber each snapshot).`,
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
return handle;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Resolve a selector to an ElementHandle for handle-based actions. An
|
|
1254
|
+
* `aria-ref=eN` selector resolves against the latest ariaSnapshot's refs
|
|
1255
|
+
* (main world); anything else goes through the normal locator wait.
|
|
1256
|
+
*/
|
|
1257
|
+
async #resolveActionHandle(selector: string, timeoutMs: number, sig: AbortSignal): Promise<ElementHandle> {
|
|
1258
|
+
if (parseAriaRefSelector(selector) !== null) return this.#resolveAriaRef(selector);
|
|
1259
|
+
return (await untilAborted(sig, () =>
|
|
1260
|
+
this.#requirePage().locator(normalizeSelector(selector)).setTimeout(timeoutMs).waitHandle(),
|
|
1261
|
+
)) as ElementHandle;
|
|
1262
|
+
}
|
|
1191
1263
|
#clearElementCache(): void {
|
|
1192
1264
|
if (this.#elementCache.size === 0) {
|
|
1193
1265
|
this.#elementCounter = 0;
|
package/src/tools/browser.ts
CHANGED
|
@@ -16,6 +16,11 @@ import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
|
16
16
|
import { toolResult } from "./tool-result";
|
|
17
17
|
import { clampTimeout } from "./tool-timeouts";
|
|
18
18
|
|
|
19
|
+
export {
|
|
20
|
+
type AriaSnapshotOptions,
|
|
21
|
+
buildAriaSnapshotScript,
|
|
22
|
+
parseAriaRefSelector,
|
|
23
|
+
} from "./browser/aria/aria-snapshot";
|
|
19
24
|
export { cmuxSnapshotToObservation, mapWaitUntil, resolveCmuxKind, serializeEval } from "./browser/cmux/rpc";
|
|
20
25
|
export { CmuxSocketClient } from "./browser/cmux/socket-client";
|
|
21
26
|
export { extractReadableFromHtml, type ReadableFormat, type ReadableResult } from "./browser/readable";
|
package/src/tools/find.ts
CHANGED
|
@@ -101,8 +101,7 @@ interface FindTarget {
|
|
|
101
101
|
export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
102
102
|
readonly name = "find";
|
|
103
103
|
readonly approval = "read" as const;
|
|
104
|
-
readonly
|
|
105
|
-
readonly loadMode = "discoverable";
|
|
104
|
+
readonly loadMode = "essential";
|
|
106
105
|
readonly label = "Find";
|
|
107
106
|
readonly description: string;
|
|
108
107
|
readonly parameters = findSchema;
|
package/src/tools/index.ts
CHANGED
|
@@ -375,7 +375,7 @@ export type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool |
|
|
|
375
375
|
export type BuiltinToolLoadMode = "essential" | "discoverable";
|
|
376
376
|
|
|
377
377
|
/** Default essential tool names when tools.essentialOverride is empty. */
|
|
378
|
-
export const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[] = ["read", "bash", "edit"] as const;
|
|
378
|
+
export const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[] = ["read", "bash", "edit", "write", "find"] as const;
|
|
379
379
|
|
|
380
380
|
/**
|
|
381
381
|
* Resolve the active essential built-in tool names from settings.
|
package/src/tools/write.ts
CHANGED
|
@@ -293,8 +293,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
293
293
|
readonly parameters = writeSchema;
|
|
294
294
|
readonly strict = true;
|
|
295
295
|
readonly concurrency = "exclusive";
|
|
296
|
-
readonly loadMode = "
|
|
297
|
-
readonly summary = "Write content to a file (creates or overwrites)";
|
|
296
|
+
readonly loadMode = "essential";
|
|
298
297
|
|
|
299
298
|
/** Stream matchers should see the real file content, not its JSON-escaped argument encoding. */
|
|
300
299
|
matcherDigest(args: unknown): string | undefined {
|
package/src/utils/git.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { $which, hasFsCode, isEnoent, Snowflake } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { $which, hasFsCode, isEisdir, isEnoent, isEnotdir, Snowflake } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import {
|
|
6
6
|
parseDiffHunks as parseCommitDiffHunks,
|
|
7
7
|
parseFileDiffs,
|
|
@@ -376,7 +376,8 @@ async function writeTempPatch(content: string): Promise<string> {
|
|
|
376
376
|
type EntryType = "directory" | "file";
|
|
377
377
|
|
|
378
378
|
function shouldRetry(err: unknown, n: number) {
|
|
379
|
-
if (isEnoent(err) || hasFsCode(err, "ENFILE") || hasFsCode(err, "EMFILE"))
|
|
379
|
+
if (isEnoent(err) || isEisdir(err) || isEnotdir(err) || hasFsCode(err, "ENFILE") || hasFsCode(err, "EMFILE"))
|
|
380
|
+
return false;
|
|
380
381
|
if (hasFsCode(err, "EINTR")) return n < EINTR_MAX_RETRIES;
|
|
381
382
|
if (n > EINTR_MAX_RETRIES) throw err;
|
|
382
383
|
throw err;
|