@hover-dev/core 0.17.0 → 0.19.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.
Files changed (109) hide show
  1. package/dist/engine.d.ts +16 -39
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +18 -67
  4. package/dist/specs/pageObjectManifest.d.ts.map +1 -1
  5. package/dist/specs/pageObjectManifest.js +11 -10
  6. package/dist/specs/replayGrounded.d.ts.map +1 -1
  7. package/dist/specs/writeApiSpec.d.ts +36 -0
  8. package/dist/specs/writeApiSpec.d.ts.map +1 -0
  9. package/dist/specs/writeApiSpec.js +94 -0
  10. package/package.json +5 -22
  11. package/dist/agents/argv.d.ts +0 -11
  12. package/dist/agents/argv.d.ts.map +0 -1
  13. package/dist/agents/argv.js +0 -23
  14. package/dist/agents/claude.d.ts +0 -3
  15. package/dist/agents/claude.d.ts.map +0 -1
  16. package/dist/agents/claude.js +0 -220
  17. package/dist/agents/codex.d.ts +0 -19
  18. package/dist/agents/codex.d.ts.map +0 -1
  19. package/dist/agents/codex.js +0 -231
  20. package/dist/agents/detect.d.ts +0 -46
  21. package/dist/agents/detect.d.ts.map +0 -1
  22. package/dist/agents/detect.js +0 -80
  23. package/dist/agents/gemini.d.ts +0 -17
  24. package/dist/agents/gemini.d.ts.map +0 -1
  25. package/dist/agents/gemini.js +0 -186
  26. package/dist/agents/index.d.ts +0 -6
  27. package/dist/agents/index.d.ts.map +0 -1
  28. package/dist/agents/index.js +0 -5
  29. package/dist/agents/invoke.d.ts +0 -12
  30. package/dist/agents/invoke.d.ts.map +0 -1
  31. package/dist/agents/invoke.js +0 -93
  32. package/dist/agents/qwen.d.ts +0 -17
  33. package/dist/agents/qwen.d.ts.map +0 -1
  34. package/dist/agents/qwen.js +0 -172
  35. package/dist/agents/registry.d.ts +0 -19
  36. package/dist/agents/registry.d.ts.map +0 -1
  37. package/dist/agents/registry.js +0 -30
  38. package/dist/agents/shared.d.ts +0 -28
  39. package/dist/agents/shared.d.ts.map +0 -1
  40. package/dist/agents/shared.js +0 -35
  41. package/dist/agents/types.d.ts +0 -194
  42. package/dist/agents/types.d.ts.map +0 -1
  43. package/dist/agents/types.js +0 -23
  44. package/dist/index.d.ts +0 -3
  45. package/dist/index.d.ts.map +0 -1
  46. package/dist/index.js +0 -2
  47. package/dist/mcp/actuateServer.d.ts +0 -3
  48. package/dist/mcp/actuateServer.d.ts.map +0 -1
  49. package/dist/mcp/actuateServer.js +0 -594
  50. package/dist/mcp/sourceFence.d.ts +0 -23
  51. package/dist/mcp/sourceFence.d.ts.map +0 -1
  52. package/dist/mcp/sourceFence.js +0 -79
  53. package/dist/mcp/sourceServer.d.ts +0 -3
  54. package/dist/mcp/sourceServer.d.ts.map +0 -1
  55. package/dist/mcp/sourceServer.js +0 -191
  56. package/dist/modes.d.ts +0 -39
  57. package/dist/modes.d.ts.map +0 -1
  58. package/dist/modes.js +0 -34
  59. package/dist/playwright/cdpStatus.d.ts +0 -14
  60. package/dist/playwright/cdpStatus.d.ts.map +0 -1
  61. package/dist/playwright/cdpStatus.js +0 -52
  62. package/dist/playwright/preflight.d.ts +0 -31
  63. package/dist/playwright/preflight.d.ts.map +0 -1
  64. package/dist/playwright/preflight.js +0 -82
  65. package/dist/playwright/preflightCache.d.ts +0 -27
  66. package/dist/playwright/preflightCache.d.ts.map +0 -1
  67. package/dist/playwright/preflightCache.js +0 -21
  68. package/dist/playwright/resolveMcpConfig.d.ts +0 -61
  69. package/dist/playwright/resolveMcpConfig.d.ts.map +0 -1
  70. package/dist/playwright/resolveMcpConfig.js +0 -84
  71. package/dist/plugin-api.d.ts +0 -237
  72. package/dist/plugin-api.d.ts.map +0 -1
  73. package/dist/plugin-api.js +0 -52
  74. package/dist/qa/classify.d.ts +0 -38
  75. package/dist/qa/classify.d.ts.map +0 -1
  76. package/dist/qa/classify.js +0 -138
  77. package/dist/runSession.d.ts +0 -53
  78. package/dist/runSession.d.ts.map +0 -1
  79. package/dist/runSession.js +0 -96
  80. package/dist/service/cdpHandlers.d.ts +0 -24
  81. package/dist/service/cdpHandlers.d.ts.map +0 -1
  82. package/dist/service/cdpHandlers.js +0 -50
  83. package/dist/service/cdpHint.d.ts +0 -41
  84. package/dist/service/cdpHint.d.ts.map +0 -1
  85. package/dist/service/cdpHint.js +0 -158
  86. package/dist/service/conventions.d.ts +0 -8
  87. package/dist/service/conventions.d.ts.map +0 -1
  88. package/dist/service/conventions.js +0 -42
  89. package/dist/service/relayHandlers.d.ts +0 -28
  90. package/dist/service/relayHandlers.d.ts.map +0 -1
  91. package/dist/service/relayHandlers.js +0 -105
  92. package/dist/service/saveHandlers.d.ts +0 -50
  93. package/dist/service/saveHandlers.d.ts.map +0 -1
  94. package/dist/service/saveHandlers.js +0 -77
  95. package/dist/service/types.d.ts +0 -158
  96. package/dist/service/types.d.ts.map +0 -1
  97. package/dist/service/types.js +0 -26
  98. package/dist/service.d.ts +0 -54
  99. package/dist/service.d.ts.map +0 -1
  100. package/dist/service.js +0 -1772
  101. package/dist/specs/businessMap.d.ts +0 -29
  102. package/dist/specs/businessMap.d.ts.map +0 -1
  103. package/dist/specs/businessMap.js +0 -95
  104. package/dist/specs/extractPageObjects.d.ts +0 -18
  105. package/dist/specs/extractPageObjects.d.ts.map +0 -1
  106. package/dist/specs/extractPageObjects.js +0 -98
  107. package/dist/specs/optimizeSpecWithAgent.d.ts +0 -9
  108. package/dist/specs/optimizeSpecWithAgent.d.ts.map +0 -1
  109. package/dist/specs/optimizeSpecWithAgent.js +0 -39
@@ -1,220 +0,0 @@
1
- /**
2
- * Running-cost accumulator across one parser lifecycle. Claude Code's
3
- * stream-json sometimes carries `total_cost_usd` on intermediate events; when
4
- * it does we just forward it. When it doesn't, we estimate from token usage
5
- * using public per-million pricing. Hardcoded rates because they change
6
- * rarely and we'd rather not network-fetch a price table from a sandbox.
7
- *
8
- * The estimate is empirically about 1.5–2× the authoritative
9
- * `total_cost_usd` that arrives on the final `result` event (overhead from
10
- * tool-definition tokens we don't see + Claude's actual cache hit ratios
11
- * vs. our pessimistic accounting). That's fine for the widget's purpose:
12
- * give the user a signal of cost direction and order-of-magnitude so they
13
- * know when to hit Stop. The done card displays the final authoritative
14
- * number from `total_cost_usd`, so the user always sees the ground truth.
15
- */
16
- const PRICE_PER_M_USD = {
17
- // claude-sonnet-4 / 4.5 / 4.6 / 4.7 — all priced the same as of 2026
18
- sonnet: { in: 3, out: 15, cacheCreate: 3.75, cacheRead: 0.3 },
19
- opus: { in: 15, out: 75, cacheCreate: 18.75, cacheRead: 1.5 },
20
- haiku: { in: 1, out: 5, cacheCreate: 1.25, cacheRead: 0.1 },
21
- };
22
- function estimateCostUsd(modelHint, usage) {
23
- // Match the most specific tier by substring. e.g. 'claude-sonnet-4-6' → sonnet.
24
- const m = (modelHint ?? 'sonnet').toLowerCase();
25
- const tier = m.includes('opus') ? PRICE_PER_M_USD.opus :
26
- m.includes('haiku') ? PRICE_PER_M_USD.haiku :
27
- PRICE_PER_M_USD.sonnet;
28
- return ((usage.input_tokens ?? 0) * tier.in +
29
- (usage.output_tokens ?? 0) * tier.out +
30
- (usage.cache_creation_input_tokens ?? 0) * tier.cacheCreate +
31
- (usage.cache_read_input_tokens ?? 0) * tier.cacheRead) / 1_000_000;
32
- }
33
- function claudeState(state) {
34
- // First touch on this state object — seed the keys we read below.
35
- if (typeof state.runningCost !== 'number') {
36
- state.runningCost = 0;
37
- state.runningTurns = 0;
38
- state.runningTokens = 0;
39
- state.runningModel = undefined;
40
- }
41
- return state;
42
- }
43
- /** Every built-in claude-code tool that has nothing to do with driving a
44
- * browser. Combined with `--strict-mcp-config` + an allow-list of mcp__*
45
- * ids, this leaves Claude with only the Playwright MCP (plus any
46
- * plugin-contributed MCPs) as a usable tool surface. */
47
- const CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
48
- // file / shell / data access — never appropriate for browser driving
49
- 'Bash', 'BashOutput', 'KillBash',
50
- 'Edit', 'MultiEdit', 'Write', 'Read', 'NotebookEdit',
51
- // `Task` was renamed `Agent` in newer Claude Code — it spawns a subagent with
52
- // the full tool surface (Bash/Write/…), a real sandbox escape, so deny BOTH
53
- // the current and legacy name. (Only this one targeted name was added; do NOT
54
- // add MCP-machinery tools like WaitForMcpServers here — denying those breaks
55
- // the MCP tool path and the agent loses its real browser tools.)
56
- 'Grep', 'Glob', 'Task', 'Agent', 'TodoWrite',
57
- 'WebFetch', 'WebSearch',
58
- // plan / worktree / cron / notification — irrelevant in -p mode
59
- 'EnterPlanMode', 'ExitPlanMode',
60
- 'EnterWorktree', 'ExitWorktree',
61
- 'CronCreate', 'CronDelete', 'CronList',
62
- 'PushNotification', 'RemoteTrigger',
63
- // task & tool introspection added in claude 2.1.x — let through and
64
- // the agent will burn turns exploring instead of executing
65
- 'ToolSearch',
66
- 'Monitor', 'TaskOutput', 'TaskStop',
67
- 'AskUserQuestion',
68
- 'ShareOnboardingGuide',
69
- // Skills are loaded independently of the --allowedTools allow-list, so an
70
- // allow-list of `mcp__playwright` does NOT block the `Skill` tool. Left
71
- // through, the agent burns a turn "checking for a project skill first" and
72
- // pollutes the crystallized spec with a junk `When · Skill` step. Deny it.
73
- 'Skill',
74
- // Playwright MCP's arbitrary-JS tools. browser_run_code_unsafe /
75
- // browser_evaluate run any JS in the page — a real prompt-injection exfil
76
- // path (fetch a token out, read localStorage) that punches through the
77
- // "Playwright MCP only" sandbox, and their output can't be translated into
78
- // a deterministic Playwright spec anyway (it lands as a `// TODO`). Agents
79
- // drive via click/fill/select and read state via snapshot instead.
80
- 'mcp__playwright__browser_run_code_unsafe',
81
- 'mcp__playwright__browser_evaluate',
82
- ];
83
- export const claudeAgent = {
84
- id: 'claude',
85
- binName: 'claude',
86
- protocol: 'argv',
87
- streamFormat: 'stream-json',
88
- sandboxStrength: 'hard',
89
- defaultDisallowedTools: CLAUDE_DEFAULT_DISALLOWED_TOOLS,
90
- // F7 optimize is text-in/text-out refinement — haiku is plenty and ~cheap.
91
- cheapModel: 'haiku',
92
- display: {
93
- label: 'Claude Code',
94
- tagline: 'Anthropic — best-in-class browser driving, hard tool sandbox',
95
- homepage: 'https://docs.claude.com/claude-code',
96
- installHint: 'npm install -g @anthropic-ai/claude-code',
97
- },
98
- buildArgs(opts) {
99
- const args = ['-p', opts.prompt];
100
- args.push('--output-format', 'stream-json', '--verbose');
101
- args.push('--permission-mode', 'dontAsk');
102
- if (opts.mcpConfig) {
103
- args.push('--mcp-config', opts.mcpConfig, '--strict-mcp-config');
104
- }
105
- if (opts.allowedTools?.length) {
106
- args.push('--allowedTools', ...opts.allowedTools);
107
- }
108
- if (opts.disallowedTools?.length) {
109
- args.push('--disallowedTools', ...opts.disallowedTools);
110
- }
111
- if (opts.maxBudgetUsd != null) {
112
- args.push('--max-budget-usd', String(opts.maxBudgetUsd));
113
- }
114
- if (opts.maxTurns != null) {
115
- args.push('--max-turns', String(opts.maxTurns));
116
- }
117
- if (opts.model) {
118
- args.push('--model', opts.model);
119
- }
120
- if (opts.effort) {
121
- args.push('--effort', opts.effort);
122
- }
123
- if (opts.sessionId) {
124
- args.push('--resume', opts.sessionId);
125
- }
126
- if (opts.appendSystemPrompt && opts.appendSystemPrompt.trim().length > 0) {
127
- args.push('--append-system-prompt', opts.appendSystemPrompt);
128
- }
129
- return args;
130
- },
131
- parseEvent(line, state = {}) {
132
- if (!line.trim())
133
- return [];
134
- let ev;
135
- try {
136
- ev = JSON.parse(line);
137
- }
138
- catch {
139
- return [{ kind: 'raw', line }];
140
- }
141
- const s = claudeState(state);
142
- const out = [];
143
- if (ev.type === 'system' && ev.subtype === 'init') {
144
- // Fresh session — reset the cost/turn accumulator.
145
- s.runningCost = 0;
146
- s.runningTurns = 0;
147
- s.runningTokens = 0;
148
- s.runningModel = ev.model;
149
- if (ev.session_id) {
150
- out.push({ kind: 'session_start', sessionId: ev.session_id, model: ev.model });
151
- }
152
- for (const server of ev.mcp_servers ?? []) {
153
- out.push({ kind: 'mcp_status', server: server.name, status: server.status });
154
- }
155
- return out;
156
- }
157
- if (ev.type === 'assistant') {
158
- s.runningTurns += 1;
159
- // Claude Code sometimes carries `total_cost_usd` on intermediate events;
160
- // when present it's authoritative (server-computed, includes anything
161
- // we'd miss). When absent, estimate from this turn's usage so the widget
162
- // still shows a growing $ counter on long runs.
163
- if (typeof ev.total_cost_usd === 'number') {
164
- s.runningCost = ev.total_cost_usd;
165
- }
166
- else if (ev.message?.usage) {
167
- s.runningCost += estimateCostUsd(s.runningModel ?? ev.message.model, ev.message.usage);
168
- }
169
- // Token consumption = fresh input + output only. We deliberately EXCLUDE
170
- // cache_read (and cache_creation): Claude re-reads ~the whole context from
171
- // cache every turn, so summing cache_read across turns inflates the total
172
- // ~5-10× and diverges from what Claude Code reports. input+output tracks
173
- // the new tokens processed, matching the user's mental model + CC's number.
174
- if (ev.message?.usage) {
175
- const u = ev.message.usage;
176
- s.runningTokens += (u.input_tokens ?? 0) + (u.output_tokens ?? 0);
177
- }
178
- out.push({ kind: 'usage', costUsd: s.runningCost, turns: s.runningTurns, tokens: s.runningTokens });
179
- for (const block of ev.message?.content ?? []) {
180
- if (block.type === 'tool_use') {
181
- const name = block.name ?? '';
182
- const tool = name.replace(/^mcp__playwright__/, '');
183
- out.push({
184
- kind: 'tool_use',
185
- tool,
186
- input: block.input,
187
- costUsdSnapshot: s.runningCost,
188
- tokensSnapshot: s.runningTokens,
189
- });
190
- }
191
- else if (block.type === 'text') {
192
- const text = block.text?.trim();
193
- if (text)
194
- out.push({ kind: 'text', text });
195
- }
196
- }
197
- return out;
198
- }
199
- if (ev.type === 'user') {
200
- for (const block of ev.message?.content ?? []) {
201
- if (block.type === 'tool_result') {
202
- out.push({ kind: 'tool_result', isError: block.is_error });
203
- }
204
- }
205
- return out;
206
- }
207
- if (ev.type === 'result') {
208
- out.push({
209
- kind: 'session_end',
210
- turns: ev.num_turns,
211
- costUsd: ev.total_cost_usd,
212
- tokens: s.runningTokens,
213
- isError: ev.is_error,
214
- summary: ev.result,
215
- });
216
- return out;
217
- }
218
- return [];
219
- },
220
- };
@@ -1,19 +0,0 @@
1
- import type { AgentDescriptor, ParserState } from './types.js';
2
- export declare const codexAgent: AgentDescriptor;
3
- /**
4
- * Test-only escape hatches. Tests pass a state object in and get the
5
- * accumulated counters back — same shape as the parser sees during a real
6
- * invocation, just driven by the test instead of by invokeAgent.
7
- */
8
- export declare const __testing: {
9
- freshState: () => ParserState;
10
- resetCounters: (state: ParserState) => void;
11
- getState: (state: ParserState) => {
12
- runningCost: number;
13
- runningTurns: number;
14
- runningSessionId: string | undefined;
15
- lastAgentMessage: string | undefined;
16
- sawErrorEvent: boolean;
17
- };
18
- };
19
- //# sourceMappingURL=codex.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/agents/codex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AA6K3F,eAAO,MAAM,UAAU,EAAE,eAoKxB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS;sBACJ,WAAW;2BACJ,WAAW;sBAChB,WAAW;;;;;;;CAU9B,CAAC"}
@@ -1,231 +0,0 @@
1
- import { stripMcpPrefix } from './shared.js';
2
- /**
3
- * Pricing per million tokens. Keep in lockstep with claude.ts's table —
4
- * approximate published OpenAI rates as of 2026. We are deliberately
5
- * conservative (no cache-hit discount); cost shown to the user is therefore
6
- * a high-water estimate, which is the right error direction for a "should
7
- * I hit Stop now" UI signal.
8
- */
9
- const PRICE_PER_M_USD = {
10
- // gpt-5.5 / gpt-5.4 / gpt-5 — public per-million pricing is similar to
11
- // claude opus; tune empirically when OpenAI publishes a stable price table
12
- // for the Codex tier specifically.
13
- 'gpt-5.5': { in: 5, out: 25 },
14
- 'gpt-5.4': { in: 5, out: 25 },
15
- 'gpt-5': { in: 5, out: 25 },
16
- // gpt-4.x kept for users on legacy --model
17
- 'gpt-4o': { in: 2.5, out: 10 },
18
- 'gpt-4': { in: 30, out: 60 },
19
- };
20
- // `modelHint` is currently always passed as undefined — the parser can't see
21
- // the invocation's --model — so the default tier below is what gets used. The
22
- // parameter is kept so a future caller that does have the model id can pass it.
23
- function estimateCostUsd(modelHint, usage) {
24
- const m = (modelHint ?? 'gpt-5.5').toLowerCase();
25
- // Match by longest-prefix so 'gpt-5.5-mini' picks up the 'gpt-5.5' tier.
26
- const tier = Object.entries(PRICE_PER_M_USD).find(([key]) => m.startsWith(key))?.[1] ??
27
- PRICE_PER_M_USD['gpt-5.5'];
28
- return ((usage.input_tokens ?? 0) * tier.in +
29
- (usage.output_tokens ?? 0) * tier.out) / 1_000_000;
30
- }
31
- function codexState(state) {
32
- if (typeof state.runningCost !== 'number') {
33
- state.runningCost = 0;
34
- state.runningTurns = 0;
35
- state.runningTokens = 0;
36
- state.runningSessionId = undefined;
37
- state.lastAgentMessage = undefined;
38
- state.sawErrorEvent = false;
39
- state.itemTypeById = new Map();
40
- }
41
- return state;
42
- }
43
- function resetCodexCounters(s) {
44
- s.runningCost = 0;
45
- s.runningTurns = 0;
46
- s.runningTokens = 0;
47
- s.runningSessionId = undefined;
48
- s.lastAgentMessage = undefined;
49
- s.sawErrorEvent = false;
50
- s.itemTypeById.clear();
51
- }
52
- /** The tool-restriction is codex's only sandbox (no --allowedTools flag), so it
53
- * must mirror the hard-sandbox allow-list the service computes per mode — NOT a
54
- * hardcoded "playwright only", which made codex refuse the active mode's plugin
55
- * tools (api_request, replay_flow, hover-control, …). Built from opts.allowedTools. */
56
- function codexDeveloperInstructions(allowedTools) {
57
- const prefixes = (allowedTools && allowedTools.length ? allowedTools : ['mcp__playwright']).map((p) => `${p}__*`);
58
- return [
59
- 'You are operating in Hover, a browser- and API-testing tool.',
60
- `Use ONLY MCP tools whose name starts with one of these prefixes: ${prefixes.join(', ')}. They cover driving the browser, the Hover control + API-request tools, and reading source — everything the task needs.`,
61
- 'Do NOT call shell, file-edit, web-search, or any other built-in tool.',
62
- 'Do NOT navigate to a URL the user is already on; check the page state via `browser_snapshot` first.',
63
- 'When the task is complete, emit a short agent_message summary and stop.',
64
- ].join(' ');
65
- }
66
- export const codexAgent = {
67
- id: 'codex',
68
- binName: 'codex',
69
- protocol: 'argv',
70
- streamFormat: 'json-lines',
71
- sandboxStrength: 'soft',
72
- display: {
73
- label: 'OpenAI Codex',
74
- tagline: 'OpenAI — soft sandbox (no built-in tool deny-list)',
75
- homepage: 'https://developers.openai.com/codex',
76
- installHint: 'npm install -g @openai/codex',
77
- },
78
- buildArgs(opts) {
79
- const args = ['exec'];
80
- // Resume must come BEFORE the prompt positional. `codex exec resume <id>
81
- // [prompt]` is the documented shape.
82
- if (opts.sessionId) {
83
- args.push('resume', opts.sessionId);
84
- }
85
- args.push(opts.prompt);
86
- // JSONL streaming output.
87
- args.push('--json');
88
- // Never prompt for approval in headless mode.
89
- args.push('--ask-for-approval', 'never');
90
- // Soft sandbox: prevent shell side-effects on disk / network even when
91
- // the agent tries to call its built-in shell. read-only is the strictest
92
- // documented level.
93
- args.push('--sandbox', 'read-only');
94
- if (opts.model) {
95
- args.push('--model', opts.model);
96
- }
97
- // Reasoning effort → the API's reasoning_effort, set via the `-c` TOML
98
- // override (no stable long flag exists). Sits alongside the other -c keys.
99
- if (opts.effort) {
100
- args.push('-c', `model_reasoning_effort=${opts.effort}`);
101
- }
102
- // System-prompt injection. Codex has no --append-system-prompt; we route
103
- // through `-c developer_instructions='...'`. Concatenate the standing
104
- // Hover-mode instructions with whatever the caller passes (e.g. "user is
105
- // already on http://localhost:5173/").
106
- const base = codexDeveloperInstructions(opts.allowedTools);
107
- const sysPrompt = opts.appendSystemPrompt && opts.appendSystemPrompt.trim().length > 0
108
- ? `${base} ${opts.appendSystemPrompt}`
109
- : base;
110
- args.push('-c', `developer_instructions=${JSON.stringify(sysPrompt)}`);
111
- // MCP servers are configured in ~/.codex/config.toml at install time,
112
- // not per-invocation. If the user passed an mcpConfig path, we don't
113
- // have a way to forward it to codex — log a warning to stderr from the
114
- // invoker so the user knows. (See invoke.ts wiring.)
115
- // No equivalent for --max-budget-usd or --allowedTools.
116
- return args;
117
- },
118
- parseEvent(line, state = {}) {
119
- if (!line.trim())
120
- return [];
121
- let ev;
122
- try {
123
- ev = JSON.parse(line);
124
- }
125
- catch {
126
- return [{ kind: 'raw', line }];
127
- }
128
- const s = codexState(state);
129
- const out = [];
130
- if (ev.type === 'thread.started') {
131
- resetCodexCounters(s);
132
- if (ev.thread_id) {
133
- s.runningSessionId = ev.thread_id;
134
- out.push({ kind: 'session_start', sessionId: ev.thread_id, model: ev.model });
135
- }
136
- return out;
137
- }
138
- if (ev.type === 'item.started' && ev.item) {
139
- const it = ev.item;
140
- if (it.id && it.type)
141
- s.itemTypeById.set(it.id, it.type);
142
- if (it.type === 'mcp_tool_call') {
143
- // The exact field names aren't published. Read defensively: prefer
144
- // `name`, fall back to `tool`. Same for input.
145
- const rawName = it.name ?? it.tool ?? '';
146
- const tool = stripMcpPrefix(rawName);
147
- out.push({ kind: 'tool_use', tool, input: it.input ?? it.arguments, costUsdSnapshot: s.runningCost, tokensSnapshot: s.runningTokens });
148
- }
149
- else if (it.type === 'command_execution') {
150
- // We DISCOURAGED this in developer_instructions but the agent can
151
- // still try. Surface it so the user sees it happen.
152
- out.push({ kind: 'tool_use', tool: 'shell', input: { command: it.command }, costUsdSnapshot: s.runningCost, tokensSnapshot: s.runningTokens });
153
- }
154
- return out;
155
- }
156
- if (ev.type === 'item.completed' && ev.item) {
157
- const it = ev.item;
158
- const recordedType = (it.id && s.itemTypeById.get(it.id)) || it.type;
159
- if (recordedType === 'agent_message') {
160
- const text = it.text?.trim();
161
- if (text) {
162
- s.lastAgentMessage = text;
163
- out.push({ kind: 'text', text });
164
- }
165
- }
166
- else if (recordedType === 'mcp_tool_call' || recordedType === 'command_execution') {
167
- const isError = it.is_error === true ||
168
- (typeof it.status === 'string' && /error|fail/i.test(it.status));
169
- out.push({ kind: 'tool_result', isError });
170
- }
171
- return out;
172
- }
173
- if (ev.type === 'turn.completed') {
174
- s.runningTurns += 1;
175
- if (ev.usage) {
176
- // The parser has no access to the invocation's --model, so we let
177
- // estimateCostUsd fall back to its fixed default tier. Cost is a
178
- // high-water "should I hit Stop now" signal, not an invoice.
179
- s.runningCost += estimateCostUsd(undefined, ev.usage);
180
- s.runningTokens += (ev.usage.input_tokens ?? 0) + (ev.usage.output_tokens ?? 0);
181
- }
182
- out.push({ kind: 'usage', costUsd: s.runningCost, turns: s.runningTurns, tokens: s.runningTokens });
183
- return out;
184
- }
185
- // Codex emits various error envelopes; we conservatively match anything
186
- // whose `type` contains 'error' or carries a top-level message string.
187
- if (ev.type && /error/i.test(ev.type)) {
188
- s.sawErrorEvent = true;
189
- if (ev.message) {
190
- out.push({ kind: 'text', text: `[codex] ${ev.message}` });
191
- }
192
- return out;
193
- }
194
- return [];
195
- },
196
- /**
197
- * Codex doesn't emit a `session_end` line — the child process simply
198
- * exits after the final `turn.completed`. We synthesize the terminator
199
- * here so the widget sees the same shape it sees from claude.
200
- */
201
- onStreamEnd(exitCode, state = {}) {
202
- const s = codexState(state);
203
- return {
204
- kind: 'session_end',
205
- turns: s.runningTurns,
206
- costUsd: s.runningCost,
207
- tokens: s.runningTokens,
208
- isError: s.sawErrorEvent || (exitCode != null && exitCode !== 0),
209
- summary: s.lastAgentMessage,
210
- };
211
- },
212
- };
213
- /**
214
- * Test-only escape hatches. Tests pass a state object in and get the
215
- * accumulated counters back — same shape as the parser sees during a real
216
- * invocation, just driven by the test instead of by invokeAgent.
217
- */
218
- export const __testing = {
219
- freshState: () => ({}),
220
- resetCounters: (state) => resetCodexCounters(codexState(state)),
221
- getState: (state) => {
222
- const s = codexState(state);
223
- return {
224
- runningCost: s.runningCost,
225
- runningTurns: s.runningTurns,
226
- runningSessionId: s.runningSessionId,
227
- lastAgentMessage: s.lastAgentMessage,
228
- sawErrorEvent: s.sawErrorEvent,
229
- };
230
- },
231
- };
@@ -1,46 +0,0 @@
1
- import type { AgentDescriptor } from './types.js';
2
- /**
3
- * Find a binary on PATH. Returns absolute path or null.
4
- * macOS/Linux uses `which`; Windows uses `where`.
5
- */
6
- export declare function resolveOnPath(binName: string): Promise<string | null>;
7
- export declare function resolveBinForAgent(descriptor: AgentDescriptor): Promise<string | null>;
8
- export interface DetectedAgent {
9
- descriptor: AgentDescriptor;
10
- binPath: string;
11
- }
12
- /**
13
- * Scan PATH for every agent in the registry. Returns only the ones found,
14
- * in registry insertion order.
15
- *
16
- * Probes all agents in parallel — each `which`/`where` call is ~50-200ms
17
- * and the registry's growth target is 7-10 agents, so serial would noticeably
18
- * lag the first widget hello / agent-dropdown open.
19
- */
20
- export declare function detectAgents(): Promise<DetectedAgent[]>;
21
- export interface AgentAvailability {
22
- id: string;
23
- label: string;
24
- tagline?: string;
25
- sandboxStrength: 'hard' | 'soft';
26
- installed: boolean;
27
- binPath?: string;
28
- homepage?: string;
29
- installHint?: string;
30
- }
31
- /**
32
- * Like `detectAgents`, but also includes registered-but-not-installed agents
33
- * so the widget can render them dimmed with an install hint. Order matches
34
- * the registry. Probes run in parallel — see `detectAgents`.
35
- */
36
- export declare function listAgentAvailability(): Promise<AgentAvailability[]>;
37
- /**
38
- * Pick the agent we should default to when the user / Vite plugin didn't
39
- * specify one. Prefer the explicit hint if it's installed; otherwise the
40
- * first registered agent that's installed; finally null if nothing matches.
41
- *
42
- * `preferredId` is typically `process.env.HOVER_AGENT` or the value the user
43
- * picked in the widget last session (persisted by the widget to localStorage).
44
- */
45
- export declare function pickPrimaryAgent(preferredId?: string): Promise<DetectedAgent | null>;
46
- //# sourceMappingURL=detect.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/agents/detect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS3E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAE5F;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAO7D;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgB1E;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU1F"}
@@ -1,80 +0,0 @@
1
- import { execFile } from 'node:child_process';
2
- import { promisify } from 'node:util';
3
- import { AGENTS, listAgents } from './registry.js';
4
- const execFileAsync = promisify(execFile);
5
- /**
6
- * Find a binary on PATH. Returns absolute path or null.
7
- * macOS/Linux uses `which`; Windows uses `where`.
8
- */
9
- export async function resolveOnPath(binName) {
10
- const tool = process.platform === 'win32' ? 'where' : 'which';
11
- try {
12
- const { stdout } = await execFileAsync(tool, [binName]);
13
- const first = stdout.split('\n')[0]?.trim();
14
- return first && first.length > 0 ? first : null;
15
- }
16
- catch {
17
- return null;
18
- }
19
- }
20
- export async function resolveBinForAgent(descriptor) {
21
- return resolveOnPath(descriptor.binName);
22
- }
23
- /**
24
- * Scan PATH for every agent in the registry. Returns only the ones found,
25
- * in registry insertion order.
26
- *
27
- * Probes all agents in parallel — each `which`/`where` call is ~50-200ms
28
- * and the registry's growth target is 7-10 agents, so serial would noticeably
29
- * lag the first widget hello / agent-dropdown open.
30
- */
31
- export async function detectAgents() {
32
- const descriptors = listAgents();
33
- const paths = await Promise.all(descriptors.map(d => resolveBinForAgent(d)));
34
- return descriptors.flatMap((descriptor, i) => {
35
- const binPath = paths[i];
36
- return binPath ? [{ descriptor, binPath }] : [];
37
- });
38
- }
39
- /**
40
- * Like `detectAgents`, but also includes registered-but-not-installed agents
41
- * so the widget can render them dimmed with an install hint. Order matches
42
- * the registry. Probes run in parallel — see `detectAgents`.
43
- */
44
- export async function listAgentAvailability() {
45
- const descriptors = listAgents();
46
- const paths = await Promise.all(descriptors.map(d => resolveBinForAgent(d)));
47
- return descriptors.map((descriptor, i) => {
48
- const binPath = paths[i];
49
- return {
50
- id: descriptor.id,
51
- label: descriptor.display.label,
52
- tagline: descriptor.display.tagline,
53
- sandboxStrength: descriptor.sandboxStrength,
54
- installed: binPath != null,
55
- binPath: binPath ?? undefined,
56
- homepage: descriptor.display.homepage,
57
- installHint: descriptor.display.installHint,
58
- };
59
- });
60
- }
61
- /**
62
- * Pick the agent we should default to when the user / Vite plugin didn't
63
- * specify one. Prefer the explicit hint if it's installed; otherwise the
64
- * first registered agent that's installed; finally null if nothing matches.
65
- *
66
- * `preferredId` is typically `process.env.HOVER_AGENT` or the value the user
67
- * picked in the widget last session (persisted by the widget to localStorage).
68
- */
69
- export async function pickPrimaryAgent(preferredId) {
70
- if (preferredId) {
71
- const descriptor = AGENTS[preferredId];
72
- if (descriptor) {
73
- const binPath = await resolveBinForAgent(descriptor);
74
- if (binPath)
75
- return { descriptor, binPath };
76
- }
77
- }
78
- const detected = await detectAgents();
79
- return detected[0] ?? null;
80
- }
@@ -1,17 +0,0 @@
1
- import type { AgentDescriptor, ParserState } from './types.js';
2
- export declare const geminiAgent: AgentDescriptor;
3
- /**
4
- * Test-only escape hatches, same pattern as cursor.ts / codex.ts.
5
- */
6
- export declare const __testing: {
7
- freshState: () => ParserState;
8
- resetCounters: (state: ParserState) => void;
9
- getState: (state: ParserState) => {
10
- runningTurns: number;
11
- runningSessionId: string | undefined;
12
- runningModel: string | undefined;
13
- lastAssistantText: string | undefined;
14
- sawErrorEvent: boolean;
15
- };
16
- };
17
- //# sourceMappingURL=gemini.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/agents/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AA+J3F,eAAO,MAAM,WAAW,EAAE,eAiJzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS;sBACJ,WAAW;2BACJ,WAAW;sBAChB,WAAW;;;;;;;CAU9B,CAAC"}