@hover-dev/core 0.16.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.
Files changed (181) hide show
  1. package/README.md +26 -55
  2. package/dist/agentDirectives.d.ts +55 -0
  3. package/dist/agentDirectives.d.ts.map +1 -0
  4. package/dist/agentDirectives.js +276 -0
  5. package/dist/engine.d.ts +28 -0
  6. package/dist/engine.d.ts.map +1 -0
  7. package/dist/engine.js +27 -0
  8. package/dist/memory/businessMemory.d.ts +29 -0
  9. package/dist/memory/businessMemory.d.ts.map +1 -0
  10. package/dist/memory/businessMemory.js +125 -0
  11. package/dist/playwright/launchChrome.d.ts +18 -0
  12. package/dist/playwright/launchChrome.d.ts.map +1 -1
  13. package/dist/playwright/launchChrome.js +46 -3
  14. package/dist/qa/candidates.d.ts +32 -0
  15. package/dist/qa/candidates.d.ts.map +1 -0
  16. package/dist/qa/candidates.js +20 -0
  17. package/dist/qa/intensity.d.ts +33 -0
  18. package/dist/qa/intensity.d.ts.map +1 -0
  19. package/dist/qa/intensity.js +25 -0
  20. package/dist/qa/qaReport.d.ts +19 -0
  21. package/dist/qa/qaReport.d.ts.map +1 -0
  22. package/dist/qa/qaReport.js +50 -0
  23. package/dist/sessions/sessions.d.ts +125 -0
  24. package/dist/sessions/sessions.d.ts.map +1 -0
  25. package/dist/sessions/sessions.js +175 -0
  26. package/dist/specs/authFixture.d.ts +30 -0
  27. package/dist/specs/authFixture.d.ts.map +1 -0
  28. package/dist/specs/authFixture.js +145 -0
  29. package/dist/specs/detectSharedFlows.d.ts +1 -1
  30. package/dist/specs/detectSharedFlows.d.ts.map +1 -1
  31. package/dist/specs/detectSharedFlows.js +20 -21
  32. package/dist/specs/generatePageObject.d.ts +1 -1
  33. package/dist/specs/generatePageObject.d.ts.map +1 -1
  34. package/dist/specs/healPrompt.d.ts +19 -0
  35. package/dist/specs/healPrompt.d.ts.map +1 -0
  36. package/dist/specs/healPrompt.js +48 -0
  37. package/dist/specs/humanSteps.d.ts +4 -8
  38. package/dist/specs/humanSteps.d.ts.map +1 -1
  39. package/dist/specs/humanSteps.js +6 -1
  40. package/dist/specs/optimizeSpec.d.ts +15 -8
  41. package/dist/specs/optimizeSpec.d.ts.map +1 -1
  42. package/dist/specs/optimizeSpec.js +71 -41
  43. package/dist/specs/pageObjectManifest.d.ts +3 -1
  44. package/dist/specs/pageObjectManifest.d.ts.map +1 -1
  45. package/dist/specs/pageObjectManifest.js +24 -19
  46. package/dist/specs/replayGrounded.d.ts +45 -0
  47. package/dist/specs/replayGrounded.d.ts.map +1 -0
  48. package/dist/specs/replayGrounded.js +155 -0
  49. package/dist/specs/runFailures.d.ts +34 -0
  50. package/dist/specs/runFailures.d.ts.map +1 -0
  51. package/dist/specs/runFailures.js +93 -0
  52. package/dist/specs/seeds.d.ts +16 -15
  53. package/dist/specs/seeds.d.ts.map +1 -1
  54. package/dist/specs/seeds.js +86 -54
  55. package/dist/specs/sidecar.d.ts +34 -6
  56. package/dist/specs/sidecar.d.ts.map +1 -1
  57. package/dist/specs/sidecar.js +79 -9
  58. package/dist/specs/specStep.d.ts +21 -0
  59. package/dist/specs/specStep.d.ts.map +1 -0
  60. package/dist/specs/specStep.js +1 -0
  61. package/dist/specs/text.d.ts +8 -6
  62. package/dist/specs/text.d.ts.map +1 -1
  63. package/dist/specs/text.js +10 -7
  64. package/dist/specs/writeSpec.d.ts +62 -1
  65. package/dist/specs/writeSpec.d.ts.map +1 -1
  66. package/dist/specs/writeSpec.js +596 -21
  67. package/package.json +9 -29
  68. package/dist/agents/aider.d.ts +0 -16
  69. package/dist/agents/aider.d.ts.map +0 -1
  70. package/dist/agents/aider.js +0 -161
  71. package/dist/agents/argv.d.ts +0 -11
  72. package/dist/agents/argv.d.ts.map +0 -1
  73. package/dist/agents/argv.js +0 -23
  74. package/dist/agents/claude.d.ts +0 -3
  75. package/dist/agents/claude.d.ts.map +0 -1
  76. package/dist/agents/claude.js +0 -195
  77. package/dist/agents/codex.d.ts +0 -19
  78. package/dist/agents/codex.d.ts.map +0 -1
  79. package/dist/agents/codex.js +0 -216
  80. package/dist/agents/cursor.d.ts +0 -18
  81. package/dist/agents/cursor.d.ts.map +0 -1
  82. package/dist/agents/cursor.js +0 -220
  83. package/dist/agents/detect.d.ts +0 -46
  84. package/dist/agents/detect.d.ts.map +0 -1
  85. package/dist/agents/detect.js +0 -80
  86. package/dist/agents/gemini.d.ts +0 -17
  87. package/dist/agents/gemini.d.ts.map +0 -1
  88. package/dist/agents/gemini.js +0 -186
  89. package/dist/agents/index.d.ts +0 -6
  90. package/dist/agents/index.d.ts.map +0 -1
  91. package/dist/agents/index.js +0 -5
  92. package/dist/agents/invoke.d.ts +0 -12
  93. package/dist/agents/invoke.d.ts.map +0 -1
  94. package/dist/agents/invoke.js +0 -96
  95. package/dist/agents/qwen.d.ts +0 -17
  96. package/dist/agents/qwen.d.ts.map +0 -1
  97. package/dist/agents/qwen.js +0 -172
  98. package/dist/agents/registry.d.ts +0 -19
  99. package/dist/agents/registry.d.ts.map +0 -1
  100. package/dist/agents/registry.js +0 -34
  101. package/dist/agents/shared.d.ts +0 -28
  102. package/dist/agents/shared.d.ts.map +0 -1
  103. package/dist/agents/shared.js +0 -35
  104. package/dist/agents/types.d.ts +0 -186
  105. package/dist/agents/types.d.ts.map +0 -1
  106. package/dist/agents/types.js +0 -23
  107. package/dist/index.d.ts +0 -3
  108. package/dist/index.d.ts.map +0 -1
  109. package/dist/index.js +0 -2
  110. package/dist/mcp/sourceFence.d.ts +0 -23
  111. package/dist/mcp/sourceFence.d.ts.map +0 -1
  112. package/dist/mcp/sourceFence.js +0 -75
  113. package/dist/mcp/sourceServer.d.ts +0 -3
  114. package/dist/mcp/sourceServer.d.ts.map +0 -1
  115. package/dist/mcp/sourceServer.js +0 -116
  116. package/dist/playwright/cdpStatus.d.ts +0 -29
  117. package/dist/playwright/cdpStatus.d.ts.map +0 -1
  118. package/dist/playwright/cdpStatus.js +0 -119
  119. package/dist/playwright/preflight.d.ts +0 -31
  120. package/dist/playwright/preflight.d.ts.map +0 -1
  121. package/dist/playwright/preflight.js +0 -82
  122. package/dist/playwright/preflightCache.d.ts +0 -27
  123. package/dist/playwright/preflightCache.d.ts.map +0 -1
  124. package/dist/playwright/preflightCache.js +0 -21
  125. package/dist/playwright/raiseWindow.d.ts +0 -10
  126. package/dist/playwright/raiseWindow.d.ts.map +0 -1
  127. package/dist/playwright/raiseWindow.js +0 -158
  128. package/dist/playwright/resolveMcpConfig.d.ts +0 -55
  129. package/dist/playwright/resolveMcpConfig.d.ts.map +0 -1
  130. package/dist/playwright/resolveMcpConfig.js +0 -66
  131. package/dist/plugin-api.d.ts +0 -235
  132. package/dist/plugin-api.d.ts.map +0 -1
  133. package/dist/plugin-api.js +0 -52
  134. package/dist/runSession.d.ts +0 -42
  135. package/dist/runSession.d.ts.map +0 -1
  136. package/dist/runSession.js +0 -81
  137. package/dist/scripts/bench-multi-tab.d.ts +0 -2
  138. package/dist/scripts/bench-multi-tab.d.ts.map +0 -1
  139. package/dist/scripts/bench-multi-tab.js +0 -192
  140. package/dist/scripts/bench-ttfb.d.ts +0 -2
  141. package/dist/scripts/bench-ttfb.d.ts.map +0 -1
  142. package/dist/scripts/bench-ttfb.js +0 -127
  143. package/dist/scripts/start-chrome.d.ts +0 -3
  144. package/dist/scripts/start-chrome.d.ts.map +0 -1
  145. package/dist/scripts/start-chrome.js +0 -23
  146. package/dist/service/cdpHandlers.d.ts +0 -44
  147. package/dist/service/cdpHandlers.d.ts.map +0 -1
  148. package/dist/service/cdpHandlers.js +0 -85
  149. package/dist/service/cdpHint.d.ts +0 -48
  150. package/dist/service/cdpHint.d.ts.map +0 -1
  151. package/dist/service/cdpHint.js +0 -216
  152. package/dist/service/conventions.d.ts +0 -8
  153. package/dist/service/conventions.d.ts.map +0 -1
  154. package/dist/service/conventions.js +0 -42
  155. package/dist/service/saveHandlers.d.ts +0 -52
  156. package/dist/service/saveHandlers.d.ts.map +0 -1
  157. package/dist/service/saveHandlers.js +0 -75
  158. package/dist/service/types.d.ts +0 -58
  159. package/dist/service/types.d.ts.map +0 -1
  160. package/dist/service/types.js +0 -26
  161. package/dist/service.d.ts +0 -50
  162. package/dist/service.d.ts.map +0 -1
  163. package/dist/service.js +0 -1065
  164. package/dist/skills/writeSkill.d.ts +0 -27
  165. package/dist/skills/writeSkill.d.ts.map +0 -1
  166. package/dist/skills/writeSkill.js +0 -13
  167. package/dist/specs/extractPageObjects.d.ts +0 -18
  168. package/dist/specs/extractPageObjects.d.ts.map +0 -1
  169. package/dist/specs/extractPageObjects.js +0 -98
  170. package/dist/specs/listSpecs.d.ts +0 -52
  171. package/dist/specs/listSpecs.d.ts.map +0 -1
  172. package/dist/specs/listSpecs.js +0 -139
  173. package/dist/specs/optimizationSuggestion.d.ts +0 -26
  174. package/dist/specs/optimizationSuggestion.d.ts.map +0 -1
  175. package/dist/specs/optimizationSuggestion.js +0 -28
  176. package/dist/specs/optimizeSpecWithAgent.d.ts +0 -11
  177. package/dist/specs/optimizeSpecWithAgent.d.ts.map +0 -1
  178. package/dist/specs/optimizeSpecWithAgent.js +0 -40
  179. package/dist/specs/writeCaseCsv.d.ts +0 -28
  180. package/dist/specs/writeCaseCsv.d.ts.map +0 -1
  181. package/dist/specs/writeCaseCsv.js +0 -134
@@ -1,216 +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.runningSessionId = undefined;
36
- state.lastAgentMessage = undefined;
37
- state.sawErrorEvent = false;
38
- state.itemTypeById = new Map();
39
- }
40
- return state;
41
- }
42
- function resetCodexCounters(s) {
43
- s.runningCost = 0;
44
- s.runningTurns = 0;
45
- s.runningSessionId = undefined;
46
- s.lastAgentMessage = undefined;
47
- s.sawErrorEvent = false;
48
- s.itemTypeById.clear();
49
- }
50
- /** Cap surfaced as a constraint in the system prompt — codex has no CLI flag. */
51
- const CODEX_DEVELOPER_INSTRUCTIONS = [
52
- 'You are operating in Hover, a browser-testing tool.',
53
- 'Use ONLY the MCP playwright tools (prefixed `mcp__playwright__` / `mcp__hover-playwright__`) to drive the browser.',
54
- 'Do NOT call shell, file-edit, web-search, or any other built-in tool.',
55
- 'Do NOT navigate to a URL the user is already on; check the page state via `browser_snapshot` first.',
56
- 'When the task is complete, emit a short agent_message summary and stop.',
57
- ].join(' ');
58
- export const codexAgent = {
59
- id: 'codex',
60
- binName: 'codex',
61
- protocol: 'argv',
62
- streamFormat: 'json-lines',
63
- sandboxStrength: 'soft',
64
- apiKeyEnv: 'OPENAI_API_KEY',
65
- display: {
66
- label: 'OpenAI Codex',
67
- tagline: 'OpenAI — soft sandbox (no built-in tool deny-list)',
68
- homepage: 'https://developers.openai.com/codex',
69
- installHint: 'npm install -g @openai/codex',
70
- },
71
- buildArgs(opts) {
72
- const args = ['exec'];
73
- // Resume must come BEFORE the prompt positional. `codex exec resume <id>
74
- // [prompt]` is the documented shape.
75
- if (opts.sessionId) {
76
- args.push('resume', opts.sessionId);
77
- }
78
- args.push(opts.prompt);
79
- // JSONL streaming output.
80
- args.push('--json');
81
- // Never prompt for approval in headless mode.
82
- args.push('--ask-for-approval', 'never');
83
- // Soft sandbox: prevent shell side-effects on disk / network even when
84
- // the agent tries to call its built-in shell. read-only is the strictest
85
- // documented level.
86
- args.push('--sandbox', 'read-only');
87
- if (opts.model) {
88
- args.push('--model', opts.model);
89
- }
90
- // System-prompt injection. Codex has no --append-system-prompt; we route
91
- // through `-c developer_instructions='...'`. Concatenate the standing
92
- // Hover-mode instructions with whatever the caller passes (e.g. "user is
93
- // already on http://localhost:5173/").
94
- const sysPrompt = opts.appendSystemPrompt && opts.appendSystemPrompt.trim().length > 0
95
- ? `${CODEX_DEVELOPER_INSTRUCTIONS} ${opts.appendSystemPrompt}`
96
- : CODEX_DEVELOPER_INSTRUCTIONS;
97
- args.push('-c', `developer_instructions=${JSON.stringify(sysPrompt)}`);
98
- // MCP servers are configured in ~/.codex/config.toml at install time,
99
- // not per-invocation. If the user passed an mcpConfig path, we don't
100
- // have a way to forward it to codex — log a warning to stderr from the
101
- // invoker so the user knows. (See invoke.ts wiring.)
102
- // No equivalent for --max-budget-usd or --allowedTools.
103
- return args;
104
- },
105
- parseEvent(line, state = {}) {
106
- if (!line.trim())
107
- return [];
108
- let ev;
109
- try {
110
- ev = JSON.parse(line);
111
- }
112
- catch {
113
- return [{ kind: 'raw', line }];
114
- }
115
- const s = codexState(state);
116
- const out = [];
117
- if (ev.type === 'thread.started') {
118
- resetCodexCounters(s);
119
- if (ev.thread_id) {
120
- s.runningSessionId = ev.thread_id;
121
- out.push({ kind: 'session_start', sessionId: ev.thread_id, model: ev.model });
122
- }
123
- return out;
124
- }
125
- if (ev.type === 'item.started' && ev.item) {
126
- const it = ev.item;
127
- if (it.id && it.type)
128
- s.itemTypeById.set(it.id, it.type);
129
- if (it.type === 'mcp_tool_call') {
130
- // The exact field names aren't published. Read defensively: prefer
131
- // `name`, fall back to `tool`. Same for input.
132
- const rawName = it.name ?? it.tool ?? '';
133
- const tool = stripMcpPrefix(rawName);
134
- out.push({ kind: 'tool_use', tool, input: it.input ?? it.arguments, costUsdSnapshot: s.runningCost });
135
- }
136
- else if (it.type === 'command_execution') {
137
- // We DISCOURAGED this in developer_instructions but the agent can
138
- // still try. Surface it so the user sees it happen.
139
- out.push({ kind: 'tool_use', tool: 'shell', input: { command: it.command }, costUsdSnapshot: s.runningCost });
140
- }
141
- return out;
142
- }
143
- if (ev.type === 'item.completed' && ev.item) {
144
- const it = ev.item;
145
- const recordedType = (it.id && s.itemTypeById.get(it.id)) || it.type;
146
- if (recordedType === 'agent_message') {
147
- const text = it.text?.trim();
148
- if (text) {
149
- s.lastAgentMessage = text;
150
- out.push({ kind: 'text', text });
151
- }
152
- }
153
- else if (recordedType === 'mcp_tool_call' || recordedType === 'command_execution') {
154
- const isError = it.is_error === true ||
155
- (typeof it.status === 'string' && /error|fail/i.test(it.status));
156
- out.push({ kind: 'tool_result', isError });
157
- }
158
- return out;
159
- }
160
- if (ev.type === 'turn.completed') {
161
- s.runningTurns += 1;
162
- if (ev.usage) {
163
- // The parser has no access to the invocation's --model, so we let
164
- // estimateCostUsd fall back to its fixed default tier. Cost is a
165
- // high-water "should I hit Stop now" signal, not an invoice.
166
- s.runningCost += estimateCostUsd(undefined, ev.usage);
167
- }
168
- out.push({ kind: 'usage', costUsd: s.runningCost, turns: s.runningTurns });
169
- return out;
170
- }
171
- // Codex emits various error envelopes; we conservatively match anything
172
- // whose `type` contains 'error' or carries a top-level message string.
173
- if (ev.type && /error/i.test(ev.type)) {
174
- s.sawErrorEvent = true;
175
- if (ev.message) {
176
- out.push({ kind: 'text', text: `[codex] ${ev.message}` });
177
- }
178
- return out;
179
- }
180
- return [];
181
- },
182
- /**
183
- * Codex doesn't emit a `session_end` line — the child process simply
184
- * exits after the final `turn.completed`. We synthesize the terminator
185
- * here so the widget sees the same shape it sees from claude.
186
- */
187
- onStreamEnd(exitCode, state = {}) {
188
- const s = codexState(state);
189
- return {
190
- kind: 'session_end',
191
- turns: s.runningTurns,
192
- costUsd: s.runningCost,
193
- isError: s.sawErrorEvent || (exitCode != null && exitCode !== 0),
194
- summary: s.lastAgentMessage,
195
- };
196
- },
197
- };
198
- /**
199
- * Test-only escape hatches. Tests pass a state object in and get the
200
- * accumulated counters back — same shape as the parser sees during a real
201
- * invocation, just driven by the test instead of by invokeAgent.
202
- */
203
- export const __testing = {
204
- freshState: () => ({}),
205
- resetCounters: (state) => resetCodexCounters(codexState(state)),
206
- getState: (state) => {
207
- const s = codexState(state);
208
- return {
209
- runningCost: s.runningCost,
210
- runningTurns: s.runningTurns,
211
- runningSessionId: s.runningSessionId,
212
- lastAgentMessage: s.lastAgentMessage,
213
- sawErrorEvent: s.sawErrorEvent,
214
- };
215
- },
216
- };
@@ -1,18 +0,0 @@
1
- import type { AgentDescriptor, ParserState } from './types.js';
2
- export declare const cursorAgent: AgentDescriptor;
3
- /**
4
- * Test-only escape hatches. Same pattern as codex.ts so the tests can drive
5
- * the parser without going through invokeAgent.
6
- */
7
- export declare const __testing: {
8
- freshState: () => ParserState;
9
- resetCounters: (state: ParserState) => void;
10
- getState: (state: ParserState) => {
11
- runningTurns: number;
12
- runningSessionId: string | undefined;
13
- runningModel: string | undefined;
14
- lastAssistantText: string | undefined;
15
- sawErrorEvent: boolean;
16
- };
17
- };
18
- //# sourceMappingURL=cursor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AA2K3F,eAAO,MAAM,WAAW,EAAE,eAuJzB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS;sBACJ,WAAW;2BACJ,WAAW;sBAChB,WAAW;;;;;;;CAU9B,CAAC"}
@@ -1,220 +0,0 @@
1
- import { HOVER_PROMPT_PREFACE, stripMcpPrefix } from './shared.js';
2
- function cursorState(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.toolNameByCallId = new Map();
10
- }
11
- return state;
12
- }
13
- function resetCursorCounters(s) {
14
- s.runningTurns = 0;
15
- s.runningSessionId = undefined;
16
- s.runningModel = undefined;
17
- s.lastAssistantText = undefined;
18
- s.sawErrorEvent = false;
19
- s.toolNameByCallId.clear();
20
- }
21
- /**
22
- * Best-effort extraction of a tool's name from the `tool_call` envelope.
23
- * Cursor's stream-json wraps each kind in a sub-object keyed by name
24
- * (`shellToolCall`, `mcpToolCall`, etc.) but doesn't publish a stable
25
- * `name` field at the top level. We:
26
- * 1. Look for the first key that ends in `ToolCall` → strip the suffix.
27
- * `shellToolCall` → `shell`, `mcpToolCall` → `mcp`.
28
- * 2. If the sub-object carries a `tool` / `name` field, prefer that
29
- * (mcp calls embed the playwright tool name there).
30
- * 3. Strip the `mcp__playwright__` / `mcp__hover-playwright__` prefix to
31
- * match the normalised tool names claude / codex emit.
32
- */
33
- function extractToolName(tc) {
34
- if (!tc)
35
- return { tool: 'unknown', input: undefined };
36
- const wrapperKey = Object.keys(tc).find(k => k.endsWith('ToolCall'));
37
- const inner = (wrapperKey ? tc[wrapperKey] : undefined) ?? undefined;
38
- // Prefer the inner sub-tool name if it exists (MCP case).
39
- const innerName = (inner && typeof inner === 'object' && 'tool' in inner && typeof inner.tool === 'string' && inner.tool) ||
40
- (inner && typeof inner === 'object' && 'name' in inner && typeof inner.name === 'string' && inner.name) ||
41
- null;
42
- const kindFromKey = wrapperKey ? wrapperKey.replace(/ToolCall$/, '') : 'unknown';
43
- const rawName = innerName || kindFromKey;
44
- const tool = stripMcpPrefix(rawName);
45
- const input = (inner && typeof inner === 'object' && 'input' in inner && inner.input) ||
46
- (inner && typeof inner === 'object' && 'arguments' in inner && inner.arguments) ||
47
- (inner && typeof inner === 'object' && 'args' in inner && inner.args) ||
48
- inner;
49
- return { tool, input };
50
- }
51
- function detectToolError(tc) {
52
- if (!tc || typeof tc !== 'object')
53
- return false;
54
- const wrapperKey = Object.keys(tc).find(k => k.endsWith('ToolCall'));
55
- const inner = wrapperKey ? tc[wrapperKey] : undefined;
56
- if (!inner)
57
- return false;
58
- if (inner.is_error === true)
59
- return true;
60
- if (typeof inner.status === 'string' && /error|fail/i.test(inner.status))
61
- return true;
62
- return false;
63
- }
64
- // The closest analogue Cursor has to claude's --append-system-prompt or
65
- // codex's developer_instructions is prepending the standing HOVER-mode
66
- // preface (HOVER_PROMPT_PREFACE, from shared.ts) to the user prompt so the
67
- // agent sees it as the leading instruction. There is no CLI flag for it.
68
- export const cursorAgent = {
69
- id: 'cursor',
70
- binName: 'cursor-agent',
71
- protocol: 'argv',
72
- streamFormat: 'json-lines',
73
- sandboxStrength: 'soft',
74
- display: {
75
- label: 'Cursor',
76
- tagline: 'Cursor — soft sandbox (no built-in tool deny-list)',
77
- homepage: 'https://cursor.com/docs/cli/overview',
78
- installHint: 'curl https://cursor.com/install -fsS | bash',
79
- },
80
- buildArgs(opts) {
81
- // The HOVER-mode preface plus any caller-supplied appendSystemPrompt
82
- // gets prepended to the prompt. This is the closest functional analogue
83
- // Cursor has to claude's --append-system-prompt / codex's
84
- // developer_instructions, because Cursor exposes no CLI flag for it.
85
- const preface = opts.appendSystemPrompt && opts.appendSystemPrompt.trim().length > 0
86
- ? `${HOVER_PROMPT_PREFACE} ${opts.appendSystemPrompt}`
87
- : HOVER_PROMPT_PREFACE;
88
- const finalPrompt = `${preface}\n\n${opts.prompt}`;
89
- const args = ['-p', finalPrompt];
90
- // NDJSON streaming output.
91
- args.push('--output-format', 'stream-json');
92
- // Non-interactive: auto-approve commands and MCP tools so the run doesn't
93
- // hang waiting for permission. Cursor calls this --force / --yolo.
94
- args.push('--force');
95
- if (opts.model) {
96
- args.push('--model', opts.model);
97
- }
98
- if (opts.sessionId) {
99
- // Cursor's --resume accepts the chat_id. Empty / no-arg --resume
100
- // resumes the latest, which is NOT what we want here.
101
- args.push('--resume', opts.sessionId);
102
- }
103
- // MCP servers are configured in ~/.cursor/mcp.json (or repo-local
104
- // .cursor/mcp.json) at install time, not per-invocation. Cursor has no
105
- // equivalent of claude's --mcp-config. If the caller passed opts.mcpConfig
106
- // we don't have a way to forward it; service.ts logs a one-time warning.
107
- // No equivalent for --max-budget-usd or --allowedTools / --disallowedTools.
108
- return args;
109
- },
110
- parseEvent(line, state = {}) {
111
- if (!line.trim())
112
- return [];
113
- let ev;
114
- try {
115
- ev = JSON.parse(line);
116
- }
117
- catch {
118
- return [{ kind: 'raw', line }];
119
- }
120
- const s = cursorState(state);
121
- const out = [];
122
- if (ev.type === 'system' && ev.subtype === 'init') {
123
- resetCursorCounters(s);
124
- s.runningModel = ev.model;
125
- if (ev.session_id) {
126
- s.runningSessionId = ev.session_id;
127
- out.push({ kind: 'session_start', sessionId: ev.session_id, model: ev.model });
128
- }
129
- return out;
130
- }
131
- if (ev.type === 'tool_call' && ev.subtype === 'started') {
132
- const { tool, input } = extractToolName(ev.tool_call);
133
- if (ev.call_id)
134
- s.toolNameByCallId.set(ev.call_id, tool);
135
- out.push({ kind: 'tool_use', tool, input });
136
- return out;
137
- }
138
- if (ev.type === 'tool_call' && ev.subtype === 'completed') {
139
- const isError = detectToolError(ev.tool_call);
140
- out.push({ kind: 'tool_result', isError });
141
- return out;
142
- }
143
- if (ev.type === 'assistant') {
144
- s.runningTurns += 1;
145
- // Emit a usage event so the widget can advance its turn counter even
146
- // though Cursor gives us no token / $ data. costUsd intentionally
147
- // omitted — we don't fabricate a number.
148
- out.push({ kind: 'usage', turns: s.runningTurns });
149
- for (const block of ev.message?.content ?? []) {
150
- if (block.type === 'text') {
151
- const text = block.text?.trim();
152
- if (text) {
153
- s.lastAssistantText = text;
154
- out.push({ kind: 'text', text });
155
- }
156
- }
157
- }
158
- return out;
159
- }
160
- if (ev.type === 'result') {
161
- // Cursor's result event IS the session_end. We forward it directly so
162
- // onStreamEnd doesn't need to synthesize.
163
- const isError = ev.is_error === true ||
164
- (typeof ev.subtype === 'string' && /error|fail/i.test(ev.subtype));
165
- if (isError)
166
- s.sawErrorEvent = true;
167
- out.push({
168
- kind: 'session_end',
169
- turns: s.runningTurns,
170
- // costUsd intentionally undefined — Cursor doesn't publish $ or tokens.
171
- isError,
172
- summary: ev.result ?? s.lastAssistantText,
173
- });
174
- return out;
175
- }
176
- // Cursor sometimes emits error envelopes mid-stream; surface them as
177
- // text so the widget shows the problem instead of silently hanging.
178
- if (ev.type && /error/i.test(ev.type)) {
179
- s.sawErrorEvent = true;
180
- // No documented `message` field on these — best-effort.
181
- const msg = ev.result ?? `[cursor] ${ev.type}`;
182
- out.push({ kind: 'text', text: msg });
183
- return out;
184
- }
185
- return [];
186
- },
187
- /**
188
- * Cursor's `result` event already produces a session_end via parseEvent;
189
- * this fallback is for the case where the child exits without emitting a
190
- * `result` (e.g. crash, signal). Mirrors codex.ts's shape.
191
- */
192
- onStreamEnd(exitCode, state = {}) {
193
- const s = cursorState(state);
194
- return {
195
- kind: 'session_end',
196
- turns: s.runningTurns,
197
- // costUsd intentionally undefined — see parseEvent note.
198
- isError: s.sawErrorEvent || (exitCode != null && exitCode !== 0),
199
- summary: s.lastAssistantText,
200
- };
201
- },
202
- };
203
- /**
204
- * Test-only escape hatches. Same pattern as codex.ts so the tests can drive
205
- * the parser without going through invokeAgent.
206
- */
207
- export const __testing = {
208
- freshState: () => ({}),
209
- resetCounters: (state) => resetCursorCounters(cursorState(state)),
210
- getState: (state) => {
211
- const s = cursorState(state);
212
- return {
213
- runningTurns: s.runningTurns,
214
- runningSessionId: s.runningSessionId,
215
- runningModel: s.runningModel,
216
- lastAssistantText: s.lastAssistantText,
217
- sawErrorEvent: s.sawErrorEvent,
218
- };
219
- },
220
- };
@@ -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"}