@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.
- 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/engine.d.ts +28 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +27 -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/playwright/launchChrome.d.ts +18 -0
- package/dist/playwright/launchChrome.d.ts.map +1 -1
- package/dist/playwright/launchChrome.js +46 -3
- 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/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/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/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/pageObjectManifest.d.ts +3 -1
- package/dist/specs/pageObjectManifest.d.ts.map +1 -1
- package/dist/specs/pageObjectManifest.js +24 -19
- 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 +9 -29
- 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/argv.d.ts +0 -11
- package/dist/agents/argv.d.ts.map +0 -1
- package/dist/agents/argv.js +0 -23
- package/dist/agents/claude.d.ts +0 -3
- package/dist/agents/claude.d.ts.map +0 -1
- package/dist/agents/claude.js +0 -195
- package/dist/agents/codex.d.ts +0 -19
- package/dist/agents/codex.d.ts.map +0 -1
- package/dist/agents/codex.js +0 -216
- 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/agents/detect.d.ts +0 -46
- package/dist/agents/detect.d.ts.map +0 -1
- package/dist/agents/detect.js +0 -80
- package/dist/agents/gemini.d.ts +0 -17
- package/dist/agents/gemini.d.ts.map +0 -1
- package/dist/agents/gemini.js +0 -186
- package/dist/agents/index.d.ts +0 -6
- package/dist/agents/index.d.ts.map +0 -1
- package/dist/agents/index.js +0 -5
- package/dist/agents/invoke.d.ts +0 -12
- package/dist/agents/invoke.d.ts.map +0 -1
- package/dist/agents/invoke.js +0 -96
- package/dist/agents/qwen.d.ts +0 -17
- package/dist/agents/qwen.d.ts.map +0 -1
- package/dist/agents/qwen.js +0 -172
- package/dist/agents/registry.d.ts +0 -19
- package/dist/agents/registry.d.ts.map +0 -1
- package/dist/agents/registry.js +0 -34
- package/dist/agents/shared.d.ts +0 -28
- package/dist/agents/shared.d.ts.map +0 -1
- package/dist/agents/shared.js +0 -35
- package/dist/agents/types.d.ts +0 -186
- package/dist/agents/types.d.ts.map +0 -1
- package/dist/agents/types.js +0 -23
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/mcp/sourceFence.d.ts +0 -23
- package/dist/mcp/sourceFence.d.ts.map +0 -1
- package/dist/mcp/sourceFence.js +0 -75
- package/dist/mcp/sourceServer.d.ts +0 -3
- package/dist/mcp/sourceServer.d.ts.map +0 -1
- package/dist/mcp/sourceServer.js +0 -116
- package/dist/playwright/cdpStatus.d.ts +0 -29
- package/dist/playwright/cdpStatus.d.ts.map +0 -1
- package/dist/playwright/cdpStatus.js +0 -119
- package/dist/playwright/preflight.d.ts +0 -31
- package/dist/playwright/preflight.d.ts.map +0 -1
- package/dist/playwright/preflight.js +0 -82
- package/dist/playwright/preflightCache.d.ts +0 -27
- package/dist/playwright/preflightCache.d.ts.map +0 -1
- package/dist/playwright/preflightCache.js +0 -21
- package/dist/playwright/raiseWindow.d.ts +0 -10
- package/dist/playwright/raiseWindow.d.ts.map +0 -1
- package/dist/playwright/raiseWindow.js +0 -158
- package/dist/playwright/resolveMcpConfig.d.ts +0 -55
- package/dist/playwright/resolveMcpConfig.d.ts.map +0 -1
- package/dist/playwright/resolveMcpConfig.js +0 -66
- package/dist/plugin-api.d.ts +0 -235
- package/dist/plugin-api.d.ts.map +0 -1
- package/dist/plugin-api.js +0 -52
- package/dist/runSession.d.ts +0 -42
- package/dist/runSession.d.ts.map +0 -1
- package/dist/runSession.js +0 -81
- 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/service/cdpHandlers.d.ts +0 -44
- package/dist/service/cdpHandlers.d.ts.map +0 -1
- package/dist/service/cdpHandlers.js +0 -85
- package/dist/service/cdpHint.d.ts +0 -48
- package/dist/service/cdpHint.d.ts.map +0 -1
- package/dist/service/cdpHint.js +0 -216
- package/dist/service/conventions.d.ts +0 -8
- package/dist/service/conventions.d.ts.map +0 -1
- package/dist/service/conventions.js +0 -42
- package/dist/service/saveHandlers.d.ts +0 -52
- package/dist/service/saveHandlers.d.ts.map +0 -1
- package/dist/service/saveHandlers.js +0 -75
- package/dist/service/types.d.ts +0 -58
- package/dist/service/types.d.ts.map +0 -1
- package/dist/service/types.js +0 -26
- package/dist/service.d.ts +0 -50
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -1065
- 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/extractPageObjects.d.ts +0 -18
- package/dist/specs/extractPageObjects.d.ts.map +0 -1
- package/dist/specs/extractPageObjects.js +0 -98
- 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/optimizeSpecWithAgent.d.ts +0 -11
- package/dist/specs/optimizeSpecWithAgent.d.ts.map +0 -1
- package/dist/specs/optimizeSpecWithAgent.js +0 -40
- package/dist/specs/writeCaseCsv.d.ts +0 -28
- package/dist/specs/writeCaseCsv.d.ts.map +0 -1
- package/dist/specs/writeCaseCsv.js +0 -134
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business memory — the per-app knowledge QA / API modes accumulate so they stop
|
|
3
|
+
* re-asking the same business questions every run. Lives at `<devRoot>/.hover/
|
|
4
|
+
* memory/`, mirroring Claude's own memory layout (an index + one fact per file):
|
|
5
|
+
*
|
|
6
|
+
* .hover/memory/
|
|
7
|
+
* MEMORY.md ← index: one `- [title](file.md) — hook` line per fact
|
|
8
|
+
* checkout-tax.md ← frontmatter (name / description / type) + the fact body
|
|
9
|
+
* ...
|
|
10
|
+
*
|
|
11
|
+
* Loop: load at run start → inject as agent context ("Known business rules…");
|
|
12
|
+
* after the user answers a business clarification (trigger-B, a later QA stage),
|
|
13
|
+
* write the learned fact. The more an app is tested, the fewer popups — see the
|
|
14
|
+
* QA-tester-mode design + project-moat-strategy.
|
|
15
|
+
*
|
|
16
|
+
* CONTRACT: business RULES only, NEVER secrets / PII / credentials (extends the
|
|
17
|
+
* standing "never read env / secrets" rule). Writes are best-effort — a memory
|
|
18
|
+
* failure must NEVER break a run (same rule as the session ledger). Used by QA +
|
|
19
|
+
* API modes only; Flow / Pentest don't read or write it.
|
|
20
|
+
*/
|
|
21
|
+
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { hoverDir } from '../specs/sidecar.js';
|
|
24
|
+
export function memoryDir(devRoot) {
|
|
25
|
+
return join(hoverDir(devRoot), 'memory');
|
|
26
|
+
}
|
|
27
|
+
/** kebab-case a title into a safe filename stem. */
|
|
28
|
+
export function slugify(s) {
|
|
29
|
+
return s
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
32
|
+
.replace(/^-+|-+$/g, '')
|
|
33
|
+
.slice(0, 60) || 'fact';
|
|
34
|
+
}
|
|
35
|
+
/** Parse a fact file's `---` frontmatter + body. Minimal + total — a malformed
|
|
36
|
+
* file yields nulls and is skipped by the caller. Only the three known keys are
|
|
37
|
+
* read; everything after the closing `---` is the body. */
|
|
38
|
+
function parseFact(slug, raw) {
|
|
39
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
40
|
+
if (!m)
|
|
41
|
+
return null;
|
|
42
|
+
const fm = m[1];
|
|
43
|
+
const body = m[2].trim();
|
|
44
|
+
const field = (k) => {
|
|
45
|
+
const f = fm.match(new RegExp(`^${k}\\s*:\\s*(.+)$`, 'm'));
|
|
46
|
+
return f ? f[1].trim().replace(/^["']|["']$/g, '') : '';
|
|
47
|
+
};
|
|
48
|
+
const description = field('description');
|
|
49
|
+
const rawType = field('type');
|
|
50
|
+
const types = ['business-rule', 'expected-behavior', 'validation', 'access-policy'];
|
|
51
|
+
const type = types.includes(rawType) ? rawType : 'business-rule';
|
|
52
|
+
if (!body)
|
|
53
|
+
return null;
|
|
54
|
+
return { name: field('name') || slug, description, type, body };
|
|
55
|
+
}
|
|
56
|
+
/** Load every fact under `.hover/memory/` (excluding the MEMORY.md index).
|
|
57
|
+
* Pure + total: a missing dir / unreadable file just yields fewer facts. */
|
|
58
|
+
export async function loadMemory(devRoot) {
|
|
59
|
+
try {
|
|
60
|
+
const dir = memoryDir(devRoot);
|
|
61
|
+
const entries = (await readdir(dir)).filter((e) => e.endsWith('.md') && e.toLowerCase() !== 'memory.md');
|
|
62
|
+
const facts = [];
|
|
63
|
+
for (const entry of entries.sort()) {
|
|
64
|
+
try {
|
|
65
|
+
const raw = await readFile(join(dir, entry), 'utf-8');
|
|
66
|
+
const fact = parseFact(entry.replace(/\.md$/, ''), raw);
|
|
67
|
+
if (fact)
|
|
68
|
+
facts.push(fact);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* skip unreadable */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return facts;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Format loaded facts as a system-prompt block, or '' when there are none (so
|
|
81
|
+
* the caller appends nothing). Grouped nothing-fancy: one bullet per fact. */
|
|
82
|
+
export function formatMemoryForPrompt(facts) {
|
|
83
|
+
if (!facts.length)
|
|
84
|
+
return '';
|
|
85
|
+
const lines = facts.map((f) => `- ${f.description ? f.description + ' — ' : ''}${f.body.replace(/\s+/g, ' ').trim()}`);
|
|
86
|
+
return ('KNOWN BUSINESS KNOWLEDGE FOR THIS APP (learned from earlier runs — treat as ' +
|
|
87
|
+
'ground truth; do NOT re-ask what these already answer):\n' +
|
|
88
|
+
lines.join('\n'));
|
|
89
|
+
}
|
|
90
|
+
/** Write (or overwrite) a fact file + refresh the MEMORY.md index line. NEVER
|
|
91
|
+
* throws — returns the path or an error string for the caller to log. Business
|
|
92
|
+
* RULES only; the caller must never pass secrets / PII / credentials. */
|
|
93
|
+
export async function writeFact(devRoot, fact) {
|
|
94
|
+
try {
|
|
95
|
+
const dir = memoryDir(devRoot);
|
|
96
|
+
await mkdir(dir, { recursive: true });
|
|
97
|
+
const slug = slugify(fact.name);
|
|
98
|
+
const file = `${slug}.md`;
|
|
99
|
+
const content = `---\nname: ${slug}\ndescription: ${fact.description}\ntype: ${fact.type}\n---\n\n${fact.body.trim()}\n`;
|
|
100
|
+
await writeFile(join(dir, file), content, 'utf-8');
|
|
101
|
+
await upsertIndex(dir, slug, fact);
|
|
102
|
+
return { path: join(dir, file) };
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Add/replace this fact's line in MEMORY.md (`- [title](file.md) — hook`),
|
|
109
|
+
* keyed by the file link so re-writing a fact updates rather than duplicates. */
|
|
110
|
+
async function upsertIndex(dir, slug, fact) {
|
|
111
|
+
const indexPath = join(dir, 'MEMORY.md');
|
|
112
|
+
const link = `(${slug}.md)`;
|
|
113
|
+
const line = `- [${fact.name}](${slug}.md) — ${fact.description || fact.type}`;
|
|
114
|
+
let existing = '';
|
|
115
|
+
try {
|
|
116
|
+
existing = await readFile(indexPath, 'utf-8');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
existing = '# Business memory\n\nWhat Hover has learned about this app. One fact per file.\n';
|
|
120
|
+
}
|
|
121
|
+
const kept = existing
|
|
122
|
+
.split('\n')
|
|
123
|
+
.filter((l) => !(l.startsWith('- [') && l.includes(link)));
|
|
124
|
+
await writeFile(indexPath, `${kept.join('\n').replace(/\n+$/, '')}\n${line}\n`, 'utf-8');
|
|
125
|
+
}
|
|
@@ -21,6 +21,17 @@ export interface LaunchOptions {
|
|
|
21
21
|
/** Base64 SHA-256 of the MITM CA's SubjectPublicKeyInfo. */
|
|
22
22
|
spki: string;
|
|
23
23
|
};
|
|
24
|
+
/** Launch Chrome headless (`--headless=new`) — no visible window. Still
|
|
25
|
+
* CDP-drivable and still uses the persistent profile, so login state set in
|
|
26
|
+
* a prior headed launch carries over. Used by the VSCode extension's silent
|
|
27
|
+
* mode. Default false (visible window). */
|
|
28
|
+
headless?: boolean;
|
|
29
|
+
/** Close any existing debug Chrome on this port FIRST, then launch fresh.
|
|
30
|
+
* The plain launch is idempotent (returns the running instance), so it can't
|
|
31
|
+
* switch headless↔visible or recover a window that's there-but-not-showing —
|
|
32
|
+
* `force` makes the headless/visible toggle and the "reopen browser" action
|
|
33
|
+
* actually relaunch. Default false. */
|
|
34
|
+
force?: boolean;
|
|
24
35
|
}
|
|
25
36
|
export type LaunchResult = {
|
|
26
37
|
ok: true;
|
|
@@ -32,6 +43,13 @@ export type LaunchResult = {
|
|
|
32
43
|
reason: string;
|
|
33
44
|
};
|
|
34
45
|
export declare function findChromeBinary(): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Close a debug Chrome on `port` by sending CDP `Browser.close` over its
|
|
48
|
+
* DevTools WebSocket (from /json/version). Works without a child-process handle
|
|
49
|
+
* — Hover spawns Chrome detached — so it's the only way to relaunch in a
|
|
50
|
+
* different mode. Resolves once closed (or the timeout lapses); never throws.
|
|
51
|
+
*/
|
|
52
|
+
export declare function closeDebugChrome(port: number, timeoutMs?: number): Promise<boolean>;
|
|
35
53
|
/**
|
|
36
54
|
* Start (or detect) a debug Chrome listening on the given CDP port. Detaches
|
|
37
55
|
* the child process so the calling script can exit cleanly while Chrome keeps
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launchChrome.d.ts","sourceRoot":"","sources":["../../src/playwright/launchChrome.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;wEAKoE;IACpE,KAAK,CAAC,EAAE;QACN,oDAAoD;QACpD,IAAI,EAAE,MAAM,CAAC;QACb,4DAA4D;QAC5D,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;
|
|
1
|
+
{"version":3,"file":"launchChrome.d.ts","sourceRoot":"","sources":["../../src/playwright/launchChrome.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;wEAKoE;IACpE,KAAK,CAAC,EAAE;QACN,oDAAoD;QACpD,IAAI,EAAE,MAAM,CAAC;QACb,4DAA4D;QAC5D,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF;;;gDAG4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;4CAIwC;IACxC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMlC,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CA0ChD;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBvF;AAiCD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuEvF"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-platform launcher for an isolated debug Chrome on a known CDP port.
|
|
3
3
|
*
|
|
4
|
-
* Idempotent — if the port already responds, returns immediately.
|
|
5
|
-
*
|
|
6
|
-
* - `pnpm exec hover-chrome` (npm consumers) via vite-plugin-hover's bin
|
|
4
|
+
* Idempotent — if the port already responds, returns immediately. The VS Code
|
|
5
|
+
* extension calls this on demand (first ✨ click) to bring up the debug Chrome.
|
|
7
6
|
*
|
|
8
7
|
* The user-data-dir is isolated under tmpdir so we never touch the user's
|
|
9
8
|
* primary Chrome profile.
|
|
@@ -12,6 +11,7 @@ import { spawn } from 'node:child_process';
|
|
|
12
11
|
import { existsSync, unlinkSync } from 'node:fs';
|
|
13
12
|
import { platform, tmpdir } from 'node:os';
|
|
14
13
|
import { join } from 'node:path';
|
|
14
|
+
import { WebSocket } from 'ws';
|
|
15
15
|
const DEFAULT_PORT = 9222;
|
|
16
16
|
const DEFAULT_READY_TIMEOUT_MS = 9000;
|
|
17
17
|
const DEFAULT_POLL_MS = 300;
|
|
@@ -53,6 +53,39 @@ export function findChromeBinary() {
|
|
|
53
53
|
}
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Close a debug Chrome on `port` by sending CDP `Browser.close` over its
|
|
58
|
+
* DevTools WebSocket (from /json/version). Works without a child-process handle
|
|
59
|
+
* — Hover spawns Chrome detached — so it's the only way to relaunch in a
|
|
60
|
+
* different mode. Resolves once closed (or the timeout lapses); never throws.
|
|
61
|
+
*/
|
|
62
|
+
export async function closeDebugChrome(port, timeoutMs = 4000) {
|
|
63
|
+
let wsUrl;
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(`http://localhost:${port}/json/version`, { signal: AbortSignal.timeout(1500) });
|
|
66
|
+
if (!res.ok)
|
|
67
|
+
return false;
|
|
68
|
+
wsUrl = (await res.json()).webSocketDebuggerUrl ?? '';
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (!wsUrl)
|
|
74
|
+
return false;
|
|
75
|
+
return await new Promise((resolve) => {
|
|
76
|
+
let done = false;
|
|
77
|
+
const finish = (ok) => { if (done)
|
|
78
|
+
return; done = true; try {
|
|
79
|
+
sock.close();
|
|
80
|
+
}
|
|
81
|
+
catch { /* ignore */ } resolve(ok); };
|
|
82
|
+
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
83
|
+
const sock = new WebSocket(wsUrl);
|
|
84
|
+
sock.on('open', () => sock.send(JSON.stringify({ id: 1, method: 'Browser.close' })));
|
|
85
|
+
sock.on('message', () => { clearTimeout(timer); finish(true); });
|
|
86
|
+
sock.on('error', () => { clearTimeout(timer); finish(false); });
|
|
87
|
+
});
|
|
88
|
+
}
|
|
56
89
|
async function isCdpAlive(port) {
|
|
57
90
|
try {
|
|
58
91
|
const res = await fetch(`http://localhost:${port}/json/version`, {
|
|
@@ -95,6 +128,14 @@ export async function launchDebugChrome(opts = {}) {
|
|
|
95
128
|
const url = opts.url ?? 'about:blank';
|
|
96
129
|
const readyTimeoutMs = opts.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
|
|
97
130
|
const pollMs = opts.pollMs ?? DEFAULT_POLL_MS;
|
|
131
|
+
// force: close any existing instance first so a headless↔visible switch (or
|
|
132
|
+
// a "reopen browser" click) actually relaunches instead of no-opping.
|
|
133
|
+
if (opts.force && (await isCdpAlive(port))) {
|
|
134
|
+
await closeDebugChrome(port);
|
|
135
|
+
// Give the port a moment to free up before relaunch.
|
|
136
|
+
for (let i = 0; i < 20 && (await isCdpAlive(port)); i++)
|
|
137
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
138
|
+
}
|
|
98
139
|
if (await isCdpAlive(port)) {
|
|
99
140
|
return { ok: true, alreadyRunning: true, userDataDir, port };
|
|
100
141
|
}
|
|
@@ -112,6 +153,8 @@ export async function launchDebugChrome(opts = {}) {
|
|
|
112
153
|
'--no-first-run',
|
|
113
154
|
'--no-default-browser-check',
|
|
114
155
|
];
|
|
156
|
+
if (opts.headless)
|
|
157
|
+
args.push('--headless=new');
|
|
115
158
|
if (opts.proxy) {
|
|
116
159
|
args.push(`--proxy-server=127.0.0.1:${opts.proxy.port}`, `--ignore-certificate-errors-spki-list=${opts.proxy.spki}`);
|
|
117
160
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA candidate-flow finalization.
|
|
3
|
+
*
|
|
4
|
+
* During a QA run the agent calls `record_candidate(name)` right after it
|
|
5
|
+
* completes a coherent flow; the hover-control MCP captures the actual grounded
|
|
6
|
+
* actuation steps since the previous marker and sends them along — so a
|
|
7
|
+
* candidate already carries its real, replayable SkillSteps (no fragile
|
|
8
|
+
* step-number citing). This module just validates + de-dupes them before they
|
|
9
|
+
* become one-click "Crystallize" cards.
|
|
10
|
+
*
|
|
11
|
+
* Pure + side-effect-free so it can be unit-tested without a live run.
|
|
12
|
+
*/
|
|
13
|
+
import type { SkillStep } from '../specs/specStep.js';
|
|
14
|
+
/** What the agent recorded: a flow name + the real steps Hover captured for it. */
|
|
15
|
+
export interface RecordedCandidate {
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
steps: SkillStep[];
|
|
19
|
+
}
|
|
20
|
+
/** A candidate ready to crystallize. */
|
|
21
|
+
export interface ResolvedCandidate {
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
steps: SkillStep[];
|
|
25
|
+
stepCount: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate + de-dupe recorded candidates: drop ones with no name or no steps,
|
|
29
|
+
* collapse identical repeats (same name + same step count), and stamp stepCount.
|
|
30
|
+
*/
|
|
31
|
+
export declare function finalizeCandidates(candidates: readonly RecordedCandidate[]): ResolvedCandidate[];
|
|
32
|
+
//# sourceMappingURL=candidates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../src/qa/candidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,mFAAmF;AACnF,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,wCAAwC;AACxC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,SAAS,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAahG"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate + de-dupe recorded candidates: drop ones with no name or no steps,
|
|
3
|
+
* collapse identical repeats (same name + same step count), and stamp stepCount.
|
|
4
|
+
*/
|
|
5
|
+
export function finalizeCandidates(candidates) {
|
|
6
|
+
const out = [];
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
for (const c of candidates) {
|
|
9
|
+
const name = c.name?.trim();
|
|
10
|
+
const steps = Array.isArray(c.steps) ? c.steps.filter((s) => s && s.kind === 'step') : [];
|
|
11
|
+
if (!name || !steps.length)
|
|
12
|
+
continue;
|
|
13
|
+
const key = `${name}|${steps.length}`;
|
|
14
|
+
if (seen.has(key))
|
|
15
|
+
continue;
|
|
16
|
+
seen.add(key);
|
|
17
|
+
out.push({ name, description: c.description?.trim() || undefined, steps, stepCount: steps.length });
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA run intensity presets — how hard a QA exploration tries, bounded by a hard
|
|
3
|
+
* STEP ceiling so "explore the whole app" can't run away on time/cost.
|
|
4
|
+
*
|
|
5
|
+
* Each preset maps to a `maxSteps` (agent turns ≈ steps). It's enforced two ways:
|
|
6
|
+
* 1. the prompt (qaBudgetDirective) tells the agent its step budget so it paces
|
|
7
|
+
* itself and writes the findings report BEFORE running out — the graceful
|
|
8
|
+
* path, and it works for every agent;
|
|
9
|
+
* 2. a hard `--max-turns` backstop (claude) so a misbehaving agent is still
|
|
10
|
+
* bounded. Steps are what the user reasons in, so the budget is in steps,
|
|
11
|
+
* not dollars.
|
|
12
|
+
* Only applies in QA mode.
|
|
13
|
+
*/
|
|
14
|
+
export type QaIntensity = 'quick' | 'standard' | 'deep';
|
|
15
|
+
export interface QaIntensitySpec {
|
|
16
|
+
label: string;
|
|
17
|
+
/** Hard ceiling on agent turns (~steps): the prompt paces against it and
|
|
18
|
+
* `--max-turns` enforces it as a backstop. */
|
|
19
|
+
maxSteps: number;
|
|
20
|
+
/** One-line description (with the rough step range) — used in the prompt + UI. */
|
|
21
|
+
blurb: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const QA_INTENSITY: Record<QaIntensity, QaIntensitySpec>;
|
|
24
|
+
export declare const DEFAULT_QA_INTENSITY: QaIntensity;
|
|
25
|
+
/** Coerce arbitrary input (from the run payload) to a valid intensity. */
|
|
26
|
+
export declare function asQaIntensity(v: unknown): QaIntensity;
|
|
27
|
+
/**
|
|
28
|
+
* Prompt directive: tell the agent its STEP budget so it paces and ALWAYS wraps
|
|
29
|
+
* up with a report before the ceiling. The `--max-turns` backstop is the hard
|
|
30
|
+
* limit; this prose is what guarantees a report.
|
|
31
|
+
*/
|
|
32
|
+
export declare function qaBudgetDirective(intensity: QaIntensity): string;
|
|
33
|
+
//# sourceMappingURL=intensity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intensity.d.ts","sourceRoot":"","sources":["../../src/qa/intensity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd;mDAC+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,eAAe,CAI7D,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,WAAwB,CAAC;AAE5D,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,WAAW,CAErD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,WAAW,GAAG,MAAM,CAWhE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const QA_INTENSITY = {
|
|
2
|
+
quick: { label: 'Quick', maxSteps: 45, blurb: 'a fast pass over the main flows — breadth over depth (~20–45 steps)' },
|
|
3
|
+
standard: { label: 'Standard', maxSteps: 150, blurb: 'the main flows plus key negative tests (~45–150 steps)' },
|
|
4
|
+
deep: { label: 'Deep', maxSteps: 500, blurb: 'exhaustive — every reachable control and state (~150–500 steps)' },
|
|
5
|
+
};
|
|
6
|
+
export const DEFAULT_QA_INTENSITY = 'standard';
|
|
7
|
+
/** Coerce arbitrary input (from the run payload) to a valid intensity. */
|
|
8
|
+
export function asQaIntensity(v) {
|
|
9
|
+
return v === 'quick' || v === 'deep' || v === 'standard' ? v : DEFAULT_QA_INTENSITY;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Prompt directive: tell the agent its STEP budget so it paces and ALWAYS wraps
|
|
13
|
+
* up with a report before the ceiling. The `--max-turns` backstop is the hard
|
|
14
|
+
* limit; this prose is what guarantees a report.
|
|
15
|
+
*/
|
|
16
|
+
export function qaBudgetDirective(intensity) {
|
|
17
|
+
const spec = QA_INTENSITY[intensity];
|
|
18
|
+
const wrapAt = Math.max(5, spec.maxSteps - Math.ceil(spec.maxSteps * 0.1));
|
|
19
|
+
return (`RUN BUDGET — ${spec.label}: ${spec.blurb}. You have about ${spec.maxSteps} steps ` +
|
|
20
|
+
`(tool actions) this run, enforced. Pace yourself to fit: cover the most ` +
|
|
21
|
+
`important flows FIRST. By roughly step ${wrapAt}, STOP exploring and ` +
|
|
22
|
+
`immediately WRITE YOUR FINDINGS REPORT (and record any clean candidate flows) ` +
|
|
23
|
+
`while you still can — never end a run without a report. On Quick, be decisive ` +
|
|
24
|
+
`and favour breadth; on Deep, be exhaustive.`);
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SessionFinding } from '../sessions/sessions.js';
|
|
2
|
+
export interface QaReportInput {
|
|
3
|
+
prompt: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
findings: SessionFinding[];
|
|
6
|
+
endedAt: string;
|
|
7
|
+
targetUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Render the report Markdown (pure — exported for testing). */
|
|
10
|
+
export declare function renderQaReport(input: QaReportInput): string;
|
|
11
|
+
/** Write the QA report into the run's folder as `report.md`. Each run (incl.
|
|
12
|
+
* each phase of a two-pass run) has its own folder, so there's no name
|
|
13
|
+
* collision. NEVER throws; returns the path or an error string. */
|
|
14
|
+
export declare function writeQaReport(runDirPath: string, input: QaReportInput): Promise<{
|
|
15
|
+
path: string;
|
|
16
|
+
} | {
|
|
17
|
+
error: string;
|
|
18
|
+
}>;
|
|
19
|
+
//# sourceMappingURL=qaReport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qaReport.d.ts","sourceRoot":"","sources":["../../src/qa/qaReport.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAkB3D;AAED;;oEAEoE;AACpE,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAS/C"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA report artifact — the durable, human-readable output of a QA Testing run.
|
|
3
|
+
*
|
|
4
|
+
* QA is report-first: a run produces findings (rendered live in the chat's
|
|
5
|
+
* Findings card via the normal parseFindings pipeline) AND this persistent
|
|
6
|
+
* Markdown report under `<devRoot>/.hover/qa-reports/<slug>.md`, mirroring
|
|
7
|
+
* pentest's report file. Latest-run-wins per prompt slug (the session ledger
|
|
8
|
+
* keeps the full history; this is the readable artifact).
|
|
9
|
+
*
|
|
10
|
+
* Best-effort by contract: a report-write failure must NEVER break a run or the
|
|
11
|
+
* ledger (same rule as the session ledger + business memory).
|
|
12
|
+
*/
|
|
13
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
/** Render the report Markdown (pure — exported for testing). */
|
|
16
|
+
export function renderQaReport(input) {
|
|
17
|
+
const { prompt, summary, findings, endedAt, targetUrl } = input;
|
|
18
|
+
const meta = [endedAt, targetUrl, `${findings.length} finding${findings.length === 1 ? '' : 's'}`]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' · ');
|
|
21
|
+
const body = [`# QA report — ${prompt.trim()}`, '', `_${meta}_`];
|
|
22
|
+
if (summary.trim())
|
|
23
|
+
body.push('', summary.trim());
|
|
24
|
+
body.push('', '## Findings');
|
|
25
|
+
if (findings.length) {
|
|
26
|
+
for (const f of findings) {
|
|
27
|
+
const sev = (f.severity || 'note').trim();
|
|
28
|
+
const head = f.title && f.title !== f.text ? `${f.title} — ` : '';
|
|
29
|
+
body.push(`- **${sev}** — ${head}${f.text.trim()}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
body.push('_No issues found._');
|
|
34
|
+
}
|
|
35
|
+
return body.join('\n') + '\n';
|
|
36
|
+
}
|
|
37
|
+
/** Write the QA report into the run's folder as `report.md`. Each run (incl.
|
|
38
|
+
* each phase of a two-pass run) has its own folder, so there's no name
|
|
39
|
+
* collision. NEVER throws; returns the path or an error string. */
|
|
40
|
+
export async function writeQaReport(runDirPath, input) {
|
|
41
|
+
try {
|
|
42
|
+
await mkdir(runDirPath, { recursive: true });
|
|
43
|
+
const path = join(runDirPath, 'report.md');
|
|
44
|
+
await writeFile(path, renderQaReport(input), 'utf-8');
|
|
45
|
+
return { path };
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export declare const SESSION_RECORD_VERSION = 2;
|
|
2
|
+
/** One agent-reported finding (the ## Findings block), persisted so the
|
|
3
|
+
* ledger becomes a reusable findings log — not just a run-history list.
|
|
4
|
+
* Severity is the raw marker the agent emitted (Bug / Minor / Info / …);
|
|
5
|
+
* readers normalise it for display. */
|
|
6
|
+
export interface SessionFinding {
|
|
7
|
+
severity: string;
|
|
8
|
+
text: string;
|
|
9
|
+
/** Optional short headline (from the structured JSON findings block). */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Endpoint / method when the finding is about an API call — used to
|
|
12
|
+
* crystallize a request-based regression later. */
|
|
13
|
+
endpoint?: string;
|
|
14
|
+
method?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionRecord {
|
|
17
|
+
/** Bumped to 2 when the reproducibility + outcome fields below were added.
|
|
18
|
+
* Readers must tolerate v1 records (every new field is optional). */
|
|
19
|
+
version: number;
|
|
20
|
+
/** `<sanitized-startedAt>-<rand>` — the runId, generated at run start; names
|
|
21
|
+
* the run folder `.hover/runs/<conversationId>/<id>/` and is the meta.json id. */
|
|
22
|
+
id: string;
|
|
23
|
+
/** The chat conversation this run belongs to — the folder it's grouped under,
|
|
24
|
+
* so deleting a conversation removes all its runs. */
|
|
25
|
+
conversationId?: string;
|
|
26
|
+
startedAt: string;
|
|
27
|
+
endedAt: string;
|
|
28
|
+
/** Real wall-clock of the agent run (endedAt − startedAt in ms). The bare
|
|
29
|
+
* timestamps can collapse to ~0 for an instant failure; this is explicit. */
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
agent: string;
|
|
32
|
+
model?: string;
|
|
33
|
+
/** Active mode: null/absent = normal authoring, else 'api-test' / 'pentest'.
|
|
34
|
+
* A pentest record is a different artifact from a normal one. */
|
|
35
|
+
mode?: string | null;
|
|
36
|
+
prompt: string;
|
|
37
|
+
outcome: 'saved' | 'completed' | 'error' | 'aborted';
|
|
38
|
+
/** Why an error/aborted run ended — engine message, preflight failure,
|
|
39
|
+
* budget cutoff, user cancel. Makes a failed record diagnostic. */
|
|
40
|
+
errorReason?: string;
|
|
41
|
+
/** The agent's final verification prose (the Result card body), minus the
|
|
42
|
+
* Findings block. Searchable history + context, not just the prompt. */
|
|
43
|
+
summary?: string;
|
|
44
|
+
/** Parsed ## Findings — the run's actual product output. */
|
|
45
|
+
findings?: SessionFinding[];
|
|
46
|
+
/** Per-tool call counts (browser_snapshot → 12, browser_click → 8). Explains
|
|
47
|
+
* cost and feeds optimization targeting. */
|
|
48
|
+
toolCounts?: Record<string, number>;
|
|
49
|
+
/** What this run drove. envId/envName come from the editor's environment
|
|
50
|
+
* store (Local vs a remote target); url is the active dev tab. The Cloud
|
|
51
|
+
* run layer keys flakiness + scheduling off these. */
|
|
52
|
+
target?: {
|
|
53
|
+
url?: string;
|
|
54
|
+
envId?: string;
|
|
55
|
+
envName?: string;
|
|
56
|
+
};
|
|
57
|
+
/** @account labels this run logged in with — LABELS ONLY, never the
|
|
58
|
+
* username/password (same contract as spec redaction). */
|
|
59
|
+
accountLabels?: string[];
|
|
60
|
+
/** Tag of the `.hover/screenshots/<tag>` dir this run wrote to, so the UI
|
|
61
|
+
* can open the run's artifacts. (Distinct from `id` because the screenshot
|
|
62
|
+
* dir is named at MCP-launch time, before the record id exists.) */
|
|
63
|
+
screenshotTag?: string;
|
|
64
|
+
/** Chaining hook (reserved for Cloud): the prior turn's session id when this
|
|
65
|
+
* was a `--resume` follow-up, so a multi-turn conversation links as one. */
|
|
66
|
+
resumeOf?: string;
|
|
67
|
+
/** Set when the session was crystallized into a spec. */
|
|
68
|
+
specSlug?: string;
|
|
69
|
+
turns?: number;
|
|
70
|
+
costUsd?: number;
|
|
71
|
+
/** Total tokens consumed (input + output + cache) — the raw-usage counterpart
|
|
72
|
+
* to costUsd, surfaced by the widget/dashboard for users who track tokens
|
|
73
|
+
* rather than dollars. */
|
|
74
|
+
tokensUsed?: number;
|
|
75
|
+
stepCount: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Strip leaked function-call syntax a model sometimes emits as TEXT instead of
|
|
79
|
+
* actually invoking the tool — e.g. a final summary that ends with
|
|
80
|
+
* `call\n<invoke name="mcp__playwright__browser_wait_for">…</invoke>`. The model
|
|
81
|
+
* "writes out" the call (a known tool-calling glitch, common at end-of-turn /
|
|
82
|
+
* budget cap) and the parser renders it verbatim into the report + Done card.
|
|
83
|
+
* This keeps user-facing prose about the APP, not Hover's tooling
|
|
84
|
+
* (REPORTING_DIRECTIVE). Defensive + total: any agent can trip this.
|
|
85
|
+
*/
|
|
86
|
+
export declare function stripToolCallNoise(text: string): string;
|
|
87
|
+
/** Markdown-forced: the agent emits a plain-markdown report (REPORTING_DIRECTIVE)
|
|
88
|
+
* — ONE outcome line, `- ` bullets, and an optional `## Findings` section with
|
|
89
|
+
* `- **severity** — text` items. Parse the summary + findings from that markdown
|
|
90
|
+
* only; a stray ```json block (a non-compliant agent) is stripped, never parsed
|
|
91
|
+
* for findings and never leaked. Pure + total — no Findings block yields none. */
|
|
92
|
+
export declare function parseFindings(summary: string): {
|
|
93
|
+
summary: string;
|
|
94
|
+
findings: SessionFinding[];
|
|
95
|
+
};
|
|
96
|
+
/** Count tool_use steps by tool name for the `toolCounts` field. */
|
|
97
|
+
export declare function tallyTools(steps: {
|
|
98
|
+
kind: string;
|
|
99
|
+
tool?: string;
|
|
100
|
+
}[]): Record<string, number>;
|
|
101
|
+
/** Write one session record as `<runDir>/meta.json`. The id (runId) + the
|
|
102
|
+
* conversation are decided by the caller at run start (so screenshots + report
|
|
103
|
+
* share the folder). NEVER throws; returns the path or an error string. */
|
|
104
|
+
export declare function writeSessionRecord(devRoot: string, conversationId: string, runId: string, rec: Omit<SessionRecord, 'version' | 'id' | 'conversationId'>): Promise<{
|
|
105
|
+
path: string;
|
|
106
|
+
id: string;
|
|
107
|
+
} | {
|
|
108
|
+
error: string;
|
|
109
|
+
}>;
|
|
110
|
+
/** List every run's meta.json across all conversations: `.hover/runs/<conv>/<run>/meta.json`.
|
|
111
|
+
* Best-effort; returns [] if no runs yet. */
|
|
112
|
+
export declare function listSessionRecords(devRoot: string): Promise<{
|
|
113
|
+
path: string;
|
|
114
|
+
rec: SessionRecord;
|
|
115
|
+
}[]>;
|
|
116
|
+
/**
|
|
117
|
+
* Mark the session that produced `promptText` as crystallized: find the most
|
|
118
|
+
* recent record matching the prompt that has no `specSlug` yet, set
|
|
119
|
+
* `outcome: 'saved'` + the slug. Save-as-spec arrives as a separate WS message
|
|
120
|
+
* after the run record was already written, so this is a patch, keyed on the
|
|
121
|
+
* prompt (the `user` seed step) — tolerant by design; a miss is a no-op.
|
|
122
|
+
* NEVER throws.
|
|
123
|
+
*/
|
|
124
|
+
export declare function markSessionSaved(devRoot: string, promptText: string, specSlug: string): Promise<void>;
|
|
125
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/sessions/sessions.ts"],"names":[],"mappings":"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"}
|