opendevbrowser 0.0.24 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/fingerprint/canary.d.ts.map +1 -1
  3. package/dist/{chunk-K2TEHJCV.js → chunk-AVQL6WAS.js} +2856 -694
  4. package/dist/chunk-AVQL6WAS.js.map +1 -0
  5. package/dist/{chunk-5I6TZRVS.js → chunk-GTTYIAI7.js} +1053 -474
  6. package/dist/chunk-GTTYIAI7.js.map +1 -0
  7. package/dist/cli/commands/daemon.d.ts +25 -0
  8. package/dist/cli/commands/daemon.d.ts.map +1 -1
  9. package/dist/cli/commands/inspiredesign.d.ts.map +1 -1
  10. package/dist/cli/commands/serve.d.ts +10 -13
  11. package/dist/cli/commands/serve.d.ts.map +1 -1
  12. package/dist/cli/commands/status.d.ts.map +1 -1
  13. package/dist/cli/daemon-client.d.ts +13 -0
  14. package/dist/cli/daemon-client.d.ts.map +1 -1
  15. package/dist/cli/daemon-commands.d.ts.map +1 -1
  16. package/dist/cli/daemon-status-policy.d.ts +6 -0
  17. package/dist/cli/daemon-status-policy.d.ts.map +1 -0
  18. package/dist/cli/daemon-status.d.ts +1 -0
  19. package/dist/cli/daemon-status.d.ts.map +1 -1
  20. package/dist/cli/daemon.d.ts +12 -2
  21. package/dist/cli/daemon.d.ts.map +1 -1
  22. package/dist/cli/help.d.ts.map +1 -1
  23. package/dist/cli/index.js +188 -108
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/remote-manager.d.ts +8 -6
  26. package/dist/cli/remote-manager.d.ts.map +1 -1
  27. package/dist/cli/utils/http.d.ts.map +1 -1
  28. package/dist/daemon-fingerprint.json +3 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +147 -46
  31. package/dist/index.js.map +1 -1
  32. package/dist/inspiredesign/brief-expansion.d.ts +41 -0
  33. package/dist/inspiredesign/brief-expansion.d.ts.map +1 -0
  34. package/dist/inspiredesign/handoff.d.ts +3 -1
  35. package/dist/inspiredesign/handoff.d.ts.map +1 -1
  36. package/dist/opendevbrowser.d.ts.map +1 -1
  37. package/dist/opendevbrowser.js +147 -46
  38. package/dist/opendevbrowser.js.map +1 -1
  39. package/dist/providers/inspiredesign-capture-mode.d.ts +3 -0
  40. package/dist/providers/inspiredesign-capture-mode.d.ts.map +1 -0
  41. package/dist/providers/inspiredesign-capture.d.ts +8 -1
  42. package/dist/providers/inspiredesign-capture.d.ts.map +1 -1
  43. package/dist/providers/inspiredesign-contract.d.ts +30 -0
  44. package/dist/providers/inspiredesign-contract.d.ts.map +1 -1
  45. package/dist/providers/renderer.d.ts +2 -1
  46. package/dist/providers/renderer.d.ts.map +1 -1
  47. package/dist/providers/workflows.d.ts +2 -0
  48. package/dist/providers/workflows.d.ts.map +1 -1
  49. package/dist/{providers-6YVHKTOJ.js → providers-T2FQJCF6.js} +2 -2
  50. package/dist/tools/index.d.ts.map +1 -1
  51. package/dist/tools/inspiredesign_run.d.ts.map +1 -1
  52. package/dist/tools/status.d.ts.map +1 -1
  53. package/extension/manifest.json +1 -1
  54. package/package.json +1 -1
  55. package/skills/opendevbrowser-best-practices/SKILL.md +3 -2
  56. package/skills/opendevbrowser-design-agent/SKILL.md +1 -0
  57. package/skills/opendevbrowser-design-agent/assets/templates/inspiredesign-advanced-brief.v1.json +1370 -0
  58. package/skills/opendevbrowser-design-agent/scripts/validate-skill-assets.sh +2 -0
  59. package/dist/chunk-5I6TZRVS.js.map +0 -1
  60. package/dist/chunk-K2TEHJCV.js.map +0 -1
  61. /package/dist/{providers-6YVHKTOJ.js.map → providers-T2FQJCF6.js.map} +0 -0
@@ -0,0 +1,41 @@
1
+ import type { CanvasNavigationModel, CanvasThemeStrategy, CanvasVisualDirectionProfile } from "../canvas/types";
2
+ type InspiredesignBriefFormatRoute = {
3
+ profile: CanvasVisualDirectionProfile;
4
+ themeStrategy: CanvasThemeStrategy;
5
+ navigationModel: CanvasNavigationModel;
6
+ layoutApproach: string;
7
+ };
8
+ export type InspiredesignBriefFormat = {
9
+ id: string;
10
+ label: string;
11
+ bestFor: string[];
12
+ businessFocus: string[];
13
+ keywords: string[];
14
+ archetype: string;
15
+ layoutArchetype: string;
16
+ typographySystem: string;
17
+ surfaceTreatment: string;
18
+ shapeLanguage: string;
19
+ componentGrammar: string;
20
+ motionGrammar: string;
21
+ paletteIntent: string;
22
+ visualDensity: string;
23
+ designVariance: string;
24
+ responsiveCollapseRules: string[];
25
+ guardrails: string[];
26
+ antiPatterns: string[];
27
+ deliverables: string[];
28
+ route: InspiredesignBriefFormatRoute;
29
+ };
30
+ export type InspiredesignBriefExpansion = {
31
+ sourceBrief: string;
32
+ advancedBrief: string;
33
+ templateVersion: string;
34
+ format: InspiredesignBriefFormat;
35
+ };
36
+ export declare const INSPIREDESIGN_BRIEF_TEMPLATE_VERSION: string;
37
+ export declare const normalizeInspiredesignBriefText: (value: string) => string;
38
+ export declare const cloneInspiredesignBriefFormat: (format: InspiredesignBriefFormat) => InspiredesignBriefFormat;
39
+ export declare const expandInspiredesignBrief: (brief: string, preferredFormatId?: string) => InspiredesignBriefExpansion;
40
+ export {};
41
+ //# sourceMappingURL=brief-expansion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief-expansion.d.ts","sourceRoot":"","sources":["../../src/inspiredesign/brief-expansion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AAUzB,KAAK,6BAA6B,GAAG;IACnC,OAAO,EAAE,4BAA4B,CAAC;IACtC,aAAa,EAAE,mBAAmB,CAAC;IACnC,eAAe,EAAE,qBAAqB,CAAC;IACvC,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAoCF,MAAM,MAAM,wBAAwB,GAAG;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,6BAA6B,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,wBAAwB,CAAC;CAClC,CAAC;AAGF,eAAO,MAAM,oCAAoC,QAAyB,CAAC;AAE3E,eAAO,MAAM,+BAA+B,GAAI,OAAO,MAAM,KAAG,MAA2C,CAAC;AAqB5G,eAAO,MAAM,6BAA6B,GACxC,QAAQ,wBAAwB,KAC/B,wBAqBD,CAAC;AAkJH,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,EACb,oBAAoB,MAAM,KACzB,2BASF,CAAC"}
@@ -1,5 +1,6 @@
1
1
  export declare const INSPIREDESIGN_HANDOFF_FILES: {
2
2
  readonly designMarkdown: "design.md";
3
+ readonly advancedBrief: "advanced-brief.md";
3
4
  readonly designContract: "design-contract.json";
4
5
  readonly canvasPlanRequest: "canvas-plan.request.json";
5
6
  readonly designAgentHandoff: "design-agent-handoff.json";
@@ -26,8 +27,9 @@ export declare const INSPIREDESIGN_HANDOFF_COMMANDS: {
26
27
  };
27
28
  export declare const INSPIREDESIGN_HANDOFF_RECOMMENDED_SKILLS: readonly [string, string];
28
29
  export declare const INSPIREDESIGN_HANDOFF_GUIDANCE: {
30
+ readonly reviewAdvancedBrief: "advanced-brief.md is the authoritative prompt route for the selected format, profile defaults, layout posture, motion grammar, and anti-patterns. Read it before touching Canvas or implementation files.";
29
31
  readonly prepareCanvasPlanRequest: "Fill canvasSessionId, leaseId, and documentId in canvas-plan.request.json before running opendevbrowser canvas --command canvas.plan.set --params-file ./canvas-plan.request.json.";
30
- readonly deepCaptureRecommendation: "Rerun inspiredesign with captureMode=deep only when you need richer evidence for visual hierarchy, protected references, or capture-specific debugging.";
32
+ readonly deepCaptureRecommendation: "Any inspiredesign run with reference URLs already uses captureMode=deep. Rerun with the same URLs only when you need refreshed DOM/layout evidence, restored session state, or capture-specific debugging.";
31
33
  };
32
34
  export declare const buildInspiredesignFollowthroughSummary: () => string;
33
35
  export declare const buildInspiredesignNextStep: () => string;
@@ -1 +1 @@
1
- {"version":3,"file":"handoff.d.ts","sourceRoot":"","sources":["../../src/inspiredesign/handoff.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B;;;;;;;;;;CAU9B,CAAC;AAEX,eAAO,MAAM,4BAA4B;;;;;;;;;CAS/B,CAAC;AAaX,eAAO,MAAM,8BAA8B;;;;CAIjC,CAAC;AAEX,eAAO,MAAM,wCAAwC,2BAG3C,CAAC;AAEX,eAAO,MAAM,8BAA8B;;;CAGjC,CAAC;AAEX,eAAO,MAAM,sCAAsC,QAAO,MAEzD,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,MAE7C,CAAC"}
1
+ {"version":3,"file":"handoff.d.ts","sourceRoot":"","sources":["../../src/inspiredesign/handoff.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B;;;;;;;;;;;CAW9B,CAAC;AAEX,eAAO,MAAM,4BAA4B;;;;;;;;;CAS/B,CAAC;AAaX,eAAO,MAAM,8BAA8B;;;;CAIjC,CAAC;AAEX,eAAO,MAAM,wCAAwC,2BAG3C,CAAC;AAEX,eAAO,MAAM,8BAA8B;;;;CAIjC,CAAC;AAEX,eAAO,MAAM,sCAAsC,QAAO,MAEzD,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,MAE7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAuClD,QAAA,MAAM,oBAAoB,EAAE,MAsQ3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA8ClD,QAAA,MAAM,oBAAoB,EAAE,MAmW3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  AGENT_INBOX_SYSTEM_MARKER,
3
+ DEFAULT_DAEMON_STATUS_FETCH_OPTIONS,
4
+ DEFAULT_WORKFLOW_TRANSPORT_TIMEOUT_MS,
3
5
  DaemonClient,
4
6
  ScriptRunner,
5
7
  buildAnnotateResult,
@@ -10,19 +12,22 @@ import {
10
12
  classifySessionRelayEndpoint,
11
13
  createAutomationCoordinator,
12
14
  createCoreRuntimeAssemblies,
15
+ createDaemonStopHeaders,
13
16
  createOpenDevBrowserCore,
14
17
  executeMacroWithRuntime,
15
18
  extractExtension,
19
+ fetchDaemonStatus,
16
20
  fetchDaemonStatusFromMetadata,
17
- getCurrentDaemonFingerprint,
21
+ fetchWithTimeout,
18
22
  inspectSession,
23
+ isCurrentDaemonFingerprint,
19
24
  onboarding_metadata_default,
20
25
  readDaemonMetadata,
21
26
  requireChallengeOrchestrationConfig,
22
27
  resolveBundledProviderRuntime,
23
28
  resolveSessionRelayRoute,
24
29
  startDaemon
25
- } from "./chunk-5I6TZRVS.js";
30
+ } from "./chunk-GTTYIAI7.js";
26
31
  import "./chunk-STGGGVYT.js";
27
32
  import "./chunk-3ILXPKSJ.js";
28
33
  import "./chunk-TBUCZX4A.js";
@@ -32,8 +37,9 @@ import {
32
37
  buildBlockerArtifacts,
33
38
  buildMacroResolveSuccessHandoff,
34
39
  classifyBlockerSignal,
35
- createRequestId
36
- } from "./chunk-K2TEHJCV.js";
40
+ createRequestId,
41
+ resolveInspiredesignCaptureMode
42
+ } from "./chunk-AVQL6WAS.js";
37
43
  import "./chunk-FUSXMW3G.js";
38
44
 
39
45
  // src/cli/remote-manager.ts
@@ -42,8 +48,18 @@ var RemoteManager = class {
42
48
  constructor(client) {
43
49
  this.client = client;
44
50
  }
45
- launch(options) {
46
- return this.client.call("session.launch", options);
51
+ callWithOptionalTimeout(name, params, timeoutMs) {
52
+ if (typeof timeoutMs === "number") {
53
+ return this.client.call(name, params, { timeoutMs });
54
+ }
55
+ return this.client.call(name, params);
56
+ }
57
+ launch(options, timeoutMs) {
58
+ return this.callWithOptionalTimeout(
59
+ "session.launch",
60
+ options,
61
+ timeoutMs
62
+ );
47
63
  }
48
64
  connect(options) {
49
65
  return this.client.call("session.connect", options);
@@ -66,20 +82,20 @@ var RemoteManager = class {
66
82
  status(sessionId) {
67
83
  return this.client.call("session.status", { sessionId });
68
84
  }
69
- cookieImport(sessionId, cookies, strict = true, requestId) {
70
- return this.client.call("session.cookieImport", {
85
+ cookieImport(sessionId, cookies, strict = true, requestId, timeoutMs) {
86
+ return this.callWithOptionalTimeout("session.cookieImport", {
71
87
  sessionId,
72
88
  cookies,
73
89
  strict,
74
90
  requestId
75
- });
91
+ }, timeoutMs);
76
92
  }
77
- cookieList(sessionId, urls, requestId) {
78
- return this.client.call("session.cookieList", {
93
+ cookieList(sessionId, urls, requestId, timeoutMs) {
94
+ return this.callWithOptionalTimeout("session.cookieList", {
79
95
  sessionId,
80
96
  ...urls && urls.length > 0 ? { urls } : {},
81
97
  requestId
82
- });
98
+ }, timeoutMs);
83
99
  }
84
100
  goto(sessionId, url, waitUntil = "load", timeoutMs = 3e4, _sessionOverride, targetId) {
85
101
  return this.client.call("nav.goto", {
@@ -107,14 +123,14 @@ var RemoteManager = class {
107
123
  ...typeof targetId === "string" ? { targetId } : {}
108
124
  });
109
125
  }
110
- snapshot(sessionId, mode, maxChars, cursor, targetId) {
111
- return this.client.call("nav.snapshot", {
126
+ snapshot(sessionId, mode, maxChars, cursor, targetId, timeoutMs) {
127
+ return this.callWithOptionalTimeout("nav.snapshot", {
112
128
  sessionId,
113
129
  mode,
114
130
  maxChars,
115
131
  cursor,
116
132
  ...typeof targetId === "string" ? { targetId } : {}
117
- });
133
+ }, timeoutMs);
118
134
  }
119
135
  click(sessionId, ref, targetId) {
120
136
  return this.client.call("interact.click", {
@@ -237,11 +253,19 @@ var RemoteManager = class {
237
253
  ...typeof targetId === "string" ? { targetId } : {}
238
254
  });
239
255
  }
240
- clonePage(sessionId, targetId) {
241
- return this.client.call("export.clonePage", {
256
+ clonePage(sessionId, targetId, timeoutMs) {
257
+ return this.callWithOptionalTimeout("export.clonePage", {
242
258
  sessionId,
243
259
  ...typeof targetId === "string" ? { targetId } : {}
244
- });
260
+ }, timeoutMs);
261
+ }
262
+ clonePageHtmlWithOptions(sessionId, targetId, options, timeoutMs) {
263
+ return this.callWithOptionalTimeout("export.clonePageHtml", {
264
+ sessionId,
265
+ ...typeof targetId === "string" ? { targetId } : {},
266
+ ...typeof options?.maxNodes === "number" ? { maxNodes: options.maxNodes } : {},
267
+ ...typeof options?.inlineStyles === "boolean" ? { inlineStyles: options.inlineStyles } : {}
268
+ }, timeoutMs);
245
269
  }
246
270
  cloneComponent(sessionId, ref, targetId) {
247
271
  return this.client.call("export.cloneComponent", {
@@ -1055,7 +1079,7 @@ function createStatusTool(deps) {
1055
1079
  let updateHint;
1056
1080
  let sessionStatus = null;
1057
1081
  if (hubEnabled) {
1058
- const daemonStatus = await fetchDaemonStatusFromMetadata();
1082
+ const daemonStatus = await fetchDaemonStatusFromMetadata(config, DEFAULT_DAEMON_STATUS_FETCH_OPTIONS);
1059
1083
  if (!daemonStatus) {
1060
1084
  return failure("Daemon not running. Start with `npx opendevbrowser serve`.", "status_failed");
1061
1085
  }
@@ -3168,7 +3192,7 @@ function createResearchRunTool(deps) {
3168
3192
  async execute(args) {
3169
3193
  try {
3170
3194
  const runtime = await resolveProviderRuntime(deps);
3171
- const { runResearchWorkflow } = await import("./providers-6YVHKTOJ.js");
3195
+ const { runResearchWorkflow } = await import("./providers-T2FQJCF6.js");
3172
3196
  const result = await runResearchWorkflow(runtime, {
3173
3197
  topic: args.topic,
3174
3198
  days: args.days,
@@ -3219,7 +3243,7 @@ function createShoppingRunTool(deps) {
3219
3243
  async execute(args) {
3220
3244
  try {
3221
3245
  const runtime = await resolveProviderRuntime(deps);
3222
- const { runShoppingWorkflow } = await import("./providers-6YVHKTOJ.js");
3246
+ const { runShoppingWorkflow } = await import("./providers-T2FQJCF6.js");
3223
3247
  const result = await runShoppingWorkflow(runtime, {
3224
3248
  query: args.query,
3225
3249
  providers: args.providers,
@@ -3288,7 +3312,7 @@ function createProductVideoRunTool(deps) {
3288
3312
  async execute(args) {
3289
3313
  try {
3290
3314
  const runtime = await resolveProviderRuntime(deps);
3291
- const { runProductVideoWorkflow } = await import("./providers-6YVHKTOJ.js");
3315
+ const { runProductVideoWorkflow } = await import("./providers-T2FQJCF6.js");
3292
3316
  const includeScreenshots = args.include_screenshots ?? true;
3293
3317
  const result = await runProductVideoWorkflow(runtime, {
3294
3318
  product_url: args.product_url,
@@ -3327,7 +3351,7 @@ function createInspiredesignRunTool(deps) {
3327
3351
  args: {
3328
3352
  brief: z66.string().min(1).describe("Inspiredesign brief"),
3329
3353
  urls: z66.array(z66.string()).optional().describe("Inspiration URLs to analyze"),
3330
- captureMode: captureModeSchema.optional().describe("Capture mode: off|deep"),
3354
+ captureMode: captureModeSchema.optional().describe("Capture mode: off|deep. Any URLs force deep."),
3331
3355
  includePrototypeGuidance: z66.boolean().optional().describe("Include prototype guidance output"),
3332
3356
  mode: modeSchema3.optional().describe("compact|json|md|context|path"),
3333
3357
  timeoutMs: z66.number().int().positive().optional().describe("Workflow timeout in milliseconds"),
@@ -3340,8 +3364,8 @@ function createInspiredesignRunTool(deps) {
3340
3364
  async execute(args) {
3341
3365
  try {
3342
3366
  const runtime = await resolveProviderRuntime(deps);
3343
- const { runInspiredesignWorkflow } = await import("./providers-6YVHKTOJ.js");
3344
- const captureMode = args.captureMode ?? "off";
3367
+ const { runInspiredesignWorkflow } = await import("./providers-T2FQJCF6.js");
3368
+ const captureMode = resolveInspiredesignCaptureMode(args.captureMode, args.urls);
3345
3369
  const cookieSource = deps.config.get().providers?.cookieSource;
3346
3370
  const result = await runInspiredesignWorkflow(runtime, {
3347
3371
  brief: args.brief,
@@ -3349,7 +3373,7 @@ function createInspiredesignRunTool(deps) {
3349
3373
  captureMode,
3350
3374
  includePrototypeGuidance: args.includePrototypeGuidance,
3351
3375
  mode: args.mode ?? "compact",
3352
- timeoutMs: args.timeoutMs,
3376
+ timeoutMs: args.timeoutMs ?? DEFAULT_WORKFLOW_TRANSPORT_TIMEOUT_MS,
3353
3377
  outputDir: args.outputDir,
3354
3378
  ttlHours: args.ttlHours,
3355
3379
  useCookies: args.useCookies,
@@ -3453,7 +3477,9 @@ function createTools(deps) {
3453
3477
  execute: async (args, context) => {
3454
3478
  try {
3455
3479
  await deps.ensureHub?.();
3456
- } catch {
3480
+ } catch (error) {
3481
+ const serialized = serializeError(error);
3482
+ return failure(serialized.message, serialized.code ?? "hub_unavailable", serialized.details);
3457
3483
  }
3458
3484
  return definition.execute(args, context);
3459
3485
  }
@@ -3623,6 +3649,69 @@ var OpenDevBrowserPlugin = async ({ directory, worktree }) => {
3623
3649
  toolDeps.providerRuntime = providerRuntime;
3624
3650
  toolDeps.browserFallbackPort = browserFallbackPort;
3625
3651
  };
3652
+ const readEnsureHubBudgetMs = (deadlineMs) => {
3653
+ const remainingMs = deadlineMs - Date.now();
3654
+ return remainingMs > 0 ? remainingMs : null;
3655
+ };
3656
+ const stopTimeoutMs = (deadlineMs) => {
3657
+ return Math.max(1, Math.min(500, readEnsureHubBudgetMs(deadlineMs) ?? 1));
3658
+ };
3659
+ const resolveHubStopConnection = (currentConfig, status) => {
3660
+ const metadata = readDaemonMetadata();
3661
+ if (metadata?.pid === status.pid) {
3662
+ return { port: metadata.port, token: metadata.token };
3663
+ }
3664
+ return { port: currentConfig.daemonPort, token: currentConfig.daemonToken };
3665
+ };
3666
+ const isConfiguredHubConnection = (currentConfig, connection) => {
3667
+ return connection.port === currentConfig.daemonPort && connection.token === currentConfig.daemonToken;
3668
+ };
3669
+ const waitForHubDaemonShutdown = async (connection, deadlineMs) => {
3670
+ while (readEnsureHubBudgetMs(deadlineMs)) {
3671
+ const status = await fetchDaemonStatus(connection.port, connection.token, {
3672
+ timeoutMs: stopTimeoutMs(deadlineMs)
3673
+ });
3674
+ if (!status?.ok) {
3675
+ return true;
3676
+ }
3677
+ await new Promise((resolve) => setTimeout(resolve, Math.min(100, stopTimeoutMs(deadlineMs))));
3678
+ }
3679
+ return false;
3680
+ };
3681
+ const stopMismatchedHubDaemon = async (currentConfig, status, deadlineMs) => {
3682
+ const connection = resolveHubStopConnection(currentConfig, status);
3683
+ const configuredConnection = isConfiguredHubConnection(currentConfig, connection);
3684
+ let response;
3685
+ try {
3686
+ response = await fetchWithTimeout(`http://127.0.0.1:${connection.port}/stop`, {
3687
+ method: "POST",
3688
+ headers: createDaemonStopHeaders(connection.token, "plugin.ensureHub.upgrade")
3689
+ }, stopTimeoutMs(deadlineMs));
3690
+ } catch (error) {
3691
+ if (!configuredConnection) {
3692
+ return;
3693
+ }
3694
+ throw error instanceof Error ? error : new Error(String(error));
3695
+ }
3696
+ if (response.status === 409) {
3697
+ if (!configuredConnection) {
3698
+ return;
3699
+ }
3700
+ throw new Error(`Hub daemon on 127.0.0.1:${connection.port} pid=${status.pid} is protected by a different opendevbrowser build.`);
3701
+ }
3702
+ if (!response.ok) {
3703
+ if (!configuredConnection) {
3704
+ return;
3705
+ }
3706
+ throw new Error(`Hub daemon stop failed with status ${response.status}.`);
3707
+ }
3708
+ if (!await waitForHubDaemonShutdown(connection, deadlineMs)) {
3709
+ if (!configuredConnection) {
3710
+ return;
3711
+ }
3712
+ throw new Error(`Timed out waiting for hub daemon on 127.0.0.1:${connection.port} to stop.`);
3713
+ }
3714
+ };
3626
3715
  const ensureHub = async () => {
3627
3716
  const currentConfig = configStore.get();
3628
3717
  if (!isHubEnabled(currentConfig)) {
@@ -3634,29 +3723,26 @@ var OpenDevBrowserPlugin = async ({ directory, worktree }) => {
3634
3723
  const deadline = Date.now() + 2e3;
3635
3724
  let attempt = 0;
3636
3725
  let lastError = null;
3637
- const currentFingerprint = getCurrentDaemonFingerprint();
3638
3726
  while (attempt < 2 && Date.now() < deadline) {
3639
3727
  attempt += 1;
3640
- const status = await fetchDaemonStatusFromMetadata(currentConfig);
3641
- if (status?.ok && status.fingerprint === currentFingerprint) {
3642
- bindRemote();
3643
- await relay?.refresh?.();
3644
- return;
3728
+ const statusTimeoutMs = readEnsureHubBudgetMs(deadline);
3729
+ if (!statusTimeoutMs) {
3730
+ break;
3645
3731
  }
3732
+ const status = await fetchDaemonStatusFromMetadata(currentConfig, {
3733
+ ...DEFAULT_DAEMON_STATUS_FETCH_OPTIONS,
3734
+ timeoutMs: statusTimeoutMs
3735
+ });
3646
3736
  if (status?.ok) {
3647
- const metadata = readDaemonMetadata();
3648
- const daemonPort = metadata?.port ?? currentConfig.daemonPort;
3649
- const daemonToken = metadata?.token ?? currentConfig.daemonToken;
3650
- if (daemonPort > 0 && daemonToken) {
3651
- try {
3652
- await fetch(`http://127.0.0.1:${daemonPort}/stop`, {
3653
- method: "POST",
3654
- headers: { Authorization: `Bearer ${daemonToken}` }
3655
- });
3656
- } catch (error) {
3657
- lastError = error instanceof Error ? error : new Error(String(error));
3658
- }
3737
+ if (isCurrentDaemonFingerprint(status.fingerprint)) {
3738
+ bindRemote();
3739
+ await relay?.refresh?.();
3740
+ return;
3659
3741
  }
3742
+ await stopMismatchedHubDaemon(currentConfig, status, deadline);
3743
+ }
3744
+ if (!readEnsureHubBudgetMs(deadline)) {
3745
+ break;
3660
3746
  }
3661
3747
  try {
3662
3748
  const { stop } = await startDaemon({ config: currentConfig, directory, worktree });
@@ -3664,6 +3750,22 @@ var OpenDevBrowserPlugin = async ({ directory, worktree }) => {
3664
3750
  } catch (error) {
3665
3751
  lastError = error instanceof Error ? error : new Error(String(error));
3666
3752
  }
3753
+ const refreshedTimeoutMs = readEnsureHubBudgetMs(deadline);
3754
+ if (!refreshedTimeoutMs) {
3755
+ break;
3756
+ }
3757
+ const refreshedStatus = await fetchDaemonStatusFromMetadata(currentConfig, {
3758
+ ...DEFAULT_DAEMON_STATUS_FETCH_OPTIONS,
3759
+ timeoutMs: refreshedTimeoutMs
3760
+ });
3761
+ if (refreshedStatus?.ok) {
3762
+ if (isCurrentDaemonFingerprint(refreshedStatus.fingerprint)) {
3763
+ bindRemote();
3764
+ await relay?.refresh?.();
3765
+ return;
3766
+ }
3767
+ await stopMismatchedHubDaemon(currentConfig, refreshedStatus, deadline);
3768
+ }
3667
3769
  if (Date.now() < deadline) {
3668
3770
  await new Promise((resolve) => setTimeout(resolve, 500));
3669
3771
  }
@@ -3676,7 +3778,6 @@ var OpenDevBrowserPlugin = async ({ directory, worktree }) => {
3676
3778
  toolDeps.ensureHub = ensureHub;
3677
3779
  const hubEnabled = isHubEnabled(config);
3678
3780
  if (hubEnabled) {
3679
- bindRemote();
3680
3781
  try {
3681
3782
  await ensureHub();
3682
3783
  } catch (error) {