@hover-dev/core 0.17.0 → 0.18.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/dist/engine.d.ts +14 -39
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +16 -67
- package/dist/specs/pageObjectManifest.d.ts.map +1 -1
- package/dist/specs/pageObjectManifest.js +11 -10
- package/dist/specs/replayGrounded.d.ts.map +1 -1
- package/package.json +5 -22
- package/dist/agents/argv.d.ts +0 -11
- package/dist/agents/argv.d.ts.map +0 -1
- package/dist/agents/argv.js +0 -23
- package/dist/agents/claude.d.ts +0 -3
- package/dist/agents/claude.d.ts.map +0 -1
- package/dist/agents/claude.js +0 -220
- package/dist/agents/codex.d.ts +0 -19
- package/dist/agents/codex.d.ts.map +0 -1
- package/dist/agents/codex.js +0 -231
- package/dist/agents/detect.d.ts +0 -46
- package/dist/agents/detect.d.ts.map +0 -1
- package/dist/agents/detect.js +0 -80
- package/dist/agents/gemini.d.ts +0 -17
- package/dist/agents/gemini.d.ts.map +0 -1
- package/dist/agents/gemini.js +0 -186
- package/dist/agents/index.d.ts +0 -6
- package/dist/agents/index.d.ts.map +0 -1
- package/dist/agents/index.js +0 -5
- package/dist/agents/invoke.d.ts +0 -12
- package/dist/agents/invoke.d.ts.map +0 -1
- package/dist/agents/invoke.js +0 -93
- package/dist/agents/qwen.d.ts +0 -17
- package/dist/agents/qwen.d.ts.map +0 -1
- package/dist/agents/qwen.js +0 -172
- package/dist/agents/registry.d.ts +0 -19
- package/dist/agents/registry.d.ts.map +0 -1
- package/dist/agents/registry.js +0 -30
- package/dist/agents/shared.d.ts +0 -28
- package/dist/agents/shared.d.ts.map +0 -1
- package/dist/agents/shared.js +0 -35
- package/dist/agents/types.d.ts +0 -194
- package/dist/agents/types.d.ts.map +0 -1
- package/dist/agents/types.js +0 -23
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/mcp/actuateServer.d.ts +0 -3
- package/dist/mcp/actuateServer.d.ts.map +0 -1
- package/dist/mcp/actuateServer.js +0 -594
- package/dist/mcp/sourceFence.d.ts +0 -23
- package/dist/mcp/sourceFence.d.ts.map +0 -1
- package/dist/mcp/sourceFence.js +0 -79
- package/dist/mcp/sourceServer.d.ts +0 -3
- package/dist/mcp/sourceServer.d.ts.map +0 -1
- package/dist/mcp/sourceServer.js +0 -191
- package/dist/modes.d.ts +0 -39
- package/dist/modes.d.ts.map +0 -1
- package/dist/modes.js +0 -34
- package/dist/playwright/cdpStatus.d.ts +0 -14
- package/dist/playwright/cdpStatus.d.ts.map +0 -1
- package/dist/playwright/cdpStatus.js +0 -52
- package/dist/playwright/preflight.d.ts +0 -31
- package/dist/playwright/preflight.d.ts.map +0 -1
- package/dist/playwright/preflight.js +0 -82
- package/dist/playwright/preflightCache.d.ts +0 -27
- package/dist/playwright/preflightCache.d.ts.map +0 -1
- package/dist/playwright/preflightCache.js +0 -21
- package/dist/playwright/resolveMcpConfig.d.ts +0 -61
- package/dist/playwright/resolveMcpConfig.d.ts.map +0 -1
- package/dist/playwright/resolveMcpConfig.js +0 -84
- package/dist/plugin-api.d.ts +0 -237
- package/dist/plugin-api.d.ts.map +0 -1
- package/dist/plugin-api.js +0 -52
- package/dist/qa/classify.d.ts +0 -38
- package/dist/qa/classify.d.ts.map +0 -1
- package/dist/qa/classify.js +0 -138
- package/dist/runSession.d.ts +0 -53
- package/dist/runSession.d.ts.map +0 -1
- package/dist/runSession.js +0 -96
- package/dist/service/cdpHandlers.d.ts +0 -24
- package/dist/service/cdpHandlers.d.ts.map +0 -1
- package/dist/service/cdpHandlers.js +0 -50
- package/dist/service/cdpHint.d.ts +0 -41
- package/dist/service/cdpHint.d.ts.map +0 -1
- package/dist/service/cdpHint.js +0 -158
- package/dist/service/conventions.d.ts +0 -8
- package/dist/service/conventions.d.ts.map +0 -1
- package/dist/service/conventions.js +0 -42
- package/dist/service/relayHandlers.d.ts +0 -28
- package/dist/service/relayHandlers.d.ts.map +0 -1
- package/dist/service/relayHandlers.js +0 -105
- package/dist/service/saveHandlers.d.ts +0 -50
- package/dist/service/saveHandlers.d.ts.map +0 -1
- package/dist/service/saveHandlers.js +0 -77
- package/dist/service/types.d.ts +0 -158
- package/dist/service/types.d.ts.map +0 -1
- package/dist/service/types.js +0 -26
- package/dist/service.d.ts +0 -54
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -1772
- package/dist/specs/businessMap.d.ts +0 -29
- package/dist/specs/businessMap.d.ts.map +0 -1
- package/dist/specs/businessMap.js +0 -95
- package/dist/specs/extractPageObjects.d.ts +0 -18
- package/dist/specs/extractPageObjects.d.ts.map +0 -1
- package/dist/specs/extractPageObjects.js +0 -98
- package/dist/specs/optimizeSpecWithAgent.d.ts +0 -9
- package/dist/specs/optimizeSpecWithAgent.d.ts.map +0 -1
- package/dist/specs/optimizeSpecWithAgent.js +0 -39
package/dist/agents/codex.js
DELETED
|
@@ -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
|
-
};
|
package/dist/agents/detect.d.ts
DELETED
|
@@ -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"}
|
package/dist/agents/detect.js
DELETED
|
@@ -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
|
-
}
|
package/dist/agents/gemini.d.ts
DELETED
|
@@ -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"}
|
package/dist/agents/gemini.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { HOVER_PROMPT_PREFACE, stripMcpPrefix } from './shared.js';
|
|
2
|
-
function geminiState(state) {
|
|
3
|
-
if (typeof state.runningTurns !== 'number') {
|
|
4
|
-
state.runningTurns = 0;
|
|
5
|
-
state.runningSessionId = undefined;
|
|
6
|
-
state.runningModel = undefined;
|
|
7
|
-
state.lastAssistantText = undefined;
|
|
8
|
-
state.sawErrorEvent = false;
|
|
9
|
-
state.toolNameByUseId = new Map();
|
|
10
|
-
}
|
|
11
|
-
return state;
|
|
12
|
-
}
|
|
13
|
-
function resetGeminiCounters(s) {
|
|
14
|
-
s.runningTurns = 0;
|
|
15
|
-
s.runningSessionId = undefined;
|
|
16
|
-
s.runningModel = undefined;
|
|
17
|
-
s.lastAssistantText = undefined;
|
|
18
|
-
s.sawErrorEvent = false;
|
|
19
|
-
s.toolNameByUseId.clear();
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Extract assistant text from a `message` event whose `content` may be a
|
|
23
|
-
* plain string OR an array of `{type:'text', text}` content blocks. Gemini's
|
|
24
|
-
* docs aren't explicit on which shape ships per build, so we handle both.
|
|
25
|
-
*/
|
|
26
|
-
function extractMessageText(ev) {
|
|
27
|
-
if (typeof ev.content === 'string') {
|
|
28
|
-
const t = ev.content.trim();
|
|
29
|
-
return t.length > 0 ? t : undefined;
|
|
30
|
-
}
|
|
31
|
-
if (Array.isArray(ev.content)) {
|
|
32
|
-
const parts = ev.content
|
|
33
|
-
.filter(b => b.type === 'text' && typeof b.text === 'string')
|
|
34
|
-
.map(b => b.text.trim())
|
|
35
|
-
.filter(t => t.length > 0);
|
|
36
|
-
return parts.length > 0 ? parts.join('\n') : undefined;
|
|
37
|
-
}
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
export const geminiAgent = {
|
|
41
|
-
id: 'gemini',
|
|
42
|
-
binName: 'gemini',
|
|
43
|
-
protocol: 'argv',
|
|
44
|
-
streamFormat: 'json-lines',
|
|
45
|
-
sandboxStrength: 'soft',
|
|
46
|
-
display: {
|
|
47
|
-
label: 'Gemini',
|
|
48
|
-
tagline: 'Google Gemini — soft sandbox (no built-in tool deny-list)',
|
|
49
|
-
homepage: 'https://github.com/google-gemini/gemini-cli',
|
|
50
|
-
installHint: 'npm install -g @google/gemini-cli',
|
|
51
|
-
},
|
|
52
|
-
buildArgs(opts) {
|
|
53
|
-
// Gemini has no --append-system-prompt CLI flag (only the
|
|
54
|
-
// GEMINI_SYSTEM_MD env var which writes a file). Prepend the HOVER-mode
|
|
55
|
-
// preface to the prompt instead — same pattern as cursor.ts / aider.ts.
|
|
56
|
-
const preface = opts.appendSystemPrompt && opts.appendSystemPrompt.trim().length > 0
|
|
57
|
-
? `${HOVER_PROMPT_PREFACE} ${opts.appendSystemPrompt}`
|
|
58
|
-
: HOVER_PROMPT_PREFACE;
|
|
59
|
-
const finalPrompt = `${preface}\n\n${opts.prompt}`;
|
|
60
|
-
const args = ['-p', finalPrompt];
|
|
61
|
-
// NDJSON streaming output.
|
|
62
|
-
args.push('--output-format', 'stream-json');
|
|
63
|
-
// Auto-approve all tool calls so the run doesn't hang. The newer
|
|
64
|
-
// canonical form is --approval-mode=yolo; --yolo is deprecated but
|
|
65
|
-
// still accepted in 2026-05. We use the modern form.
|
|
66
|
-
args.push('--approval-mode', 'yolo');
|
|
67
|
-
if (opts.model) {
|
|
68
|
-
args.push('--model', opts.model);
|
|
69
|
-
}
|
|
70
|
-
if (opts.sessionId) {
|
|
71
|
-
// --resume <id> is the documented form. -r is the alias. The single
|
|
72
|
-
// string 'latest' picks the most recent session; we only pass an
|
|
73
|
-
// explicit id, never the literal 'latest'.
|
|
74
|
-
args.push('--resume', opts.sessionId);
|
|
75
|
-
}
|
|
76
|
-
// MCP servers configured via `gemini mcp add` at install time — no
|
|
77
|
-
// per-invocation --mcp-config equivalent.
|
|
78
|
-
// No equivalent for --max-budget-usd or --allowedTools / --disallowedTools
|
|
79
|
-
// in the disable-built-ins sense.
|
|
80
|
-
return args;
|
|
81
|
-
},
|
|
82
|
-
parseEvent(line, state = {}) {
|
|
83
|
-
if (!line.trim())
|
|
84
|
-
return [];
|
|
85
|
-
let ev;
|
|
86
|
-
try {
|
|
87
|
-
ev = JSON.parse(line);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return [{ kind: 'raw', line }];
|
|
91
|
-
}
|
|
92
|
-
const s = geminiState(state);
|
|
93
|
-
const out = [];
|
|
94
|
-
if (ev.type === 'init') {
|
|
95
|
-
resetGeminiCounters(s);
|
|
96
|
-
s.runningModel = ev.model;
|
|
97
|
-
if (ev.session_id) {
|
|
98
|
-
s.runningSessionId = ev.session_id;
|
|
99
|
-
out.push({ kind: 'session_start', sessionId: ev.session_id, model: ev.model });
|
|
100
|
-
}
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
103
|
-
if (ev.type === 'message') {
|
|
104
|
-
// Only count and surface assistant messages — user echoes (the
|
|
105
|
-
// role:'user' message events) don't count as turns from our POV.
|
|
106
|
-
if (ev.role === 'assistant' || ev.role === undefined) {
|
|
107
|
-
s.runningTurns += 1;
|
|
108
|
-
out.push({ kind: 'usage', turns: s.runningTurns });
|
|
109
|
-
const text = extractMessageText(ev);
|
|
110
|
-
if (text) {
|
|
111
|
-
s.lastAssistantText = text;
|
|
112
|
-
out.push({ kind: 'text', text });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return out;
|
|
116
|
-
}
|
|
117
|
-
if (ev.type === 'tool_use') {
|
|
118
|
-
const rawName = ev.name ?? '';
|
|
119
|
-
const tool = stripMcpPrefix(rawName);
|
|
120
|
-
if (ev.id)
|
|
121
|
-
s.toolNameByUseId.set(ev.id, tool);
|
|
122
|
-
out.push({ kind: 'tool_use', tool, input: ev.input });
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
|
-
if (ev.type === 'tool_result') {
|
|
126
|
-
const isError = ev.is_error === true;
|
|
127
|
-
out.push({ kind: 'tool_result', isError });
|
|
128
|
-
return out;
|
|
129
|
-
}
|
|
130
|
-
if (ev.type === 'result') {
|
|
131
|
-
// result.stats may carry turns; prefer it over our running count.
|
|
132
|
-
const turns = ev.stats?.turns ?? ev.stats?.model?.turns ?? s.runningTurns;
|
|
133
|
-
const isError = ev.is_error === true || ev.error !== undefined && ev.error !== null;
|
|
134
|
-
if (isError)
|
|
135
|
-
s.sawErrorEvent = true;
|
|
136
|
-
out.push({
|
|
137
|
-
kind: 'session_end',
|
|
138
|
-
turns,
|
|
139
|
-
// costUsd intentionally undefined — gemini's stats block does not
|
|
140
|
-
// include $ figures.
|
|
141
|
-
isError,
|
|
142
|
-
summary: ev.response ?? s.lastAssistantText,
|
|
143
|
-
});
|
|
144
|
-
return out;
|
|
145
|
-
}
|
|
146
|
-
if (ev.type === 'error') {
|
|
147
|
-
s.sawErrorEvent = true;
|
|
148
|
-
const msg = ev.message ?? ev.error?.message ?? `[gemini] error`;
|
|
149
|
-
out.push({ kind: 'text', text: msg });
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
return [];
|
|
153
|
-
},
|
|
154
|
-
/**
|
|
155
|
-
* Gemini's `result` event already produces a session_end via parseEvent;
|
|
156
|
-
* this fallback is for the case where the child exits without emitting a
|
|
157
|
-
* `result` (e.g. crash, signal). Same shape as cursor.ts / qwen.ts.
|
|
158
|
-
*/
|
|
159
|
-
onStreamEnd(exitCode, state = {}) {
|
|
160
|
-
const s = geminiState(state);
|
|
161
|
-
return {
|
|
162
|
-
kind: 'session_end',
|
|
163
|
-
turns: s.runningTurns,
|
|
164
|
-
// costUsd intentionally undefined — see parseEvent note.
|
|
165
|
-
isError: s.sawErrorEvent || (exitCode != null && exitCode !== 0),
|
|
166
|
-
summary: s.lastAssistantText,
|
|
167
|
-
};
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
/**
|
|
171
|
-
* Test-only escape hatches, same pattern as cursor.ts / codex.ts.
|
|
172
|
-
*/
|
|
173
|
-
export const __testing = {
|
|
174
|
-
freshState: () => ({}),
|
|
175
|
-
resetCounters: (state) => resetGeminiCounters(geminiState(state)),
|
|
176
|
-
getState: (state) => {
|
|
177
|
-
const s = geminiState(state);
|
|
178
|
-
return {
|
|
179
|
-
runningTurns: s.runningTurns,
|
|
180
|
-
runningSessionId: s.runningSessionId,
|
|
181
|
-
runningModel: s.runningModel,
|
|
182
|
-
lastAssistantText: s.lastAssistantText,
|
|
183
|
-
sawErrorEvent: s.sawErrorEvent,
|
|
184
|
-
};
|
|
185
|
-
},
|
|
186
|
-
};
|
package/dist/agents/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC"}
|
package/dist/agents/index.js
DELETED
package/dist/agents/invoke.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { InvokeEvent, InvokeOptions } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Spawn an agent and yield normalized InvokeEvents as they arrive.
|
|
4
|
-
*
|
|
5
|
-
* Lifecycle: the generator owns the child process. Iterating to completion
|
|
6
|
-
* drains stdout; breaking early (e.g. WS disconnect) runs the finally block
|
|
7
|
-
* which closes readline and SIGTERMs the child, so we never leak orphan
|
|
8
|
-
* agent processes that would keep driving the browser (and burning tokens)
|
|
9
|
-
* after the user has navigated away.
|
|
10
|
-
*/
|
|
11
|
-
export declare function invokeAgent(opts: InvokeOptions): AsyncIterable<InvokeEvent>;
|
|
12
|
-
//# sourceMappingURL=invoke.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../src/agents/invoke.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAe,MAAM,YAAY,CAAC;AAE1E;;;;;;;;GAQG;AACH,wBAAuB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAAC,WAAW,CAAC,CA4ElF"}
|