@oh-my-pi/pi-coding-agent 16.1.8 → 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 +40 -0
- package/dist/cli.js +4775 -3942
- package/dist/types/cli/flag-tables.d.ts +17 -0
- package/dist/types/cli-commands.d.ts +4 -2
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/edit/streaming.d.ts +5 -5
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +0 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +13 -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/launch.d.ts +5 -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/cli/args.ts +0 -1
- package/src/cli/flag-tables.ts +42 -0
- package/src/cli/profile-bootstrap.ts +1 -11
- package/src/cli-commands.ts +48 -3
- package/src/config/model-registry.ts +6 -7
- 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/lsp/client.ts +39 -25
- package/src/modes/controllers/command-controller.ts +18 -3
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/mcp-command-controller.ts +40 -12
- package/src/modes/controllers/selector-controller.ts +3 -5
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/types.ts +1 -1
- 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/session/agent-session.ts +36 -1
- package/src/slash-commands/builtin-registry.ts +24 -66
- 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/launch.ts +107 -31
- 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/puppeteer/00_stealth_tampering.txt +16 -35
- package/src/tools/puppeteer/01_stealth_activity.txt +79 -19
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -11
- package/src/tools/puppeteer/03_stealth_botd.txt +10 -14
- package/src/tools/puppeteer/04_stealth_iframe.txt +156 -63
- package/src/tools/puppeteer/05_stealth_webgl.txt +222 -64
- package/src/tools/puppeteer/06_stealth_screen.txt +255 -67
- package/src/tools/puppeteer/07_stealth_fonts.txt +17 -15
- package/src/tools/puppeteer/08_stealth_audio.txt +32 -20
- package/src/tools/puppeteer/09_stealth_locale.txt +45 -40
- package/src/tools/puppeteer/10_stealth_plugins.txt +12 -8
- package/src/tools/puppeteer/11_stealth_hardware.txt +57 -6
- package/src/tools/puppeteer/12_stealth_codecs.txt +20 -18
- package/src/tools/puppeteer/13_stealth_worker.txt +206 -45
- package/src/tools/write.ts +1 -2
- package/src/utils/git.ts +3 -2
|
@@ -124,3 +124,20 @@ export declare const PROFILE_BOOTSTRAP_BOUNDARY_ARG = "--omp-profile-boundary";
|
|
|
124
124
|
* fires for `--`-prefixed tokens.
|
|
125
125
|
*/
|
|
126
126
|
export declare const VALUELESS_FLAGS: ReadonlySet<string>;
|
|
127
|
+
/**
|
|
128
|
+
* Whether a bare long option (`--xxx`, no `=`) is unclassified — not a known
|
|
129
|
+
* string-, optional-, or value-less flag. The bootstrap and subcommand
|
|
130
|
+
* resolver treat these as possible extension string flags that may consume a
|
|
131
|
+
* value-like successor (the extension flag table is not yet loaded). Shared so
|
|
132
|
+
* both call sites classify identically.
|
|
133
|
+
*/
|
|
134
|
+
export declare function isUnknownLongValueCandidate(arg: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Whether a leading option `flag` consumes the following argv token `next` as
|
|
137
|
+
* its value, applying the same contract as `extractProfileFlags` / `parseArgs`.
|
|
138
|
+
* Single source of truth so subcommand detection ({@link resolveCliArgv}) skips
|
|
139
|
+
* a flag's value instead of mistaking it for the subcommand — `omp --model acp`
|
|
140
|
+
* means model `acp`, not the `acp` subcommand, exactly as the launch parser
|
|
141
|
+
* reads it.
|
|
142
|
+
*/
|
|
143
|
+
export declare function flagConsumesValue(flag: string, next: string | undefined): boolean;
|
|
@@ -25,7 +25,9 @@ export type ResolvedCliArgv = {
|
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
27
|
* Decide what the CLI runner should do with raw argv: reject bare reserved
|
|
28
|
-
* management words, pass help/version through untouched,
|
|
29
|
-
*
|
|
28
|
+
* management words, pass help/version through untouched, route a recognized
|
|
29
|
+
* subcommand (even behind leading global flags like `--approval-mode=yolo`) to
|
|
30
|
+
* that command with the flags preserved, and forward everything else to
|
|
31
|
+
* `launch` (#2970).
|
|
30
32
|
*/
|
|
31
33
|
export declare function resolveCliArgv(argv: string[]): ResolvedCliArgv;
|
|
@@ -912,6 +912,16 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
912
912
|
readonly description: "Surface the active model identifier in the system prompt so the agent knows which model it is";
|
|
913
913
|
};
|
|
914
914
|
};
|
|
915
|
+
readonly includeWorkspaceTree: {
|
|
916
|
+
readonly type: "boolean";
|
|
917
|
+
readonly default: false;
|
|
918
|
+
readonly ui: {
|
|
919
|
+
readonly tab: "model";
|
|
920
|
+
readonly group: "Prompt";
|
|
921
|
+
readonly label: "Include Workspace Tree";
|
|
922
|
+
readonly description: "Render the workspace directory tree in the system prompt. WARNING: This can bust prompt caching across sessions when files are modified.";
|
|
923
|
+
};
|
|
924
|
+
};
|
|
915
925
|
readonly personality: {
|
|
916
926
|
readonly type: "enum";
|
|
917
927
|
readonly values: readonly ["default", "friendly", "pragmatic", "none"];
|
|
@@ -63,12 +63,12 @@ export interface EditStreamingStrategy<Args = unknown> {
|
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Given an edits array parsed from partial JSON, drop the last entry when the
|
|
66
|
-
* corresponding object in `partialJson`
|
|
66
|
+
* corresponding object in `partialJson` has not yet closed with `}`.
|
|
67
67
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
68
|
+
* The streaming parser materializes a trailing edit object from the fields seen
|
|
69
|
+
* so far before its closing `}` arrives, so an unfinished last entry can render
|
|
70
|
+
* as a (partial) edit mid-stream. Dropping it until the object closes keeps the
|
|
71
|
+
* preview from showing an incomplete edit.
|
|
72
72
|
*/
|
|
73
73
|
export declare function dropIncompleteLastEdit<T>(edits: readonly T[], partialJson: string | undefined, listKey: string): T[];
|
|
74
74
|
export declare const EDIT_MODE_STRATEGIES: Record<EditMode, EditStreamingStrategy<unknown>>;
|
|
@@ -11,7 +11,7 @@ export declare class CommandController {
|
|
|
11
11
|
constructor(ctx: InteractiveModeContext);
|
|
12
12
|
openInBrowser(urlOrPath: string): void;
|
|
13
13
|
handleExportCommand(text: string): Promise<void>;
|
|
14
|
-
handleDumpCommand(): void
|
|
14
|
+
handleDumpCommand(): Promise<void>;
|
|
15
15
|
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
16
16
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
17
17
|
handleShareCommand(): Promise<void>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
3
3
|
import type { SessionObserverRegistry } from "../session-observer-registry";
|
|
4
|
-
export declare function formatTemporaryModelStatus(modelLabel: string, roleSelectorHint?: string): string;
|
|
5
4
|
export declare class SelectorController {
|
|
6
5
|
#private;
|
|
7
6
|
private ctx;
|
|
@@ -271,7 +271,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
271
271
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
272
272
|
extractAssistantText(message: AssistantMessage): string;
|
|
273
273
|
handleExportCommand(text: string): Promise<void>;
|
|
274
|
-
handleDumpCommand(): void
|
|
274
|
+
handleDumpCommand(): Promise<void>;
|
|
275
275
|
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
276
276
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
277
277
|
handleShareCommand(): Promise<void>;
|
|
@@ -270,7 +270,7 @@ export interface InteractiveModeContext {
|
|
|
270
270
|
handleHotkeysCommand(): void;
|
|
271
271
|
handleToolsCommand(): void;
|
|
272
272
|
handleContextCommand(): void;
|
|
273
|
-
handleDumpCommand(): void
|
|
273
|
+
handleDumpCommand(): Promise<void>;
|
|
274
274
|
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
275
275
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
276
276
|
handleClearCommand(): Promise<void>;
|
package/dist/types/sdk.d.ts
CHANGED
|
@@ -1203,6 +1203,19 @@ export declare class AgentSession {
|
|
|
1203
1203
|
* `### Tool Call`/`### Tool Result`).
|
|
1204
1204
|
*/
|
|
1205
1205
|
formatSessionAsText(): string;
|
|
1206
|
+
/**
|
|
1207
|
+
* Dump the current session's LLM-facing request context as JSON to a
|
|
1208
|
+
* auto-named file in `os.tmpdir()`. This is the synchronous
|
|
1209
|
+
* `convertToLlm`-boundary snapshot — system prompt, tools (wire schemas),
|
|
1210
|
+
* thinking/service tier, and converted messages — with no network round-trip
|
|
1211
|
+
* and no arming flag, so advisor/side requests cannot intercept it.
|
|
1212
|
+
*
|
|
1213
|
+
* The file persists on disk and may contain the same raw context/secrets
|
|
1214
|
+
* as `/dump`; treat the path accordingly.
|
|
1215
|
+
*
|
|
1216
|
+
* @returns the written file path, or `undefined` when there are no messages.
|
|
1217
|
+
*/
|
|
1218
|
+
dumpLlmRequestToTmpDir(): Promise<string | undefined>;
|
|
1206
1219
|
/**
|
|
1207
1220
|
* Enable or disable the advisor for this session. The setting is overridden for the session,
|
|
1208
1221
|
* and the runtime is started or stopped to match.
|
|
@@ -110,6 +110,8 @@ export interface BuildSystemPromptOptions {
|
|
|
110
110
|
model?: string;
|
|
111
111
|
/** Personality preset rendered into the default system prompt. "none" omits the block. Default: "default" */
|
|
112
112
|
personality?: Personality;
|
|
113
|
+
/** Whether to include the workspace directory tree in the system prompt. Default: false */
|
|
114
|
+
includeWorkspaceTree?: boolean;
|
|
113
115
|
}
|
|
114
116
|
/** Result of building provider-facing system prompt messages. */
|
|
115
117
|
export interface BuildSystemPromptResult {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ElementHandle, Page } from "puppeteer-core";
|
|
2
|
+
export interface AriaSnapshotOptions {
|
|
3
|
+
/** Maximum tree depth to render. */
|
|
4
|
+
depth?: number;
|
|
5
|
+
/** Append `[box=x,y,w,h]` bounding boxes to each node. */
|
|
6
|
+
boxes?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Capture a Playwright-format ARIA snapshot of `root` (or the whole document when
|
|
10
|
+
* null). Always runs in `ai` mode so every node carries a `[ref=eN]` id; resolve
|
|
11
|
+
* those to elements with {@link resolveAriaRefHandle}. Ids are renumbered from e1
|
|
12
|
+
* on each call and remain valid until the next snapshot.
|
|
13
|
+
*/
|
|
14
|
+
export declare function captureAriaSnapshot(page: Page, root: ElementHandle | null, options?: AriaSnapshotOptions): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a `[ref=eN]` id from the latest snapshot to a live `ElementHandle`, or
|
|
17
|
+
* null when the ref no longer matches any element. Runs in the main world so it
|
|
18
|
+
* sees the `_ariaRef` expandos the snapshot wrote.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveAriaRefHandle(page: Page, ref: string): Promise<ElementHandle | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Recognize the explicit `[ref=eN]` selector forms and return the bare ref id,
|
|
23
|
+
* else null. Accepts `aria-ref=e5` (Playwright-MCP style), `aria-ref/e5`, and
|
|
24
|
+
* `ariaref/e5` — lets `tab.click("aria-ref=e5")` etc. act on snapshot refs. A
|
|
25
|
+
* bare `e5` is intentionally NOT a ref selector: the cmux backend already uses
|
|
26
|
+
* bare `eN`/`@eN` for its own observe ids, so requiring the prefix keeps action
|
|
27
|
+
* selectors meaning the same thing on both backends. (`tab.ref("e5")` still
|
|
28
|
+
* accepts a bare id directly.)
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseAriaRefSelector(selector: string): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Build a self-contained expression script that runs the vendored bundle in the
|
|
33
|
+
* page and returns the ARIA snapshot YAML. Used by the cmux backend, whose
|
|
34
|
+
* `browser.eval` RPC takes a script string and returns the completion value (it
|
|
35
|
+
* has no ElementHandle to pass in). The script resolves `selector` via
|
|
36
|
+
* `document.querySelector` in-page (CSS selectors only) or falls back to the
|
|
37
|
+
* whole document. Like the puppeteer path it installs nothing on `window`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildAriaSnapshotScript(selector: string | undefined, options?: AriaSnapshotOptions): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JsRuntime } from "../../../eval/js/shared/runtime";
|
|
2
2
|
import type { ToolSession } from "../../../sdk";
|
|
3
|
+
import { type AriaSnapshotOptions } from "../aria/aria-snapshot";
|
|
3
4
|
import { type ReadableFormat } from "../readable";
|
|
4
5
|
import type { Observation, ReadyInfo, RunResultOk, ScreenshotResult, SessionSnapshot } from "../tab-protocol";
|
|
5
6
|
import type { CmuxSocketClient } from "./socket-client";
|
|
@@ -75,6 +76,8 @@ export declare class CmuxTab {
|
|
|
75
76
|
timeoutMs?: number;
|
|
76
77
|
}): Promise<void>;
|
|
77
78
|
observe(opts?: ObserveOptions): Promise<Observation>;
|
|
79
|
+
ariaSnapshot(selector?: string, opts?: AriaSnapshotOptions): Promise<string>;
|
|
80
|
+
ref(id: string): Promise<CmuxElementHandle>;
|
|
78
81
|
click(selector: string): Promise<void>;
|
|
79
82
|
dblclick(selector: string): Promise<void>;
|
|
80
83
|
hover(selector: string): Promise<void>;
|
|
@@ -37,9 +37,14 @@ export interface UserAgentOverride {
|
|
|
37
37
|
version: string;
|
|
38
38
|
}>;
|
|
39
39
|
fullVersion: string;
|
|
40
|
+
fullVersionList: Array<{
|
|
41
|
+
brand: string;
|
|
42
|
+
version: string;
|
|
43
|
+
}>;
|
|
40
44
|
platform: string;
|
|
41
45
|
platformVersion: string;
|
|
42
46
|
architecture: string;
|
|
47
|
+
bitness: string;
|
|
43
48
|
model: string;
|
|
44
49
|
mobile: boolean;
|
|
45
50
|
};
|
|
@@ -4,6 +4,7 @@ import type { ToolSession } from "../sdk";
|
|
|
4
4
|
import { type BrowserKindTag } from "./browser/registry";
|
|
5
5
|
import type { Observation, ScreenshotResult } from "./browser/tab-protocol";
|
|
6
6
|
import type { OutputMeta } from "./output-meta";
|
|
7
|
+
export { type AriaSnapshotOptions, buildAriaSnapshotScript, parseAriaRefSelector, } from "./browser/aria/aria-snapshot";
|
|
7
8
|
export { cmuxSnapshotToObservation, mapWaitUntil, resolveCmuxKind, serializeEval } from "./browser/cmux/rpc";
|
|
8
9
|
export { CmuxSocketClient } from "./browser/cmux/socket-client";
|
|
9
10
|
export { extractReadableFromHtml, type ReadableFormat, type ReadableResult } from "./browser/readable";
|
|
@@ -60,8 +60,7 @@ export declare class FindTool implements AgentTool<typeof findSchema, FindToolDe
|
|
|
60
60
|
private readonly session;
|
|
61
61
|
readonly name = "find";
|
|
62
62
|
readonly approval: "read";
|
|
63
|
-
readonly
|
|
64
|
-
readonly loadMode = "discoverable";
|
|
63
|
+
readonly loadMode = "essential";
|
|
65
64
|
readonly label = "Find";
|
|
66
65
|
readonly description: string;
|
|
67
66
|
readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
@@ -40,8 +40,7 @@ export declare class WriteTool implements AgentTool<typeof writeSchema, WriteToo
|
|
|
40
40
|
}, {}>;
|
|
41
41
|
readonly strict = true;
|
|
42
42
|
readonly concurrency = "exclusive";
|
|
43
|
-
readonly loadMode = "
|
|
44
|
-
readonly summary = "Write content to a file (creates or overwrites)";
|
|
43
|
+
readonly loadMode = "essential";
|
|
45
44
|
/** Stream matchers should see the real file content, not its JSON-escaped argument encoding. */
|
|
46
45
|
matcherDigest(args: unknown): string | undefined;
|
|
47
46
|
constructor(session: ToolSession);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.1.
|
|
4
|
+
"version": "16.1.10",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -48,17 +48,17 @@
|
|
|
48
48
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
49
49
|
"@babel/parser": "^7.29.7",
|
|
50
50
|
"@mozilla/readability": "^0.6.0",
|
|
51
|
-
"@oh-my-pi/hashline": "16.1.
|
|
52
|
-
"@oh-my-pi/omp-stats": "16.1.
|
|
53
|
-
"@oh-my-pi/pi-agent-core": "16.1.
|
|
54
|
-
"@oh-my-pi/pi-ai": "16.1.
|
|
55
|
-
"@oh-my-pi/pi-catalog": "16.1.
|
|
56
|
-
"@oh-my-pi/pi-mnemopi": "16.1.
|
|
57
|
-
"@oh-my-pi/pi-natives": "16.1.
|
|
58
|
-
"@oh-my-pi/pi-tui": "16.1.
|
|
59
|
-
"@oh-my-pi/pi-utils": "16.1.
|
|
60
|
-
"@oh-my-pi/pi-wire": "16.1.
|
|
61
|
-
"@oh-my-pi/snapcompact": "16.1.
|
|
51
|
+
"@oh-my-pi/hashline": "16.1.10",
|
|
52
|
+
"@oh-my-pi/omp-stats": "16.1.10",
|
|
53
|
+
"@oh-my-pi/pi-agent-core": "16.1.10",
|
|
54
|
+
"@oh-my-pi/pi-ai": "16.1.10",
|
|
55
|
+
"@oh-my-pi/pi-catalog": "16.1.10",
|
|
56
|
+
"@oh-my-pi/pi-mnemopi": "16.1.10",
|
|
57
|
+
"@oh-my-pi/pi-natives": "16.1.10",
|
|
58
|
+
"@oh-my-pi/pi-tui": "16.1.10",
|
|
59
|
+
"@oh-my-pi/pi-utils": "16.1.10",
|
|
60
|
+
"@oh-my-pi/pi-wire": "16.1.10",
|
|
61
|
+
"@oh-my-pi/snapcompact": "16.1.10",
|
|
62
62
|
"@opentelemetry/api": "^1.9.1",
|
|
63
63
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
64
64
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Regenerates the committed browser asset
|
|
4
|
+
*
|
|
5
|
+
* src/tools/browser/aria/aria-snapshot.bundle.txt ← bundled CJS module
|
|
6
|
+
*
|
|
7
|
+
* by fetching Playwright's injected ARIA-snapshot sources (pinned to
|
|
8
|
+
* PLAYWRIGHT_TAG), wrapping them with a small entry, and bundling — all in a
|
|
9
|
+
* throwaway temp dir. Only the bundle is committed; the upstream sources are NOT
|
|
10
|
+
* vendored into the repo (no shipping both source + generated copies). This is a
|
|
11
|
+
* dev-time, network-bound step, exactly like `generate-models`.
|
|
12
|
+
*
|
|
13
|
+
* The tab worker imports the `.txt` with `{ type: "text" }`, wraps it in a
|
|
14
|
+
* `new Function` worker-side, and runs it via puppeteer's CDP evaluate (it
|
|
15
|
+
* installs nothing on `window`). The committed output means binary and source
|
|
16
|
+
* installs need no network or build step at runtime.
|
|
17
|
+
*
|
|
18
|
+
* Usage: bun scripts/generate-aria-snapshot.ts
|
|
19
|
+
*/
|
|
20
|
+
import * as fs from "node:fs/promises";
|
|
21
|
+
import * as os from "node:os";
|
|
22
|
+
import * as path from "node:path";
|
|
23
|
+
|
|
24
|
+
const PLAYWRIGHT_TAG = "v1.61.0";
|
|
25
|
+
const RAW_BASE = `https://raw.githubusercontent.com/microsoft/playwright/${PLAYWRIGHT_TAG}/packages`;
|
|
26
|
+
|
|
27
|
+
const OUTPUT = path.join(import.meta.dir, "..", "src", "tools", "browser", "aria", "aria-snapshot.bundle.txt");
|
|
28
|
+
|
|
29
|
+
// Upstream source path -> temp path (relative to the temp root).
|
|
30
|
+
const VENDOR_FILES: Array<[string, string]> = [
|
|
31
|
+
["injected/src/ariaSnapshot.ts", "injected/ariaSnapshot.ts"],
|
|
32
|
+
["injected/src/roleUtils.ts", "injected/roleUtils.ts"],
|
|
33
|
+
["injected/src/domUtils.ts", "injected/domUtils.ts"],
|
|
34
|
+
["isomorphic/ariaSnapshot.ts", "isomorphic/ariaSnapshot.ts"],
|
|
35
|
+
["isomorphic/stringUtils.ts", "isomorphic/stringUtils.ts"],
|
|
36
|
+
["isomorphic/cssTokenizer.ts", "isomorphic/cssTokenizer.ts"],
|
|
37
|
+
["isomorphic/yaml.ts", "isomorphic/yaml.ts"],
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Entry wrapping the upstream modules. Always runs Playwright's `ai` mode so every
|
|
41
|
+
// node carries a `[ref=eN]` id; matched nodes get an `_ariaRef` expando. Existing
|
|
42
|
+
// expandos are cleared first so the fresh module's counter renumbers from e1
|
|
43
|
+
// deterministically (refs are valid until the next snapshot). Installs nothing on
|
|
44
|
+
// `window`.
|
|
45
|
+
const ENTRY_SOURCE = `
|
|
46
|
+
import { generateAriaTree, renderAriaTree } from "./injected/ariaSnapshot";
|
|
47
|
+
|
|
48
|
+
export interface AriaSnapshotRequest {
|
|
49
|
+
depth?: number;
|
|
50
|
+
boxes?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function walkElements(fn: (el: Element) => void): void {
|
|
54
|
+
const walk = (root: { querySelectorAll(s: string): ArrayLike<Element> }): void => {
|
|
55
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
56
|
+
fn(el);
|
|
57
|
+
const shadow = (el as Element & { shadowRoot?: { querySelectorAll(s: string): ArrayLike<Element> } | null }).shadowRoot;
|
|
58
|
+
if (shadow) walk(shadow);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
walk(document as unknown as { querySelectorAll(s: string): ArrayLike<Element> });
|
|
62
|
+
}
|
|
63
|
+
type RefElement = Element & { _ariaRef?: { role: string; name: string; ref: string } };
|
|
64
|
+
|
|
65
|
+
export function ariaSnapshot(root: Element | null, request: AriaSnapshotRequest = {}): string {
|
|
66
|
+
walkElements(el => {
|
|
67
|
+
if ((el as RefElement)._ariaRef) delete (el as RefElement)._ariaRef;
|
|
68
|
+
});
|
|
69
|
+
const target = root ?? document.body ?? document.documentElement;
|
|
70
|
+
const options = { mode: "ai", depth: request.depth, boxes: request.boxes } as const;
|
|
71
|
+
const tree = generateAriaTree(target, options);
|
|
72
|
+
return renderAriaTree(tree, options).text;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resolveAriaRef(ref: string): Element | null {
|
|
76
|
+
let found: Element | null = null;
|
|
77
|
+
walkElements(el => {
|
|
78
|
+
if (!found && (el as RefElement)._ariaRef?.ref === ref) found = el;
|
|
79
|
+
});
|
|
80
|
+
return found;
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
async function main(): Promise<void> {
|
|
85
|
+
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "omp-aria-"));
|
|
86
|
+
try {
|
|
87
|
+
// Fetch pinned upstream sources into the temp dir.
|
|
88
|
+
for (const [src, dst] of VENDOR_FILES) {
|
|
89
|
+
const url = `${RAW_BASE}/${src}`;
|
|
90
|
+
const res = await fetch(url);
|
|
91
|
+
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
|
|
92
|
+
await Bun.write(path.join(tmp, dst), await res.text());
|
|
93
|
+
}
|
|
94
|
+
const entry = path.join(tmp, "entry.ts");
|
|
95
|
+
await Bun.write(entry, ENTRY_SOURCE);
|
|
96
|
+
|
|
97
|
+
// The injected sources import isomorphic modules via the `@isomorphic/*`
|
|
98
|
+
// alias and the `yaml` package (type-only). Resolve the alias to the fetched
|
|
99
|
+
// copies and stub `yaml` (only referenced from erased `import type`).
|
|
100
|
+
const aliasPlugin: Bun.BunPlugin = {
|
|
101
|
+
name: "aria-vendor-alias",
|
|
102
|
+
setup(build) {
|
|
103
|
+
build.onResolve({ filter: /^@isomorphic\// }, args => ({
|
|
104
|
+
path: path.join(tmp, "isomorphic", `${args.path.slice("@isomorphic/".length)}.ts`),
|
|
105
|
+
}));
|
|
106
|
+
build.onResolve({ filter: /^yaml$/ }, () => ({ path: "yaml", namespace: "aria-yaml-stub" }));
|
|
107
|
+
build.onLoad({ filter: /.*/, namespace: "aria-yaml-stub" }, () => ({
|
|
108
|
+
contents: "export {};",
|
|
109
|
+
loader: "ts",
|
|
110
|
+
}));
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const result = await Bun.build({
|
|
115
|
+
entrypoints: [entry],
|
|
116
|
+
target: "browser",
|
|
117
|
+
format: "cjs",
|
|
118
|
+
minify: true,
|
|
119
|
+
plugins: [aliasPlugin],
|
|
120
|
+
});
|
|
121
|
+
if (!result.success) {
|
|
122
|
+
for (const log of result.logs) console.error(log);
|
|
123
|
+
throw new Error("aria snapshot bundle failed");
|
|
124
|
+
}
|
|
125
|
+
const code = await result.outputs[0].text();
|
|
126
|
+
const header = `// @generated by scripts/generate-aria-snapshot.ts from Playwright ${PLAYWRIGHT_TAG}\n// Bundled from Playwright's injected ARIA-snapshot sources (Apache-2.0, (c) Microsoft).\n// Do not edit by hand. Regenerate with: bun scripts/generate-aria-snapshot.ts\n`;
|
|
127
|
+
await Bun.write(OUTPUT, header + code);
|
|
128
|
+
console.log(`bundled ${path.relative(process.cwd(), OUTPUT)} (${code.length}b)`);
|
|
129
|
+
} finally {
|
|
130
|
+
await fs.rm(tmp, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await main();
|
package/src/cli/args.ts
CHANGED
|
@@ -298,7 +298,6 @@ export function getExtraHelpText(): string {
|
|
|
298
298
|
OPENCODE_API_KEY - OpenCode Zen/OpenCode Go models
|
|
299
299
|
CURSOR_ACCESS_TOKEN - Cursor AI models
|
|
300
300
|
AI_GATEWAY_API_KEY - Vercel AI Gateway
|
|
301
|
-
WAFER_PASS_API_KEY - Wafer Pass (flat-rate subscription; GLM-5.1, Qwen3.5)
|
|
302
301
|
WAFER_SERVERLESS_API_KEY - Wafer Serverless (pay-as-you-go)
|
|
303
302
|
|
|
304
303
|
${chalk.dim("# Cloud Providers")}
|
package/src/cli/flag-tables.ts
CHANGED
|
@@ -278,3 +278,45 @@ export const VALUELESS_FLAGS: ReadonlySet<string> = new Set([
|
|
|
278
278
|
"--auto-approve",
|
|
279
279
|
"--yolo",
|
|
280
280
|
]);
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Whether a bare long option (`--xxx`, no `=`) is unclassified — not a known
|
|
284
|
+
* string-, optional-, or value-less flag. The bootstrap and subcommand
|
|
285
|
+
* resolver treat these as possible extension string flags that may consume a
|
|
286
|
+
* value-like successor (the extension flag table is not yet loaded). Shared so
|
|
287
|
+
* both call sites classify identically.
|
|
288
|
+
*/
|
|
289
|
+
export function isUnknownLongValueCandidate(arg: string): boolean {
|
|
290
|
+
return (
|
|
291
|
+
arg.startsWith("--") &&
|
|
292
|
+
!arg.includes("=") &&
|
|
293
|
+
!STRING_VALUE_FLAGS.has(arg) &&
|
|
294
|
+
!OPTIONAL_VALUE_FLAGS.has(arg) &&
|
|
295
|
+
!VALUELESS_FLAGS.has(arg)
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Whether a leading option `flag` consumes the following argv token `next` as
|
|
301
|
+
* its value, applying the same contract as `extractProfileFlags` / `parseArgs`.
|
|
302
|
+
* Single source of truth so subcommand detection ({@link resolveCliArgv}) skips
|
|
303
|
+
* a flag's value instead of mistaking it for the subcommand — `omp --model acp`
|
|
304
|
+
* means model `acp`, not the `acp` subcommand, exactly as the launch parser
|
|
305
|
+
* reads it.
|
|
306
|
+
*/
|
|
307
|
+
export function flagConsumesValue(flag: string, next: string | undefined): boolean {
|
|
308
|
+
// `--flag=value` carries its own value inline.
|
|
309
|
+
if (flag.startsWith("--") && flag.includes("=")) return false;
|
|
310
|
+
if (next === undefined) return false;
|
|
311
|
+
// Known string flags consume any successor, even a flag-looking one
|
|
312
|
+
// (`--system-prompt --foo` ⇒ the system prompt is literally `--foo`).
|
|
313
|
+
if (STRING_VALUE_FLAGS.has(flag)) return true;
|
|
314
|
+
const valueLike = !next.startsWith("-");
|
|
315
|
+
if (EXTENSION_SHADOWABLE_STRING_FLAGS.has(flag)) return valueLike;
|
|
316
|
+
if (OPTIONAL_VALUE_FLAGS.has(flag)) {
|
|
317
|
+
const config = OPTIONAL_FLAGS[flag];
|
|
318
|
+
return valueLike && !(config.rejectEmpty === true && next.length === 0);
|
|
319
|
+
}
|
|
320
|
+
if (isUnknownLongValueCandidate(flag)) return valueLike;
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
@@ -36,27 +36,17 @@
|
|
|
36
36
|
import { isSubcommand } from "../cli-commands";
|
|
37
37
|
import {
|
|
38
38
|
EXTENSION_SHADOWABLE_STRING_FLAGS,
|
|
39
|
+
isUnknownLongValueCandidate,
|
|
39
40
|
OPTIONAL_FLAGS,
|
|
40
41
|
OPTIONAL_VALUE_FLAGS,
|
|
41
42
|
PROFILE_BOOTSTRAP_BOUNDARY_ARG,
|
|
42
43
|
STRING_VALUE_FLAGS,
|
|
43
|
-
VALUELESS_FLAGS,
|
|
44
44
|
} from "./flag-tables";
|
|
45
45
|
|
|
46
46
|
function isProfileBootstrapSubcommand(arg: string): boolean {
|
|
47
47
|
return arg === "launch" || arg === "acp";
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function isUnknownLongValueCandidate(arg: string): boolean {
|
|
51
|
-
return (
|
|
52
|
-
arg.startsWith("--") &&
|
|
53
|
-
!arg.includes("=") &&
|
|
54
|
-
!STRING_VALUE_FLAGS.has(arg) &&
|
|
55
|
-
!OPTIONAL_VALUE_FLAGS.has(arg) &&
|
|
56
|
-
!VALUELESS_FLAGS.has(arg)
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
50
|
function needsBoundaryAfterGlobalStrip(stripped: readonly string[]): boolean {
|
|
61
51
|
const previous = stripped[stripped.length - 1];
|
|
62
52
|
return (
|
package/src/cli-commands.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* regression that motivated the split.
|
|
10
10
|
*/
|
|
11
11
|
import type { CommandEntry } from "@oh-my-pi/pi-utils/cli";
|
|
12
|
+
import { flagConsumesValue } from "./cli/flag-tables";
|
|
12
13
|
|
|
13
14
|
export const commands: CommandEntry[] = [
|
|
14
15
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
@@ -44,11 +45,26 @@ export const commands: CommandEntry[] = [
|
|
|
44
45
|
{ name: "search", load: () => import("./commands/web-search").then(m => m.default), aliases: ["q"] },
|
|
45
46
|
];
|
|
46
47
|
|
|
48
|
+
// Documented-looking plugin-management verbs that are NOT registered top-level
|
|
49
|
+
// commands. Without a guard `resolveCliArgv` rewrites e.g. `omp list` to
|
|
50
|
+
// `omp launch list`, silently forwarding the bare verb to the model as a prompt
|
|
51
|
+
// instead of managing plugins (#2935; same class as the `install` leak fixed in
|
|
52
|
+
// #1496/#1498). A bare (single-arg) use gets a hint pointing at the real
|
|
53
|
+
// `omp plugin <action>` command; multi-word invocations still fall through to
|
|
54
|
+
// `launch`, so genuine prompts that merely begin with one of these words work.
|
|
47
55
|
const RESERVED_TOP_LEVEL_WORDS = new Map<string, string>([
|
|
48
56
|
[
|
|
49
57
|
"extensions",
|
|
50
58
|
'`omp extensions` is not a management command. Use `omp plugin list` / `omp plugin install`, or run `omp launch extensions` if you meant to send "extensions" as a prompt.',
|
|
51
59
|
],
|
|
60
|
+
[
|
|
61
|
+
"list",
|
|
62
|
+
'`omp list` is not a top-level command. Use `omp plugin list` to list installed plugins, or run `omp launch list` if you meant to send "list" as a prompt.',
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
"remove",
|
|
66
|
+
'`omp remove` is not a top-level command. Use `omp plugin uninstall <name>` to remove a plugin, or run `omp launch remove` if you meant to send "remove" as a prompt.',
|
|
67
|
+
],
|
|
52
68
|
]);
|
|
53
69
|
|
|
54
70
|
export function reservedTopLevelWordMessage(first: string | undefined, argc = 1): string | undefined {
|
|
@@ -69,10 +85,29 @@ export function isSubcommand(first: string | undefined): boolean {
|
|
|
69
85
|
|
|
70
86
|
export type ResolvedCliArgv = { argv: string[] } | { error: string };
|
|
71
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Index of the first argv token that names a registered subcommand, skipping
|
|
90
|
+
* leading global option flags (and any value they consume) with the same
|
|
91
|
+
* contract as the launch parser ({@link flagConsumesValue}). Returns -1 when
|
|
92
|
+
* scanning hits a non-subcommand positional, an end-of-options `--`, or the end
|
|
93
|
+
* of argv first.
|
|
94
|
+
*/
|
|
95
|
+
function leadingSubcommandIndex(argv: string[]): number {
|
|
96
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
97
|
+
const arg = argv[index];
|
|
98
|
+
if (arg === "--") return -1;
|
|
99
|
+
if (!arg.startsWith("-")) return isSubcommand(arg) ? index : -1;
|
|
100
|
+
if (flagConsumesValue(arg, argv[index + 1])) index += 1;
|
|
101
|
+
}
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
/**
|
|
73
106
|
* Decide what the CLI runner should do with raw argv: reject bare reserved
|
|
74
|
-
* management words, pass help/version through untouched,
|
|
75
|
-
*
|
|
107
|
+
* management words, pass help/version through untouched, route a recognized
|
|
108
|
+
* subcommand (even behind leading global flags like `--approval-mode=yolo`) to
|
|
109
|
+
* that command with the flags preserved, and forward everything else to
|
|
110
|
+
* `launch` (#2970).
|
|
76
111
|
*/
|
|
77
112
|
export function resolveCliArgv(argv: string[]): ResolvedCliArgv {
|
|
78
113
|
const first = argv[0];
|
|
@@ -81,5 +116,15 @@ export function resolveCliArgv(argv: string[]): ResolvedCliArgv {
|
|
|
81
116
|
if (first === "--help" || first === "-h" || first === "--version" || first === "-v" || first === "help") {
|
|
82
117
|
return { argv };
|
|
83
118
|
}
|
|
84
|
-
|
|
119
|
+
if (isSubcommand(first)) return { argv };
|
|
120
|
+
// A subcommand can hide behind leading global option flags
|
|
121
|
+
// (`omp --approval-mode=yolo acp`). `run` dispatches strictly on argv[0], so
|
|
122
|
+
// hoist the subcommand to the front and keep the leading flags as its own
|
|
123
|
+
// argv; the command's parser then applies them. Genuine launch prompts (no
|
|
124
|
+
// trailing subcommand) are untouched.
|
|
125
|
+
const subIndex = leadingSubcommandIndex(argv);
|
|
126
|
+
if (subIndex >= 0) {
|
|
127
|
+
return { argv: [argv[subIndex], ...argv.slice(0, subIndex), ...argv.slice(subIndex + 1)] };
|
|
128
|
+
}
|
|
129
|
+
return { argv: ["launch", ...argv] };
|
|
85
130
|
}
|
|
@@ -24,12 +24,6 @@ import {
|
|
|
24
24
|
resolveVariantAlias,
|
|
25
25
|
} from "@oh-my-pi/pi-catalog/variant-collapse";
|
|
26
26
|
|
|
27
|
-
// Sentinels for local-only OAuth tokens — declared inline to avoid loading
|
|
28
|
-
// provider modules at startup. Must match packages/ai/src/registry/lm-studio.ts
|
|
29
|
-
// and packages/ai/src/registry/vllm.ts.
|
|
30
|
-
const DEFAULT_LOCAL_TOKEN = "lm-studio-local";
|
|
31
|
-
const DEFAULT_VLLM_LOCAL_TOKEN = "vllm-local";
|
|
32
|
-
|
|
33
27
|
const SPECIAL_MODEL_MANAGER_PROVIDER_IDS: readonly string[] = [
|
|
34
28
|
"google-antigravity",
|
|
35
29
|
"google-gemini-cli",
|
|
@@ -41,6 +35,11 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
|
|
|
41
35
|
...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
|
|
42
36
|
];
|
|
43
37
|
|
|
38
|
+
// Sentinels for local-only OAuth tokens — declared inline to avoid loading
|
|
39
|
+
// provider modules at startup. Must match packages/ai/src/registry/llama-cpp.ts,
|
|
40
|
+
// packages/ai/src/registry/lm-studio.ts, and packages/ai/src/registry/vllm.ts.
|
|
41
|
+
const LOCAL_PROVIDER_PLACEHOLDERS = new Set<string>(["llama-cpp-local", "lm-studio-local", "vllm-local"]);
|
|
42
|
+
|
|
44
43
|
import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
45
44
|
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
46
45
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
|
|
@@ -85,7 +84,7 @@ export function isAuthenticated(apiKey: string | undefined | null): apiKey is st
|
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
function isDiscoveryBearerApiKey(apiKey: string | undefined | null): apiKey is string {
|
|
88
|
-
return isAuthenticated(apiKey) && apiKey
|
|
87
|
+
return isAuthenticated(apiKey) && !LOCAL_PROVIDER_PLACEHOLDERS.has(apiKey);
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
/** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
|