@hover-dev/core 0.2.3 → 0.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAC;AAsF3F,eAAO,MAAM,WAAW,EAAE,eAoHzB,CAAC"}
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAC;AAsF3F,eAAO,MAAM,WAAW,EAAE,eAyHzB,CAAC"}
@@ -120,7 +120,12 @@ export const claudeAgent = {
120
120
  if (block.type === 'tool_use') {
121
121
  const name = block.name ?? '';
122
122
  const tool = name.replace(/^mcp__playwright__/, '');
123
- out.push({ kind: 'tool_use', tool, input: block.input });
123
+ out.push({
124
+ kind: 'tool_use',
125
+ tool,
126
+ input: block.input,
127
+ costUsdSnapshot: s.runningCost,
128
+ });
124
129
  }
125
130
  else if (block.type === 'text') {
126
131
  const text = block.text?.trim();
@@ -128,12 +128,12 @@ export const codexAgent = {
128
128
  // `name`, fall back to `tool`. Same for input.
129
129
  const rawName = it.name ?? it.tool ?? '';
130
130
  const tool = rawName.replace(/^mcp__playwright__/, '').replace(/^mcp__hover-playwright__/, '');
131
- out.push({ kind: 'tool_use', tool, input: it.input ?? it.arguments });
131
+ out.push({ kind: 'tool_use', tool, input: it.input ?? it.arguments, costUsdSnapshot: s.runningCost });
132
132
  }
133
133
  else if (it.type === 'command_execution') {
134
134
  // We DISCOURAGED this in developer_instructions but the agent can
135
135
  // still try. Surface it so the user sees it happen.
136
- out.push({ kind: 'tool_use', tool: 'shell', input: { command: it.command } });
136
+ out.push({ kind: 'tool_use', tool: 'shell', input: { command: it.command }, costUsdSnapshot: s.runningCost });
137
137
  }
138
138
  return out;
139
139
  }
@@ -12,6 +12,10 @@ export interface DetectedAgent {
12
12
  /**
13
13
  * Scan PATH for every agent in the registry. Returns only the ones found,
14
14
  * in registry insertion order.
15
+ *
16
+ * Probes all agents in parallel — each `which`/`where` call is ~50-200ms
17
+ * and the registry's growth target is 7-10 agents, so serial would noticeably
18
+ * lag the first widget hello / agent-dropdown open.
15
19
  */
16
20
  export declare function detectAgents(): Promise<DetectedAgent[]>;
17
21
  export interface AgentAvailability {
@@ -27,7 +31,7 @@ export interface AgentAvailability {
27
31
  /**
28
32
  * Like `detectAgents`, but also includes registered-but-not-installed agents
29
33
  * so the widget can render them dimmed with an install hint. Order matches
30
- * the registry.
34
+ * the registry. Probes run in parallel — see `detectAgents`.
31
35
  */
32
36
  export declare function listAgentAvailability(): Promise<AgentAvailability[]>;
33
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/agents/detect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS3E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAE5F;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAO7D;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgB1E;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU1F"}
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/agents/detect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS3E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAE5F;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAO7D;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgB1E;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU1F"}
@@ -23,26 +23,30 @@ export async function resolveBinForAgent(descriptor) {
23
23
  /**
24
24
  * Scan PATH for every agent in the registry. Returns only the ones found,
25
25
  * in registry insertion order.
26
+ *
27
+ * Probes all agents in parallel — each `which`/`where` call is ~50-200ms
28
+ * and the registry's growth target is 7-10 agents, so serial would noticeably
29
+ * lag the first widget hello / agent-dropdown open.
26
30
  */
27
31
  export async function detectAgents() {
28
- const detected = [];
29
- for (const descriptor of listAgents()) {
30
- const binPath = await resolveBinForAgent(descriptor);
31
- if (binPath)
32
- detected.push({ descriptor, binPath });
33
- }
34
- return detected;
32
+ const descriptors = listAgents();
33
+ const paths = await Promise.all(descriptors.map(d => resolveBinForAgent(d)));
34
+ return descriptors.flatMap((descriptor, i) => {
35
+ const binPath = paths[i];
36
+ return binPath ? [{ descriptor, binPath }] : [];
37
+ });
35
38
  }
36
39
  /**
37
40
  * Like `detectAgents`, but also includes registered-but-not-installed agents
38
41
  * so the widget can render them dimmed with an install hint. Order matches
39
- * the registry.
42
+ * the registry. Probes run in parallel — see `detectAgents`.
40
43
  */
41
44
  export async function listAgentAvailability() {
42
- const result = [];
43
- for (const descriptor of listAgents()) {
44
- const binPath = await resolveBinForAgent(descriptor);
45
- result.push({
45
+ const descriptors = listAgents();
46
+ const paths = await Promise.all(descriptors.map(d => resolveBinForAgent(d)));
47
+ return descriptors.map((descriptor, i) => {
48
+ const binPath = paths[i];
49
+ return {
46
50
  id: descriptor.id,
47
51
  label: descriptor.display.label,
48
52
  tagline: descriptor.display.tagline,
@@ -51,9 +55,8 @@ export async function listAgentAvailability() {
51
55
  binPath: binPath ?? undefined,
52
56
  homepage: descriptor.display.homepage,
53
57
  installHint: descriptor.display.installHint,
54
- });
55
- }
56
- return result;
58
+ };
59
+ });
57
60
  }
58
61
  /**
59
62
  * Pick the agent we should default to when the user / Vite plugin didn't
@@ -2,9 +2,11 @@ import type { InvokeEvent, InvokeOptions } from './types.js';
2
2
  /**
3
3
  * Spawn an agent and yield normalized InvokeEvents as they arrive.
4
4
  *
5
- * Caller is responsible for the lifecycle of `AsyncIterable`: iterating
6
- * to completion drains stdout; aborting via `break` will leave the child
7
- * running (use AbortController in a future iteration if needed).
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.
8
10
  */
9
11
  export declare function invokeAgent(opts: InvokeOptions): AsyncIterable<InvokeEvent>;
10
12
  //# sourceMappingURL=invoke.d.ts.map
@@ -1 +1 @@
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;;;;;;GAMG;AACH,wBAAuB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAAC,WAAW,CAAC,CAuElF"}
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,CAsElF"}
@@ -10,9 +10,11 @@ import { AgentNotInstalledError, UnsupportedAgentProtocolError, } from './types.
10
10
  /**
11
11
  * Spawn an agent and yield normalized InvokeEvents as they arrive.
12
12
  *
13
- * Caller is responsible for the lifecycle of `AsyncIterable`: iterating
14
- * to completion drains stdout; aborting via `break` will leave the child
15
- * running (use AbortController in a future iteration if needed).
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.
16
18
  */
17
19
  export async function* invokeAgent(opts) {
18
20
  const descriptor = getAgent(opts.agentId);
@@ -31,9 +33,6 @@ export async function* invokeAgent(opts) {
31
33
  // doesn't trip the nested-session guard. Harmless for other agents.
32
34
  env: { ...process.env, CLAUDECODE: '' },
33
35
  });
34
- // Wire abort: when the caller signals (e.g. WS disconnect), terminate the
35
- // child so we don't leak orphan agent processes that keep driving the
36
- // browser after the user has navigated away.
37
36
  const onAbort = () => {
38
37
  if (!child.killed)
39
38
  child.kill('SIGTERM');
@@ -50,35 +49,39 @@ export async function* invokeAgent(opts) {
50
49
  }
51
50
  const rl = createInterface({ input: child.stdout });
52
51
  const exitPromise = new Promise(res => child.on('exit', c => res(c ?? -1)));
53
- // Fresh parser state per invocation. Threaded into both parseEvent and
54
- // onStreamEnd so descriptors don't have to reach for module globals
55
- // (which would smear across concurrent invocations).
56
52
  const state = {};
57
53
  let sawSessionEnd = false;
58
- for await (const line of rl) {
59
- for (const ev of descriptor.parseEvent(line, state)) {
60
- if (ev.kind === 'session_end')
61
- sawSessionEnd = true;
62
- yield ev;
54
+ try {
55
+ for await (const line of rl) {
56
+ for (const ev of descriptor.parseEvent(line, state)) {
57
+ if (ev.kind === 'session_end')
58
+ sawSessionEnd = true;
59
+ yield ev;
60
+ }
63
61
  }
64
- }
65
- const code = await exitPromise;
66
- opts.signal?.removeEventListener('abort', onAbort);
67
- if (!sawSessionEnd && !opts.signal?.aborted) {
68
- // Give the descriptor a chance to synthesize its own terminator from
69
- // accumulated state (codex does this its stream never emits a
70
- // session_end). Falls back to a generic error session_end if the
71
- // descriptor declines and the child exited non-zero.
72
- const synthetic = descriptor.onStreamEnd?.(code, state);
73
- if (synthetic) {
74
- yield synthetic;
75
- }
76
- else if (code !== 0) {
77
- yield {
78
- kind: 'session_end',
79
- isError: true,
80
- summary: `agent exited with code ${code}`,
81
- };
62
+ const code = await exitPromise;
63
+ if (!sawSessionEnd && !opts.signal?.aborted) {
64
+ // Give the descriptor a chance to synthesize its own terminator from
65
+ // accumulated state (codex does this — its stream never emits a
66
+ // session_end). Falls back to a generic error session_end if the
67
+ // descriptor declines and the child exited non-zero.
68
+ const synthetic = descriptor.onStreamEnd?.(code, state);
69
+ if (synthetic) {
70
+ yield synthetic;
71
+ }
72
+ else if (code !== 0) {
73
+ yield {
74
+ kind: 'session_end',
75
+ isError: true,
76
+ summary: `agent exited with code ${code}`,
77
+ };
78
+ }
82
79
  }
83
80
  }
81
+ finally {
82
+ rl.close();
83
+ if (!child.killed)
84
+ child.kill('SIGTERM');
85
+ opts.signal?.removeEventListener('abort', onAbort);
86
+ }
84
87
  }
@@ -51,6 +51,7 @@ export type InvokeEvent = {
51
51
  kind: 'tool_use';
52
52
  tool: string;
53
53
  input: unknown;
54
+ costUsdSnapshot?: number;
54
55
  } | {
55
56
  kind: 'tool_result';
56
57
  isError?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"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"}
@@ -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;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;AA8BD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAwDvF"}
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;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;AAiCD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAwDvF"}
@@ -75,8 +75,11 @@ function clearStaleProfileLock(dir) {
75
75
  try {
76
76
  unlinkSync(p);
77
77
  }
78
- catch {
79
- /* ignore */
78
+ catch (err) {
79
+ // Don't bail — Chrome may still launch despite a stale lock — but
80
+ // log so a downstream launch failure can be traced back to here.
81
+ const msg = err instanceof Error ? err.message : String(err);
82
+ console.warn(`[hover] couldn't clear ${p}: ${msg}`);
80
83
  }
81
84
  }
82
85
  }
@@ -1 +1 @@
1
- {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/playwright/preflight.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ1E;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,SAAO,GACf,OAAO,CAAC,kBAAkB,CAAC,CA4C7B"}
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/playwright/preflight.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ1E;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,SAAO,GACf,OAAO,CAAC,kBAAkB,CAAC,CAiD7B"}
@@ -59,9 +59,15 @@ export async function preflightCDP(cdpUrl, timeoutMs = 2000) {
59
59
  .map(t => ({ url: t.url ?? '', title: t.title, type: t.type }))
60
60
  .filter(t => t.url.length > 0);
61
61
  }
62
+ else {
63
+ // /json/version was healthy but /json/list wasn't — surface it so the
64
+ // agent's system prompt isn't silently built from an empty tab list.
65
+ console.warn(`[hover] CDP /json/list returned HTTP ${listRes.status}; agent tab hint will be empty`);
66
+ }
62
67
  }
63
- catch {
64
- // tab list is best-effort
68
+ catch (err) {
69
+ const msg = err instanceof Error ? err.message : String(err);
70
+ console.warn(`[hover] CDP /json/list failed: ${msg}; agent tab hint will be empty`);
65
71
  }
66
72
  return {
67
73
  ok: true,
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAmEA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;6EAGyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B;4EACwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAiDD,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAmT/E"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAmEA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;6EAGyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B;4EACwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAiDD,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAwV/E"}
package/dist/service.js CHANGED
@@ -141,6 +141,38 @@ export async function startService(opts) {
141
141
  }
142
142
  return agentAvailabilityCache;
143
143
  };
144
+ // Cache the CDP preflight result for a short window. preflightCDP() does
145
+ // two HTTP roundtrips to Chrome's debug endpoint (/json/version +
146
+ // /json/list); on a multi-turn session that's 100-300ms of latency before
147
+ // every follow-up — directly observable as a pause between hitting send
148
+ // and the agent starting work. A 5s TTL is comfortably shorter than the
149
+ // time it takes a user to type+send another message, but long enough that
150
+ // back-to-back commands skip the roundtrip. Failures are NOT cached so
151
+ // the user gets immediate feedback when they fix the underlying issue
152
+ // (e.g. start Chrome). Cache is invalidated whenever an invocation fails
153
+ // (defensive: if MCP somehow spawned its own Chromium we want the next
154
+ // preflight to re-probe).
155
+ const PREFLIGHT_TTL_MS = 5000;
156
+ let cachedPreflight = null;
157
+ let cachedPreflightAt = 0;
158
+ const getPreflight = async () => {
159
+ const now = Date.now();
160
+ if (cachedPreflight?.ok && now - cachedPreflightAt < PREFLIGHT_TTL_MS) {
161
+ return cachedPreflight;
162
+ }
163
+ const result = await preflightCDP(cdpUrl);
164
+ if (result.ok) {
165
+ cachedPreflight = result;
166
+ cachedPreflightAt = now;
167
+ }
168
+ else {
169
+ cachedPreflight = null;
170
+ }
171
+ return result;
172
+ };
173
+ const invalidatePreflight = () => {
174
+ cachedPreflight = null;
175
+ };
144
176
  const broadcastAgents = async () => {
145
177
  const available = await getAvailability(false);
146
178
  const payload = { current: currentAgentId, available };
@@ -292,7 +324,7 @@ export async function startService(opts) {
292
324
  // Playwright MCP server would silently launch its own Chromium —
293
325
  // and Hover's premise is to drive the user's existing Chrome (with
294
326
  // their dev state, cookies, devtools open), never spawn a fresh one.
295
- const cdp = await preflightCDP(cdpUrl);
327
+ const cdp = await getPreflight();
296
328
  if (!cdp.ok) {
297
329
  send(ws, {
298
330
  type: 'event',
@@ -375,6 +407,11 @@ export async function startService(opts) {
375
407
  if (ws.readyState === WebSocket.OPEN) {
376
408
  send(ws, { type: 'event', payload: errorEvent });
377
409
  }
410
+ // Force the next command to re-probe CDP. The error could be from
411
+ // Chrome dying, MCP spawning a stray Chromium, the user closing
412
+ // their debug window — anything that would make a cached "all
413
+ // healthy" result lie.
414
+ invalidatePreflight();
378
415
  }
379
416
  finally {
380
417
  busy = false;
@@ -5,7 +5,7 @@ export declare class SkillExistsError extends Error {
5
5
  }
6
6
  /**
7
7
  * Serialized message shape from the widget's localStorage. Matches the
8
- * `state.messages` schema in packages/vite-plugin/src/widget.js.
8
+ * `state.messages` schema in packages/widget-bootstrap/src/widget/client.js.
9
9
  */
10
10
  export interface SkillStep {
11
11
  kind: 'user' | 'system' | 'step' | 'ai' | 'done';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hover-dev/core",
3
- "version": "0.2.3",
3
+ "version": "0.3.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",