@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,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
- };
@@ -1,6 +0,0 @@
1
- export * from './types.js';
2
- export * from './detect.js';
3
- export * from './invoke.js';
4
- export * from './registry.js';
5
- export * from './argv.js';
6
- //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -1,5 +0,0 @@
1
- export * from './types.js';
2
- export * from './detect.js';
3
- export * from './invoke.js';
4
- export * from './registry.js';
5
- export * from './argv.js';
@@ -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,CA+ElF"}
@@ -1,96 +0,0 @@
1
- // cross-spawn is a drop-in for child_process.spawn that fixes Windows behaviour
2
- // around `.cmd`/`.bat` shims (e.g. npm-installed `claude.cmd`). The return type
3
- // is identical to node:child_process so call sites are unchanged.
4
- import spawn from 'cross-spawn';
5
- import { createInterface } from 'node:readline';
6
- import { buildArgv } from './argv.js';
7
- import { resolveBinForAgent } from './detect.js';
8
- import { getAgent } from './registry.js';
9
- import { AgentNotInstalledError, UnsupportedAgentProtocolError, } from './types.js';
10
- /**
11
- * Spawn an agent and yield normalized InvokeEvents as they arrive.
12
- *
13
- * Lifecycle: the generator owns the child process. Iterating to completion
14
- * drains stdout; breaking early (e.g. WS disconnect) runs the finally block
15
- * which closes readline and SIGTERMs the child, so we never leak orphan
16
- * agent processes that would keep driving the browser (and burning tokens)
17
- * after the user has navigated away.
18
- */
19
- export async function* invokeAgent(opts) {
20
- const descriptor = getAgent(opts.agentId);
21
- if (!descriptor) {
22
- throw new UnsupportedAgentProtocolError(`Unknown agent: ${opts.agentId}`);
23
- }
24
- const bin = await resolveBinForAgent(descriptor);
25
- if (!bin)
26
- throw new AgentNotInstalledError(opts.agentId);
27
- const argv = buildArgv(descriptor, opts);
28
- const usesStdinPrompt = descriptor.protocol === 'stdin';
29
- const child = spawn(bin, argv, {
30
- stdio: [usesStdinPrompt ? 'pipe' : 'ignore', 'pipe', 'inherit'],
31
- cwd: opts.cwd,
32
- // Clear CLAUDECODE so spawning `claude` from inside a Claude Code session
33
- // doesn't trip the nested-session guard. Harmless for other agents.
34
- // If the caller supplied an API key and the descriptor names a key env var,
35
- // inject it so the CLI runs on the key instead of a logged-in subscription.
36
- // The key lives only in this child's env — never logged, never persisted.
37
- env: {
38
- ...process.env,
39
- CLAUDECODE: '',
40
- ...(opts.apiKey && descriptor.apiKeyEnv
41
- ? { [descriptor.apiKeyEnv]: opts.apiKey }
42
- : {}),
43
- },
44
- });
45
- const onAbort = () => {
46
- if (!child.killed)
47
- child.kill('SIGTERM');
48
- };
49
- if (opts.signal) {
50
- if (opts.signal.aborted)
51
- onAbort();
52
- else
53
- opts.signal.addEventListener('abort', onAbort, { once: true });
54
- }
55
- if (usesStdinPrompt && child.stdin) {
56
- child.stdin.write(opts.prompt);
57
- child.stdin.end();
58
- }
59
- const rl = createInterface({ input: child.stdout });
60
- const exitPromise = new Promise(res => child.on('exit', c => res(c ?? -1)));
61
- const state = {};
62
- let sawSessionEnd = false;
63
- try {
64
- for await (const line of rl) {
65
- for (const ev of descriptor.parseEvent(line, state)) {
66
- if (ev.kind === 'session_end')
67
- sawSessionEnd = true;
68
- yield ev;
69
- }
70
- }
71
- const code = await exitPromise;
72
- if (!sawSessionEnd && !opts.signal?.aborted) {
73
- // Give the descriptor a chance to synthesize its own terminator from
74
- // accumulated state (codex does this — its stream never emits a
75
- // session_end). Falls back to a generic error session_end if the
76
- // descriptor declines and the child exited non-zero.
77
- const synthetic = descriptor.onStreamEnd?.(code, state);
78
- if (synthetic) {
79
- yield synthetic;
80
- }
81
- else if (code !== 0) {
82
- yield {
83
- kind: 'session_end',
84
- isError: true,
85
- summary: `agent exited with code ${code}`,
86
- };
87
- }
88
- }
89
- }
90
- finally {
91
- rl.close();
92
- if (!child.killed)
93
- child.kill('SIGTERM');
94
- opts.signal?.removeEventListener('abort', onAbort);
95
- }
96
- }
@@ -1,17 +0,0 @@
1
- import type { AgentDescriptor, ParserState } from './types.js';
2
- export declare const qwenAgent: 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=qwen.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"qwen.d.ts","sourceRoot":"","sources":["../../src/agents/qwen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AAoJ3F,eAAO,MAAM,SAAS,EAAE,eAqJvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS;sBACJ,WAAW;2BACJ,WAAW;sBAChB,WAAW;;;;;;;CAU9B,CAAC"}
@@ -1,172 +0,0 @@
1
- import { HOVER_PROMPT_PREFACE, stripMcpPrefix } from './shared.js';
2
- function qwenState(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 resetQwenCounters(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
- export const qwenAgent = {
22
- id: 'qwen',
23
- binName: 'qwen',
24
- protocol: 'argv',
25
- streamFormat: 'json-lines',
26
- sandboxStrength: 'soft',
27
- display: {
28
- label: 'Qwen Code',
29
- tagline: 'Qwen Code — soft sandbox (no built-in tool deny-list)',
30
- homepage: 'https://github.com/QwenLM/qwen-code',
31
- installHint: 'npm install -g @qwen-code/qwen-code@latest',
32
- },
33
- buildArgs(opts) {
34
- const args = ['-p', opts.prompt];
35
- // NDJSON streaming output.
36
- args.push('--output-format', 'stream-json');
37
- // Auto-approve all tool calls so the run doesn't hang. The newer
38
- // canonical form is --approval-mode=yolo; --yolo is deprecated but
39
- // still works in 2026-05. We use the modern form.
40
- args.push('--approval-mode', 'yolo');
41
- if (opts.model) {
42
- args.push('--model', opts.model);
43
- }
44
- if (opts.sessionId) {
45
- // --resume <sessionId> is the documented headless form. --continue
46
- // (no arg) picks the most recent — NOT what we want when a specific
47
- // session was passed.
48
- args.push('--resume', opts.sessionId);
49
- }
50
- // Qwen has a real --append-system-prompt flag — use it instead of
51
- // prepending to the user prompt. Concatenate the standing Hover-mode
52
- // preface with whatever the caller appended.
53
- const sysPrompt = opts.appendSystemPrompt && opts.appendSystemPrompt.trim().length > 0
54
- ? `${HOVER_PROMPT_PREFACE} ${opts.appendSystemPrompt}`
55
- : HOVER_PROMPT_PREFACE;
56
- args.push('--append-system-prompt', sysPrompt);
57
- // MCP servers configured in ~/.qwen/settings.json — no per-invocation
58
- // --mcp-config equivalent. Same constraint as cursor / codex.
59
- // No equivalent for --max-budget-usd or --allowedTools / --disallowedTools.
60
- return args;
61
- },
62
- parseEvent(line, state = {}) {
63
- if (!line.trim())
64
- return [];
65
- let ev;
66
- try {
67
- ev = JSON.parse(line);
68
- }
69
- catch {
70
- return [{ kind: 'raw', line }];
71
- }
72
- const s = qwenState(state);
73
- const out = [];
74
- if (ev.type === 'system' && ev.subtype === 'session_start') {
75
- resetQwenCounters(s);
76
- s.runningModel = ev.model;
77
- if (ev.session_id) {
78
- s.runningSessionId = ev.session_id;
79
- out.push({ kind: 'session_start', sessionId: ev.session_id, model: ev.model });
80
- }
81
- return out;
82
- }
83
- if (ev.type === 'assistant' && ev.message) {
84
- s.runningTurns += 1;
85
- // Emit a usage event so the widget can advance its turn counter.
86
- // costUsd intentionally omitted — qwen doesn't publish $ in stream.
87
- out.push({ kind: 'usage', turns: s.runningTurns });
88
- for (const block of ev.message.content ?? []) {
89
- if (block.type === 'text') {
90
- const text = block.text?.trim();
91
- if (text) {
92
- s.lastAssistantText = text;
93
- out.push({ kind: 'text', text });
94
- }
95
- }
96
- else if (block.type === 'tool_use') {
97
- const rawName = block.name ?? '';
98
- const tool = stripMcpPrefix(rawName);
99
- if (block.id)
100
- s.toolNameByUseId.set(block.id, tool);
101
- out.push({ kind: 'tool_use', tool, input: block.input });
102
- }
103
- }
104
- return out;
105
- }
106
- // tool_result blocks are wrapped in `user` messages (Anthropic Messages
107
- // convention). We surface them as tool_result events.
108
- if (ev.type === 'user' && ev.message) {
109
- for (const block of ev.message.content ?? []) {
110
- if (block.type === 'tool_result') {
111
- const isError = block.is_error === true;
112
- out.push({ kind: 'tool_result', isError });
113
- }
114
- }
115
- return out;
116
- }
117
- if (ev.type === 'result') {
118
- const isError = ev.is_error === true ||
119
- (typeof ev.subtype === 'string' && /error|fail/i.test(ev.subtype));
120
- if (isError)
121
- s.sawErrorEvent = true;
122
- out.push({
123
- kind: 'session_end',
124
- turns: s.runningTurns,
125
- // costUsd intentionally undefined — qwen doesn't publish $.
126
- isError,
127
- summary: ev.result ?? s.lastAssistantText,
128
- });
129
- return out;
130
- }
131
- // Qwen emits various error envelopes mid-stream; surface them as text.
132
- if (ev.type && /error/i.test(ev.type)) {
133
- s.sawErrorEvent = true;
134
- const msg = ev.error?.message ?? ev.text ?? ev.result ?? `[qwen] ${ev.type}`;
135
- out.push({ kind: 'text', text: msg });
136
- return out;
137
- }
138
- return [];
139
- },
140
- /**
141
- * Qwen's `result` event already produces a session_end via parseEvent;
142
- * this fallback is for the case where the child exits without emitting a
143
- * `result` (e.g. crash, signal). Mirrors codex.ts / cursor.ts shape.
144
- */
145
- onStreamEnd(exitCode, state = {}) {
146
- const s = qwenState(state);
147
- return {
148
- kind: 'session_end',
149
- turns: s.runningTurns,
150
- // costUsd intentionally undefined — see parseEvent note.
151
- isError: s.sawErrorEvent || (exitCode != null && exitCode !== 0),
152
- summary: s.lastAssistantText,
153
- };
154
- },
155
- };
156
- /**
157
- * Test-only escape hatches, same pattern as cursor.ts / codex.ts.
158
- */
159
- export const __testing = {
160
- freshState: () => ({}),
161
- resetCounters: (state) => resetQwenCounters(qwenState(state)),
162
- getState: (state) => {
163
- const s = qwenState(state);
164
- return {
165
- runningTurns: s.runningTurns,
166
- runningSessionId: s.runningSessionId,
167
- runningModel: s.runningModel,
168
- lastAssistantText: s.lastAssistantText,
169
- sawErrorEvent: s.sawErrorEvent,
170
- };
171
- },
172
- };
@@ -1,19 +0,0 @@
1
- import type { AgentDescriptor } from './types.js';
2
- /**
3
- * Registry of agents Hover can drive.
4
- *
5
- * To add support for another agent (e.g. cline, continue, kilo), implement
6
- * its AgentDescriptor in its own file and register it here. The rest of the
7
- * system — detect, argv, invoke, service, widget — works without further
8
- * changes.
9
- *
10
- * Insertion order is the order shown in the widget's agent dropdown, so put
11
- * the recommended primary first. The two hard-sandbox / first-party agents
12
- * (claude, codex) lead; the soft-sandbox third-party agents follow in the
13
- * order they were added.
14
- */
15
- export declare const AGENTS: Record<string, AgentDescriptor>;
16
- export declare function getAgent(id: string): AgentDescriptor | undefined;
17
- /** Stable, insertion-ordered list of all registered agents. */
18
- export declare function listAgents(): AgentDescriptor[];
19
- //# sourceMappingURL=registry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/agents/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAQlD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAOlD,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEhE;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,IAAI,eAAe,EAAE,CAE9C"}
@@ -1,34 +0,0 @@
1
- import { claudeAgent } from './claude.js';
2
- import { codexAgent } from './codex.js';
3
- import { cursorAgent } from './cursor.js';
4
- import { aiderAgent } from './aider.js';
5
- import { geminiAgent } from './gemini.js';
6
- import { qwenAgent } from './qwen.js';
7
- /**
8
- * Registry of agents Hover can drive.
9
- *
10
- * To add support for another agent (e.g. cline, continue, kilo), implement
11
- * its AgentDescriptor in its own file and register it here. The rest of the
12
- * system — detect, argv, invoke, service, widget — works without further
13
- * changes.
14
- *
15
- * Insertion order is the order shown in the widget's agent dropdown, so put
16
- * the recommended primary first. The two hard-sandbox / first-party agents
17
- * (claude, codex) lead; the soft-sandbox third-party agents follow in the
18
- * order they were added.
19
- */
20
- export const AGENTS = {
21
- [claudeAgent.id]: claudeAgent,
22
- [codexAgent.id]: codexAgent,
23
- [cursorAgent.id]: cursorAgent,
24
- [aiderAgent.id]: aiderAgent,
25
- [geminiAgent.id]: geminiAgent,
26
- [qwenAgent.id]: qwenAgent,
27
- };
28
- export function getAgent(id) {
29
- return AGENTS[id];
30
- }
31
- /** Stable, insertion-ordered list of all registered agents. */
32
- export function listAgents() {
33
- return Object.values(AGENTS);
34
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * Cross-agent helpers shared by the soft-sandbox descriptors
3
- * (codex / cursor / gemini / qwen / aider).
4
- *
5
- * These agents all need the same two things:
6
- * 1. A standing "HOVER-mode" instruction preface that tells the agent to
7
- * drive the browser via the Playwright MCP tools only and not to touch
8
- * its built-in shell / file-edit tools. Each agent injects it through a
9
- * different channel (cursor / gemini / aider prepend it to the prompt,
10
- * qwen passes it via --append-system-prompt, codex via
11
- * `-c developer_instructions=`), so this module owns only the *text*, not
12
- * the injection.
13
- * 2. Normalising the `mcp__playwright__` / `mcp__hover-playwright__` prefix
14
- * off a raw tool name so the emitted tool names line up across agents.
15
- *
16
- * codex deliberately does NOT use HOVER_PROMPT_PREFACE — it keeps its own
17
- * wording ("Do NOT call …", "emit a short agent_message summary …") that is
18
- * tuned to codex's event vocabulary. See codex.ts.
19
- */
20
- /**
21
- * The standing HOVER-mode instruction shared by cursor / gemini / qwen / aider.
22
- * codex carries a near-identical but intentionally different variant inline.
23
- */
24
- export declare const HOVER_PROMPT_PREFACE: string;
25
- /** Strip the `mcp__playwright__` / `mcp__hover-playwright__` prefix so tool
26
- * names match the normalised names every agent emits. */
27
- export declare function stripMcpPrefix(raw: string): string;
28
- //# sourceMappingURL=shared.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/agents/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAMtB,CAAC;AAEZ;0DAC0D;AAC1D,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -1,35 +0,0 @@
1
- /**
2
- * Cross-agent helpers shared by the soft-sandbox descriptors
3
- * (codex / cursor / gemini / qwen / aider).
4
- *
5
- * These agents all need the same two things:
6
- * 1. A standing "HOVER-mode" instruction preface that tells the agent to
7
- * drive the browser via the Playwright MCP tools only and not to touch
8
- * its built-in shell / file-edit tools. Each agent injects it through a
9
- * different channel (cursor / gemini / aider prepend it to the prompt,
10
- * qwen passes it via --append-system-prompt, codex via
11
- * `-c developer_instructions=`), so this module owns only the *text*, not
12
- * the injection.
13
- * 2. Normalising the `mcp__playwright__` / `mcp__hover-playwright__` prefix
14
- * off a raw tool name so the emitted tool names line up across agents.
15
- *
16
- * codex deliberately does NOT use HOVER_PROMPT_PREFACE — it keeps its own
17
- * wording ("Do NOT call …", "emit a short agent_message summary …") that is
18
- * tuned to codex's event vocabulary. See codex.ts.
19
- */
20
- /**
21
- * The standing HOVER-mode instruction shared by cursor / gemini / qwen / aider.
22
- * codex carries a near-identical but intentionally different variant inline.
23
- */
24
- export const HOVER_PROMPT_PREFACE = [
25
- 'You are operating in Hover, a browser-testing tool.',
26
- 'Use ONLY the MCP playwright tools (prefixed `mcp__playwright__` / `mcp__hover-playwright__`) to drive the browser.',
27
- 'Do NOT use shell, file-edit, web-search, or any other built-in tool.',
28
- 'Do NOT navigate to a URL the user is already on; check the page state via `browser_snapshot` first.',
29
- 'When the task is complete, emit a short summary and stop.',
30
- ].join(' ');
31
- /** Strip the `mcp__playwright__` / `mcp__hover-playwright__` prefix so tool
32
- * names match the normalised names every agent emits. */
33
- export function stripMcpPrefix(raw) {
34
- return raw.replace(/^mcp__playwright__/, '').replace(/^mcp__hover-playwright__/, '');
35
- }