@hover-dev/core 0.16.0 → 0.17.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/README.md +26 -55
- package/dist/agentDirectives.d.ts +55 -0
- package/dist/agentDirectives.d.ts.map +1 -0
- package/dist/agentDirectives.js +276 -0
- package/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +28 -3
- package/dist/agents/codex.d.ts.map +1 -1
- package/dist/agents/codex.js +29 -14
- package/dist/agents/invoke.d.ts.map +1 -1
- package/dist/agents/invoke.js +3 -6
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +0 -4
- package/dist/agents/types.d.ts +19 -11
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/engine.d.ts +53 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +78 -0
- package/dist/mcp/actuateServer.d.ts +3 -0
- package/dist/mcp/actuateServer.d.ts.map +1 -0
- package/dist/mcp/actuateServer.js +594 -0
- package/dist/mcp/sourceFence.d.ts.map +1 -1
- package/dist/mcp/sourceFence.js +4 -0
- package/dist/mcp/sourceServer.js +75 -0
- package/dist/memory/businessMemory.d.ts +29 -0
- package/dist/memory/businessMemory.d.ts.map +1 -0
- package/dist/memory/businessMemory.js +125 -0
- package/dist/modes.d.ts +39 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +34 -0
- package/dist/playwright/cdpStatus.d.ts +0 -15
- package/dist/playwright/cdpStatus.d.ts.map +1 -1
- package/dist/playwright/cdpStatus.js +0 -67
- package/dist/playwright/launchChrome.d.ts +18 -0
- package/dist/playwright/launchChrome.d.ts.map +1 -1
- package/dist/playwright/launchChrome.js +46 -3
- package/dist/playwright/resolveMcpConfig.d.ts +7 -1
- package/dist/playwright/resolveMcpConfig.d.ts.map +1 -1
- package/dist/playwright/resolveMcpConfig.js +22 -4
- package/dist/plugin-api.d.ts +28 -26
- package/dist/plugin-api.d.ts.map +1 -1
- package/dist/plugin-api.js +2 -2
- package/dist/qa/candidates.d.ts +32 -0
- package/dist/qa/candidates.d.ts.map +1 -0
- package/dist/qa/candidates.js +20 -0
- package/dist/qa/classify.d.ts +38 -0
- package/dist/qa/classify.d.ts.map +1 -0
- package/dist/qa/classify.js +138 -0
- package/dist/qa/intensity.d.ts +33 -0
- package/dist/qa/intensity.d.ts.map +1 -0
- package/dist/qa/intensity.js +25 -0
- package/dist/qa/qaReport.d.ts +19 -0
- package/dist/qa/qaReport.d.ts.map +1 -0
- package/dist/qa/qaReport.js +50 -0
- package/dist/runSession.d.ts +14 -3
- package/dist/runSession.d.ts.map +1 -1
- package/dist/runSession.js +26 -11
- package/dist/service/cdpHandlers.d.ts +1 -21
- package/dist/service/cdpHandlers.d.ts.map +1 -1
- package/dist/service/cdpHandlers.js +4 -39
- package/dist/service/cdpHint.d.ts +21 -28
- package/dist/service/cdpHint.d.ts.map +1 -1
- package/dist/service/cdpHint.js +106 -164
- package/dist/service/relayHandlers.d.ts +28 -0
- package/dist/service/relayHandlers.d.ts.map +1 -0
- package/dist/service/relayHandlers.js +105 -0
- package/dist/service/saveHandlers.d.ts +1 -3
- package/dist/service/saveHandlers.d.ts.map +1 -1
- package/dist/service/saveHandlers.js +17 -15
- package/dist/service/types.d.ts +108 -8
- package/dist/service/types.d.ts.map +1 -1
- package/dist/service.d.ts +7 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +907 -200
- package/dist/sessions/sessions.d.ts +125 -0
- package/dist/sessions/sessions.d.ts.map +1 -0
- package/dist/sessions/sessions.js +175 -0
- package/dist/specs/authFixture.d.ts +30 -0
- package/dist/specs/authFixture.d.ts.map +1 -0
- package/dist/specs/authFixture.js +145 -0
- package/dist/specs/businessMap.d.ts +29 -0
- package/dist/specs/businessMap.d.ts.map +1 -0
- package/dist/specs/businessMap.js +95 -0
- package/dist/specs/detectSharedFlows.d.ts +1 -1
- package/dist/specs/detectSharedFlows.d.ts.map +1 -1
- package/dist/specs/detectSharedFlows.js +20 -21
- package/dist/specs/generatePageObject.d.ts +1 -1
- package/dist/specs/generatePageObject.d.ts.map +1 -1
- package/dist/specs/healPrompt.d.ts +19 -0
- package/dist/specs/healPrompt.d.ts.map +1 -0
- package/dist/specs/healPrompt.js +48 -0
- package/dist/specs/humanSteps.d.ts +4 -8
- package/dist/specs/humanSteps.d.ts.map +1 -1
- package/dist/specs/humanSteps.js +6 -1
- package/dist/specs/optimizeSpec.d.ts +15 -8
- package/dist/specs/optimizeSpec.d.ts.map +1 -1
- package/dist/specs/optimizeSpec.js +71 -41
- package/dist/specs/optimizeSpecWithAgent.d.ts +0 -2
- package/dist/specs/optimizeSpecWithAgent.d.ts.map +1 -1
- package/dist/specs/optimizeSpecWithAgent.js +0 -1
- package/dist/specs/pageObjectManifest.d.ts +3 -1
- package/dist/specs/pageObjectManifest.d.ts.map +1 -1
- package/dist/specs/pageObjectManifest.js +13 -9
- package/dist/specs/replayGrounded.d.ts +45 -0
- package/dist/specs/replayGrounded.d.ts.map +1 -0
- package/dist/specs/replayGrounded.js +155 -0
- package/dist/specs/runFailures.d.ts +34 -0
- package/dist/specs/runFailures.d.ts.map +1 -0
- package/dist/specs/runFailures.js +93 -0
- package/dist/specs/seeds.d.ts +16 -15
- package/dist/specs/seeds.d.ts.map +1 -1
- package/dist/specs/seeds.js +86 -54
- package/dist/specs/sidecar.d.ts +34 -6
- package/dist/specs/sidecar.d.ts.map +1 -1
- package/dist/specs/sidecar.js +79 -9
- package/dist/specs/specStep.d.ts +21 -0
- package/dist/specs/specStep.d.ts.map +1 -0
- package/dist/specs/specStep.js +1 -0
- package/dist/specs/text.d.ts +8 -6
- package/dist/specs/text.d.ts.map +1 -1
- package/dist/specs/text.js +10 -7
- package/dist/specs/writeSpec.d.ts +62 -1
- package/dist/specs/writeSpec.d.ts.map +1 -1
- package/dist/specs/writeSpec.js +596 -21
- package/package.json +6 -9
- package/dist/agents/aider.d.ts +0 -16
- package/dist/agents/aider.d.ts.map +0 -1
- package/dist/agents/aider.js +0 -161
- package/dist/agents/cursor.d.ts +0 -18
- package/dist/agents/cursor.d.ts.map +0 -1
- package/dist/agents/cursor.js +0 -220
- package/dist/playwright/raiseWindow.d.ts +0 -10
- package/dist/playwright/raiseWindow.d.ts.map +0 -1
- package/dist/playwright/raiseWindow.js +0 -158
- package/dist/scripts/bench-multi-tab.d.ts +0 -2
- package/dist/scripts/bench-multi-tab.d.ts.map +0 -1
- package/dist/scripts/bench-multi-tab.js +0 -192
- package/dist/scripts/bench-ttfb.d.ts +0 -2
- package/dist/scripts/bench-ttfb.d.ts.map +0 -1
- package/dist/scripts/bench-ttfb.js +0 -127
- package/dist/scripts/start-chrome.d.ts +0 -3
- package/dist/scripts/start-chrome.d.ts.map +0 -1
- package/dist/scripts/start-chrome.js +0 -23
- package/dist/skills/writeSkill.d.ts +0 -27
- package/dist/skills/writeSkill.d.ts.map +0 -1
- package/dist/skills/writeSkill.js +0 -13
- package/dist/specs/listSpecs.d.ts +0 -52
- package/dist/specs/listSpecs.d.ts.map +0 -1
- package/dist/specs/listSpecs.js +0 -139
- package/dist/specs/optimizationSuggestion.d.ts +0 -26
- package/dist/specs/optimizationSuggestion.d.ts.map +0 -1
- package/dist/specs/optimizationSuggestion.js +0 -28
- package/dist/specs/writeCaseCsv.d.ts +0 -28
- package/dist/specs/writeCaseCsv.d.ts.map +0 -1
- package/dist/specs/writeCaseCsv.js +0 -134
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hover-dev/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Hover's local Node service: agent invocation, Playwright CDP preflight, WebSocket bridge.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Hyperyond",
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
"types": "./dist/service.d.ts",
|
|
36
36
|
"import": "./dist/service.js"
|
|
37
37
|
},
|
|
38
|
+
"./engine": {
|
|
39
|
+
"types": "./dist/engine.d.ts",
|
|
40
|
+
"import": "./dist/engine.js"
|
|
41
|
+
},
|
|
38
42
|
"./launch-chrome": {
|
|
39
43
|
"types": "./dist/playwright/launchChrome.d.ts",
|
|
40
44
|
"import": "./dist/playwright/launchChrome.js"
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
},
|
|
47
51
|
"files": [
|
|
48
52
|
"dist",
|
|
53
|
+
"seeds",
|
|
49
54
|
"README.md"
|
|
50
55
|
],
|
|
51
56
|
"dependencies": {
|
|
@@ -70,14 +75,6 @@
|
|
|
70
75
|
"build": "tsc -p tsconfig.build.json",
|
|
71
76
|
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
72
77
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
73
|
-
"smoke": "tsx src/smoke.ts",
|
|
74
|
-
"smoke:chrome": "tsx src/scripts/start-chrome.ts",
|
|
75
|
-
"detect": "tsx src/scripts/detect-cli.ts",
|
|
76
|
-
"verify-widget": "tsx src/scripts/verify-widget.ts",
|
|
77
|
-
"verify-spec": "tsx src/scripts/verify-spec.ts",
|
|
78
|
-
"ws-smoke": "tsx src/scripts/ws-smoke.ts",
|
|
79
|
-
"bench-ttfb": "tsx src/scripts/bench-ttfb.ts",
|
|
80
|
-
"bench-multi-tab": "tsx src/scripts/bench-multi-tab.ts",
|
|
81
78
|
"test": "vitest run",
|
|
82
79
|
"test:watch": "vitest"
|
|
83
80
|
},
|
package/dist/agents/aider.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { AgentDescriptor, ParserState } from './types.js';
|
|
2
|
-
export declare const aiderAgent: 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
|
-
runningLines: number;
|
|
11
|
-
runningSessionId: string | undefined;
|
|
12
|
-
collectedText: string[];
|
|
13
|
-
sawErrorEvent: boolean;
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
//# sourceMappingURL=aider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aider.d.ts","sourceRoot":"","sources":["../../src/agents/aider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AA8G3F,eAAO,MAAM,UAAU,EAAE,eA0GxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS;sBACJ,WAAW;2BACJ,WAAW;sBAChB,WAAW;;;;;;CAS9B,CAAC"}
|
package/dist/agents/aider.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { HOVER_PROMPT_PREFACE } from './shared.js';
|
|
2
|
-
function aiderState(state) {
|
|
3
|
-
if (typeof state.runningLines !== 'number') {
|
|
4
|
-
state.runningLines = 0;
|
|
5
|
-
state.collectedText = [];
|
|
6
|
-
state.sawErrorEvent = false;
|
|
7
|
-
state.runningSessionId = undefined;
|
|
8
|
-
}
|
|
9
|
-
return state;
|
|
10
|
-
}
|
|
11
|
-
function resetAiderCounters(s) {
|
|
12
|
-
s.runningLines = 0;
|
|
13
|
-
s.collectedText = [];
|
|
14
|
-
s.sawErrorEvent = false;
|
|
15
|
-
s.runningSessionId = undefined;
|
|
16
|
-
}
|
|
17
|
-
// Aider has no system-prompt flag, so we prepend the standing HOVER-mode
|
|
18
|
-
// preface (HOVER_PROMPT_PREFACE, from shared.ts) to the user prompt (same
|
|
19
|
-
// approach as cursor.ts). The agent treats it as the leading user-message text.
|
|
20
|
-
/**
|
|
21
|
-
* Lines we treat as noise and drop instead of surfacing as text events.
|
|
22
|
-
* Aider chatters with status lines that would clutter the widget panel.
|
|
23
|
-
* Conservative list — anything we don't explicitly skip falls through.
|
|
24
|
-
*/
|
|
25
|
-
function isNoiseLine(line) {
|
|
26
|
-
const t = line.trim();
|
|
27
|
-
if (!t)
|
|
28
|
-
return true;
|
|
29
|
-
// Common aider boilerplate / banner lines.
|
|
30
|
-
if (/^Aider v\d/i.test(t))
|
|
31
|
-
return true;
|
|
32
|
-
if (/^Main model:/i.test(t))
|
|
33
|
-
return true;
|
|
34
|
-
if (/^Weak model:/i.test(t))
|
|
35
|
-
return true;
|
|
36
|
-
if (/^Git repo:/i.test(t))
|
|
37
|
-
return true;
|
|
38
|
-
if (/^Repo-map:/i.test(t))
|
|
39
|
-
return true;
|
|
40
|
-
if (/^VSCode terminal detected/i.test(t))
|
|
41
|
-
return true;
|
|
42
|
-
if (/^Use \/help/i.test(t))
|
|
43
|
-
return true;
|
|
44
|
-
if (/^Tokens:.*sent.*received/i.test(t))
|
|
45
|
-
return true;
|
|
46
|
-
if (/^─{3,}$/.test(t))
|
|
47
|
-
return true; // horizontal rule
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
function detectErrorLine(line) {
|
|
51
|
-
// Aider prints errors / API failures with a leading marker.
|
|
52
|
-
return /^(error|fatal|api error|litellm.*error)/i.test(line.trim());
|
|
53
|
-
}
|
|
54
|
-
export const aiderAgent = {
|
|
55
|
-
id: 'aider',
|
|
56
|
-
binName: 'aider',
|
|
57
|
-
protocol: 'argv',
|
|
58
|
-
streamFormat: 'plain-text',
|
|
59
|
-
sandboxStrength: 'soft',
|
|
60
|
-
display: {
|
|
61
|
-
label: 'Aider',
|
|
62
|
-
tagline: 'Aider — soft sandbox, plain-text stream, no MCP support',
|
|
63
|
-
homepage: 'https://aider.chat',
|
|
64
|
-
installHint: 'pipx install aider-chat',
|
|
65
|
-
},
|
|
66
|
-
buildArgs(opts) {
|
|
67
|
-
// Prepend HOVER-mode preface plus any caller-supplied appendSystemPrompt
|
|
68
|
-
// to the prompt. Aider has no --append-system-prompt flag, so this is
|
|
69
|
-
// the closest functional analogue (same trick as cursor.ts).
|
|
70
|
-
const preface = opts.appendSystemPrompt && opts.appendSystemPrompt.trim().length > 0
|
|
71
|
-
? `${HOVER_PROMPT_PREFACE} ${opts.appendSystemPrompt}`
|
|
72
|
-
: HOVER_PROMPT_PREFACE;
|
|
73
|
-
const finalPrompt = `${preface}\n\n${opts.prompt}`;
|
|
74
|
-
const args = ['--message', finalPrompt];
|
|
75
|
-
// Auto-confirm every prompt so the run doesn't hang.
|
|
76
|
-
args.push('--yes-always');
|
|
77
|
-
// Make stdout line-buffered instead of character-streamed; friendlier
|
|
78
|
-
// for our per-line parseEvent loop.
|
|
79
|
-
args.push('--no-stream');
|
|
80
|
-
// Defang git side-effects. Aider's default behaviour is to auto-commit
|
|
81
|
-
// every edit it makes; for a browser-driving agent that should never
|
|
82
|
-
// edit files this is still a hazard if cwd is a stale repo.
|
|
83
|
-
args.push('--no-auto-commits');
|
|
84
|
-
args.push('--no-git');
|
|
85
|
-
if (opts.model) {
|
|
86
|
-
args.push('--model', opts.model);
|
|
87
|
-
}
|
|
88
|
-
// Aider's `--restore-chat-history` is a boolean (no session-id form);
|
|
89
|
-
// we deliberately do NOT pass it. `opts.sessionId` is ignored because
|
|
90
|
-
// there is no way to select a specific past session by ID.
|
|
91
|
-
// No equivalents for --max-budget-usd / --allowedTools / --mcp-config /
|
|
92
|
-
// --append-system-prompt — all four are absent from aider.
|
|
93
|
-
return args;
|
|
94
|
-
},
|
|
95
|
-
parseEvent(line, state = {}) {
|
|
96
|
-
const s = aiderState(state);
|
|
97
|
-
const out = [];
|
|
98
|
-
// Emit a synthetic session_start on the very first non-empty line so
|
|
99
|
-
// the widget gets the same shape it expects from JSON-based agents.
|
|
100
|
-
if (!s.runningSessionId) {
|
|
101
|
-
// Cheap unique id; aider has no real session_id we can echo. The
|
|
102
|
-
// Math.random() suffix is load-bearing — two states created in the
|
|
103
|
-
// same millisecond would otherwise collide and break per-invocation
|
|
104
|
-
// session tracking.
|
|
105
|
-
const rand = Math.random().toString(36).slice(2, 8);
|
|
106
|
-
s.runningSessionId = `aider-${Date.now().toString(36)}-${rand}`;
|
|
107
|
-
out.push({ kind: 'session_start', sessionId: s.runningSessionId });
|
|
108
|
-
}
|
|
109
|
-
if (isNoiseLine(line))
|
|
110
|
-
return out;
|
|
111
|
-
if (detectErrorLine(line)) {
|
|
112
|
-
s.sawErrorEvent = true;
|
|
113
|
-
out.push({ kind: 'text', text: line.trim() });
|
|
114
|
-
return out;
|
|
115
|
-
}
|
|
116
|
-
// Treat everything else as assistant text. Aider has no per-tool events
|
|
117
|
-
// so we cannot emit tool_use / tool_result — see file-header doc comment.
|
|
118
|
-
s.runningLines += 1;
|
|
119
|
-
s.collectedText.push(line.trim());
|
|
120
|
-
out.push({ kind: 'text', text: line.trim() });
|
|
121
|
-
return out;
|
|
122
|
-
},
|
|
123
|
-
/**
|
|
124
|
-
* Aider doesn't emit a terminal event — the child process simply exits
|
|
125
|
-
* after the final printed line. Synthesize session_end from accumulated
|
|
126
|
-
* state, same pattern as codex.ts.
|
|
127
|
-
*/
|
|
128
|
-
onStreamEnd(exitCode, state = {}) {
|
|
129
|
-
const s = aiderState(state);
|
|
130
|
-
// The "summary" is the last non-empty text line, which is typically
|
|
131
|
-
// aider's final answer. If we collected nothing, leave it undefined
|
|
132
|
-
// rather than fabricating.
|
|
133
|
-
const lastText = s.collectedText.length > 0
|
|
134
|
-
? s.collectedText[s.collectedText.length - 1]
|
|
135
|
-
: undefined;
|
|
136
|
-
return {
|
|
137
|
-
kind: 'session_end',
|
|
138
|
-
turns: s.runningLines,
|
|
139
|
-
// costUsd intentionally undefined — aider's "Tokens:" status line is
|
|
140
|
-
// ad-hoc text, not a stable API. We don't fabricate a number.
|
|
141
|
-
isError: s.sawErrorEvent || (exitCode != null && exitCode !== 0),
|
|
142
|
-
summary: lastText,
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
/**
|
|
147
|
-
* Test-only escape hatches, same pattern as cursor.ts / codex.ts.
|
|
148
|
-
*/
|
|
149
|
-
export const __testing = {
|
|
150
|
-
freshState: () => ({}),
|
|
151
|
-
resetCounters: (state) => resetAiderCounters(aiderState(state)),
|
|
152
|
-
getState: (state) => {
|
|
153
|
-
const s = aiderState(state);
|
|
154
|
-
return {
|
|
155
|
-
runningLines: s.runningLines,
|
|
156
|
-
runningSessionId: s.runningSessionId,
|
|
157
|
-
collectedText: [...s.collectedText],
|
|
158
|
-
sawErrorEvent: s.sawErrorEvent,
|
|
159
|
-
};
|
|
160
|
-
},
|
|
161
|
-
};
|
package/dist/agents/cursor.d.ts
DELETED
|
@@ -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"}
|
package/dist/agents/cursor.js
DELETED
|
@@ -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,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Find the OS PID of the process listening on the given TCP port.
|
|
3
|
-
* Returns null if nothing is listening or the lookup tool isn't available.
|
|
4
|
-
*/
|
|
5
|
-
export declare function findCdpPid(port: number): Promise<number | null>;
|
|
6
|
-
/**
|
|
7
|
-
* Raise the Chrome window owned by `pid` to the OS foreground. Best-effort.
|
|
8
|
-
*/
|
|
9
|
-
export declare function raiseChromeWindow(pid: number): Promise<void>;
|
|
10
|
-
//# sourceMappingURL=raiseWindow.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"raiseWindow.d.ts","sourceRoot":"","sources":["../../src/playwright/raiseWindow.ts"],"names":[],"mappings":"AAyBA;;;GAGG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8BrE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDlE"}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Raise the OS-level Chrome window to the foreground.
|
|
3
|
-
*
|
|
4
|
-
* Why this exists: CDP's `Page.bringToFront()` and `Target.activateTarget`
|
|
5
|
-
* only reorder tabs *inside* the Chrome process — they do not raise the
|
|
6
|
-
* Chrome application window in the OS's window stack. When the user clicks
|
|
7
|
-
* "Switch me to it" from a widget hosted in a different window, the tab
|
|
8
|
-
* activates correctly inside the (possibly background) debug Chrome, but
|
|
9
|
-
* the window stays buried. The user then has to manually click the Chrome
|
|
10
|
-
* Dock icon / Alt-Tab to it, which defeats the point of the button.
|
|
11
|
-
*
|
|
12
|
-
* Fix: after `bringToFront()`, run an OS-specific command that raises the
|
|
13
|
-
* specific Chrome *process* (matched by PID, found from the CDP port via
|
|
14
|
-
* `lsof` / `netstat`). PID-matching is critical — the user's own primary
|
|
15
|
-
* Chrome and Hover's debug Chrome are both "Google Chrome" to AppleScript,
|
|
16
|
-
* so raising by app name would risk activating the wrong window.
|
|
17
|
-
*
|
|
18
|
-
* Best-effort and non-blocking — if the helper fails, we still leave the
|
|
19
|
-
* tab correctly focused inside the debug Chrome and the user can click
|
|
20
|
-
* over manually like before. Logging is to stderr only; this never throws
|
|
21
|
-
* back to the caller.
|
|
22
|
-
*/
|
|
23
|
-
import { spawn } from 'node:child_process';
|
|
24
|
-
import { platform } from 'node:os';
|
|
25
|
-
/**
|
|
26
|
-
* Find the OS PID of the process listening on the given TCP port.
|
|
27
|
-
* Returns null if nothing is listening or the lookup tool isn't available.
|
|
28
|
-
*/
|
|
29
|
-
export async function findCdpPid(port) {
|
|
30
|
-
const os = platform();
|
|
31
|
-
if (os === 'darwin' || os === 'linux') {
|
|
32
|
-
// -t prints just PIDs, -sTCP:LISTEN filters to listeners (otherwise
|
|
33
|
-
// every client connection's PID would show up too).
|
|
34
|
-
const out = await runCapture('lsof', ['-tiTCP:' + port, '-sTCP:LISTEN']);
|
|
35
|
-
if (!out)
|
|
36
|
-
return null;
|
|
37
|
-
// lsof may print multiple lines if forked workers also hold the port;
|
|
38
|
-
// take the first numeric one.
|
|
39
|
-
for (const line of out.split('\n')) {
|
|
40
|
-
const pid = Number.parseInt(line.trim(), 10);
|
|
41
|
-
if (Number.isInteger(pid) && pid > 0)
|
|
42
|
-
return pid;
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
if (os === 'win32') {
|
|
47
|
-
// `netstat -ano` columns: Proto Local Foreign State PID
|
|
48
|
-
const out = await runCapture('netstat', ['-ano']);
|
|
49
|
-
if (!out)
|
|
50
|
-
return null;
|
|
51
|
-
for (const line of out.split('\n')) {
|
|
52
|
-
// Match `TCP 127.0.0.1:9222 0.0.0.0:0 LISTENING 1234`
|
|
53
|
-
const m = line.match(/^\s*TCP\s+\S+:(\d+)\s+\S+\s+LISTENING\s+(\d+)\s*$/i);
|
|
54
|
-
if (m && Number(m[1]) === port)
|
|
55
|
-
return Number(m[2]);
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Raise the Chrome window owned by `pid` to the OS foreground. Best-effort.
|
|
63
|
-
*/
|
|
64
|
-
export async function raiseChromeWindow(pid) {
|
|
65
|
-
const os = platform();
|
|
66
|
-
try {
|
|
67
|
-
if (os === 'darwin') {
|
|
68
|
-
// System Events can frontmost any process by its unix PID, regardless
|
|
69
|
-
// of app bundle. This works even when several "Google Chrome"
|
|
70
|
-
// processes coexist (user's primary + Hover's debug).
|
|
71
|
-
await runDetached('osascript', [
|
|
72
|
-
'-e',
|
|
73
|
-
`tell application "System Events" to set frontmost of (first process whose unix id is ${pid}) to true`,
|
|
74
|
-
]);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
if (os === 'linux') {
|
|
78
|
-
// wmctrl is the most common helper for X11; not always installed,
|
|
79
|
-
// but the alternative (xdotool) needs the same dependency story.
|
|
80
|
-
// If it isn't installed the runCapture/runDetached calls swallow the
|
|
81
|
-
// ENOENT and we degrade gracefully.
|
|
82
|
-
//
|
|
83
|
-
// `wmctrl -i` expects an X11 WINDOW id, NOT a unix PID, so we first
|
|
84
|
-
// map PID → window id. `wmctrl -l -p` lists windows with their owning
|
|
85
|
-
// PID in the third column:
|
|
86
|
-
// 0x03c00007 0 12345 hostname Title…
|
|
87
|
-
const listing = await runCapture('wmctrl', ['-l', '-p']);
|
|
88
|
-
if (!listing)
|
|
89
|
-
return;
|
|
90
|
-
let windowId = null;
|
|
91
|
-
for (const line of listing.split('\n')) {
|
|
92
|
-
// columns: <window-id> <desktop> <pid> <host> <title…>
|
|
93
|
-
const m = line.match(/^(0x[0-9a-fA-F]+)\s+\S+\s+(\d+)\s/);
|
|
94
|
-
if (m && Number(m[2]) === pid) {
|
|
95
|
-
windowId = m[1];
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (!windowId)
|
|
100
|
-
return; // no matching window — no-op gracefully
|
|
101
|
-
await runDetached('wmctrl', ['-ia', windowId]);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (os === 'win32') {
|
|
105
|
-
// PowerShell is bundled with Windows 10+. AppActivate is best-effort:
|
|
106
|
-
// it requires the target to have a visible main window, which a
|
|
107
|
-
// headless-less Chrome with a tab open satisfies.
|
|
108
|
-
const ps = `$p = Get-Process -Id ${pid} -ErrorAction SilentlyContinue; ` +
|
|
109
|
-
`if ($p) { ` +
|
|
110
|
-
` Add-Type -AssemblyName Microsoft.VisualBasic; ` +
|
|
111
|
-
` [Microsoft.VisualBasic.Interaction]::AppActivate($p.Id) ` +
|
|
112
|
-
`}`;
|
|
113
|
-
await runDetached('powershell', ['-NoProfile', '-Command', ps]);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// Best-effort. CDP-level bringToFront already ran; user can still
|
|
119
|
-
// click the Chrome window manually.
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function runCapture(cmd, args) {
|
|
123
|
-
return new Promise(resolve => {
|
|
124
|
-
let out = '';
|
|
125
|
-
let settled = false;
|
|
126
|
-
const finish = (v) => {
|
|
127
|
-
if (settled)
|
|
128
|
-
return;
|
|
129
|
-
settled = true;
|
|
130
|
-
resolve(v);
|
|
131
|
-
};
|
|
132
|
-
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
133
|
-
child.stdout.on('data', chunk => {
|
|
134
|
-
out += chunk.toString();
|
|
135
|
-
});
|
|
136
|
-
child.on('error', () => finish(null));
|
|
137
|
-
child.on('close', code => finish(code === 0 ? out : null));
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
function runDetached(cmd, args) {
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
let settled = false;
|
|
143
|
-
const child = spawn(cmd, args, { stdio: 'ignore' });
|
|
144
|
-
child.on('error', err => {
|
|
145
|
-
if (settled)
|
|
146
|
-
return;
|
|
147
|
-
settled = true;
|
|
148
|
-
reject(err);
|
|
149
|
-
});
|
|
150
|
-
child.on('close', () => {
|
|
151
|
-
if (settled)
|
|
152
|
-
return;
|
|
153
|
-
settled = true;
|
|
154
|
-
// Don't treat non-zero as fatal — caller already wraps in try/catch.
|
|
155
|
-
resolve();
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bench-multi-tab.d.ts","sourceRoot":"","sources":["../../src/scripts/bench-multi-tab.ts"],"names":[],"mappings":""}
|