opendevbrowser 0.0.25 → 0.0.27

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 (75) hide show
  1. package/README.md +7 -10
  2. package/dist/browser/canvas-manager.d.ts.map +1 -1
  3. package/dist/canvas/document-store.d.ts.map +1 -1
  4. package/dist/canvas/types.d.ts +3 -0
  5. package/dist/canvas/types.d.ts.map +1 -1
  6. package/dist/{chunk-7U63PZ4W.js → chunk-MWBDO2L5.js} +461 -189
  7. package/dist/chunk-MWBDO2L5.js.map +1 -0
  8. package/dist/{chunk-Z6ENAZUN.js → chunk-V5DJUSPV.js} +899 -115
  9. package/dist/chunk-V5DJUSPV.js.map +1 -0
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/commands/daemon.d.ts +27 -0
  12. package/dist/cli/commands/daemon.d.ts.map +1 -1
  13. package/dist/cli/commands/devtools/console-poll.d.ts.map +1 -1
  14. package/dist/cli/commands/devtools/network-poll.d.ts.map +1 -1
  15. package/dist/cli/commands/serve.d.ts +10 -13
  16. package/dist/cli/commands/serve.d.ts.map +1 -1
  17. package/dist/cli/commands/status.d.ts.map +1 -1
  18. package/dist/cli/commands/update.d.ts.map +1 -1
  19. package/dist/cli/daemon-autostart.d.ts +6 -2
  20. package/dist/cli/daemon-autostart.d.ts.map +1 -1
  21. package/dist/cli/daemon-client.d.ts.map +1 -1
  22. package/dist/cli/daemon-status-policy.d.ts +6 -0
  23. package/dist/cli/daemon-status-policy.d.ts.map +1 -0
  24. package/dist/cli/daemon-status.d.ts +1 -0
  25. package/dist/cli/daemon-status.d.ts.map +1 -1
  26. package/dist/cli/daemon.d.ts +5 -0
  27. package/dist/cli/daemon.d.ts.map +1 -1
  28. package/dist/cli/index.js +566 -154
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/cli/utils/http.d.ts.map +1 -1
  31. package/dist/cli/utils/parse.d.ts.map +1 -1
  32. package/dist/daemon-fingerprint.json +3 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +109 -28
  35. package/dist/index.js.map +1 -1
  36. package/dist/inspiredesign/brief-expansion.d.ts +3 -0
  37. package/dist/inspiredesign/brief-expansion.d.ts.map +1 -1
  38. package/dist/{providers/inspiredesign-capture-mode.d.ts → inspiredesign/capture-mode.d.ts} +2 -2
  39. package/dist/inspiredesign/capture-mode.d.ts.map +1 -0
  40. package/dist/{providers/inspiredesign-capture.d.ts → inspiredesign/capture.d.ts} +3 -3
  41. package/dist/inspiredesign/capture.d.ts.map +1 -0
  42. package/dist/{providers/inspiredesign-contract.d.ts → inspiredesign/contract.d.ts} +18 -5
  43. package/dist/inspiredesign/contract.d.ts.map +1 -0
  44. package/dist/inspiredesign/handoff.d.ts +1 -11
  45. package/dist/inspiredesign/handoff.d.ts.map +1 -1
  46. package/dist/inspiredesign/reference-pattern-board.d.ts +73 -0
  47. package/dist/inspiredesign/reference-pattern-board.d.ts.map +1 -0
  48. package/dist/opendevbrowser.d.ts.map +1 -1
  49. package/dist/opendevbrowser.js +109 -28
  50. package/dist/opendevbrowser.js.map +1 -1
  51. package/dist/providers/renderer.d.ts +1 -1
  52. package/dist/providers/renderer.d.ts.map +1 -1
  53. package/dist/providers/workflows.d.ts +7 -5
  54. package/dist/providers/workflows.d.ts.map +1 -1
  55. package/dist/{providers-CYEJZVXB.js → providers-TR3DUJZV.js} +2 -2
  56. package/dist/public-surface/generated-manifest.d.ts +3 -3
  57. package/dist/public-surface/generated-manifest.d.ts.map +1 -1
  58. package/dist/public-surface/source.d.ts +5 -4
  59. package/dist/public-surface/source.d.ts.map +1 -1
  60. package/dist/relay/protocol.d.ts +14 -2
  61. package/dist/relay/protocol.d.ts.map +1 -1
  62. package/dist/tools/index.d.ts.map +1 -1
  63. package/dist/tools/status.d.ts.map +1 -1
  64. package/extension/dist/canvas/canvas-runtime.js +13 -6
  65. package/extension/dist/services/ConnectionManager.js +8 -4
  66. package/extension/manifest.json +1 -1
  67. package/package.json +1 -1
  68. package/skills/opendevbrowser-best-practices/assets/templates/skill-runtime-pack-matrix.json +1 -1
  69. package/skills/opendevbrowser-design-agent/assets/templates/inspiredesign-advanced-brief.v1.json +67 -31
  70. package/dist/chunk-7U63PZ4W.js.map +0 -1
  71. package/dist/chunk-Z6ENAZUN.js.map +0 -1
  72. package/dist/providers/inspiredesign-capture-mode.d.ts.map +0 -1
  73. package/dist/providers/inspiredesign-capture.d.ts.map +0 -1
  74. package/dist/providers/inspiredesign-contract.d.ts.map +0 -1
  75. /package/dist/{providers-CYEJZVXB.js.map → providers-TR3DUJZV.js.map} +0 -0
@@ -47,7 +47,7 @@ import {
47
47
  runResearchWorkflow,
48
48
  runShoppingWorkflow,
49
49
  toSnippet
50
- } from "./chunk-Z6ENAZUN.js";
50
+ } from "./chunk-V5DJUSPV.js";
51
51
  import {
52
52
  ProviderRuntimeError
53
53
  } from "./chunk-FUSXMW3G.js";
@@ -22857,6 +22857,52 @@ function requirePlanEnumArray(record, key, path7, allowedValues, allowedSet, iss
22857
22857
  }
22858
22858
  return normalized;
22859
22859
  }
22860
+ function optionalPlanStringArray(record, key, path7, issues) {
22861
+ const value = record[key];
22862
+ if (value === void 0) {
22863
+ return void 0;
22864
+ }
22865
+ if (!Array.isArray(value)) {
22866
+ pushGenerationPlanIssue(issues, {
22867
+ path: path7,
22868
+ code: "invalid_type",
22869
+ message: `${path7} must include only non-empty strings.`,
22870
+ expected: "string[]",
22871
+ received: clone(value)
22872
+ });
22873
+ return void 0;
22874
+ }
22875
+ const strings = value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
22876
+ const normalized = uniqueStrings(strings);
22877
+ if (strings.length !== value.length || normalized.length === 0) {
22878
+ pushGenerationPlanIssue(issues, {
22879
+ path: path7,
22880
+ code: "invalid_type",
22881
+ message: `${path7} must include only non-empty strings.`,
22882
+ expected: "string[]",
22883
+ received: clone(value)
22884
+ });
22885
+ return void 0;
22886
+ }
22887
+ return normalized;
22888
+ }
22889
+ function optionalPlanRecord(record, key, path7, issues) {
22890
+ const value = record[key];
22891
+ if (value === void 0) {
22892
+ return void 0;
22893
+ }
22894
+ if (isRecord6(value)) {
22895
+ return clone(value);
22896
+ }
22897
+ pushGenerationPlanIssue(issues, {
22898
+ path: path7,
22899
+ code: "invalid_type",
22900
+ message: `${path7} must be an object.`,
22901
+ expected: "object",
22902
+ received: clone(value)
22903
+ });
22904
+ return void 0;
22905
+ }
22860
22906
  function requirePositiveNumber(record, key, path7, issues) {
22861
22907
  const value = record[key];
22862
22908
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
@@ -23015,6 +23061,9 @@ function validateGenerationPlan(plan) {
23015
23061
  issues
23016
23062
  ) : null;
23017
23063
  const maxInteractionLatencyMs = validationTargets ? requirePositiveNumber(validationTargets, "maxInteractionLatencyMs", "validationTargets.maxInteractionLatencyMs", issues) : null;
23064
+ const interactionMoments = optionalPlanStringArray(plan, "interactionMoments", "interactionMoments", issues);
23065
+ const materialEffects = optionalPlanStringArray(plan, "materialEffects", "materialEffects", issues);
23066
+ const designVectors = optionalPlanRecord(plan, "designVectors", "designVectors", issues);
23018
23067
  if (issues.length > 0 || missing.length > 0) {
23019
23068
  return {
23020
23069
  ok: false,
@@ -23063,7 +23112,10 @@ function validateGenerationPlan(plan) {
23063
23112
  requiredThemes,
23064
23113
  browserValidation,
23065
23114
  maxInteractionLatencyMs
23066
- }
23115
+ },
23116
+ ...interactionMoments ? { interactionMoments } : {},
23117
+ ...materialEffects ? { materialEffects } : {},
23118
+ ...designVectors ? { designVectors } : {}
23067
23119
  }
23068
23120
  };
23069
23121
  }
@@ -30338,6 +30390,7 @@ var CanvasManager = class {
30338
30390
  repoRoot,
30339
30391
  documentRepoPath: repoPath ?? null,
30340
30392
  leaseId,
30393
+ legacyLifecycleEventsTrusted: true,
30341
30394
  mode,
30342
30395
  usesCanvasRelay: false,
30343
30396
  store: new CanvasDocumentStore(document2),
@@ -30379,6 +30432,9 @@ var CanvasManager = class {
30379
30432
  attachMode
30380
30433
  );
30381
30434
  session.leaseId = attached.leaseId;
30435
+ if (attachMode === "lease_reclaim") {
30436
+ session.legacyLifecycleEventsTrusted = false;
30437
+ }
30382
30438
  return {
30383
30439
  clientId: attached.clientId,
30384
30440
  attachMode: attached.attachMode,
@@ -32824,8 +32880,16 @@ var CanvasManager = class {
32824
32880
  if (!session) {
32825
32881
  return;
32826
32882
  }
32883
+ const isLifecycleEvent = event.event === "canvas_session_closed" || event.event === "canvas_session_expired";
32827
32884
  const payload = isRecord14(event.payload) ? event.payload : null;
32828
32885
  if (!payload) {
32886
+ if (isLifecycleEvent && event.payload === void 0 && session.legacyLifecycleEventsTrusted) {
32887
+ this.completeFeedbackSubscriptions(session, event.event === "canvas_session_closed" ? "session_closed" : "document_unloaded");
32888
+ this.sessionSyncManager.removeSession(session.canvasSessionId);
32889
+ this.codeSyncManager.disposeSession(session.canvasSessionId);
32890
+ this.sessions.delete(session.canvasSessionId);
32891
+ this.disconnectCanvasClientIfIdle();
32892
+ }
32829
32893
  return;
32830
32894
  }
32831
32895
  if (event.event === "canvas_target_closed") {
@@ -32844,7 +32908,11 @@ var CanvasManager = class {
32844
32908
  }
32845
32909
  return;
32846
32910
  }
32847
- if (event.event === "canvas_session_closed" || event.event === "canvas_session_expired") {
32911
+ if (isLifecycleEvent) {
32912
+ const eventLeaseId = optionalString5(payload.leaseId);
32913
+ if (eventLeaseId !== session.leaseId) {
32914
+ return;
32915
+ }
32848
32916
  this.completeFeedbackSubscriptions(session, event.event === "canvas_session_closed" ? "session_closed" : "document_unloaded");
32849
32917
  this.sessionSyncManager.removeSession(session.canvasSessionId);
32850
32918
  this.codeSyncManager.disposeSession(session.canvasSessionId);
@@ -35991,12 +36059,129 @@ function createOpenDevBrowserCore(options) {
35991
36059
  };
35992
36060
  }
35993
36061
 
36062
+ // src/cli/utils/http.ts
36063
+ var DEFAULT_HTTP_TIMEOUT_MS = 5e3;
36064
+ function isAbortError(error) {
36065
+ if (!error || typeof error !== "object") return false;
36066
+ return "name" in error && error.name === "AbortError";
36067
+ }
36068
+ var resolveTimeoutMs = (timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) => {
36069
+ return Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_HTTP_TIMEOUT_MS;
36070
+ };
36071
+ var createTimeoutError = (timeoutMs) => {
36072
+ return new Error(`Request timed out after ${timeoutMs}ms`);
36073
+ };
36074
+ var createTimedSignal = (timeoutMs, upstreamSignal) => {
36075
+ const controller = new AbortController();
36076
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
36077
+ let removeAbortListener;
36078
+ if (upstreamSignal) {
36079
+ if (upstreamSignal.aborted) {
36080
+ controller.abort(upstreamSignal.reason);
36081
+ } else {
36082
+ const onAbort = () => controller.abort(upstreamSignal.reason);
36083
+ upstreamSignal.addEventListener("abort", onAbort, { once: true });
36084
+ removeAbortListener = () => upstreamSignal.removeEventListener("abort", onAbort);
36085
+ }
36086
+ }
36087
+ return {
36088
+ signal: controller.signal,
36089
+ dispose: () => {
36090
+ clearTimeout(timeoutId);
36091
+ removeAbortListener?.();
36092
+ }
36093
+ };
36094
+ };
36095
+ var cancelResponseBody = (response) => {
36096
+ try {
36097
+ const cancelResult = response.body?.cancel?.();
36098
+ if (cancelResult instanceof Promise) {
36099
+ void cancelResult.catch(() => {
36100
+ });
36101
+ }
36102
+ } catch {
36103
+ }
36104
+ };
36105
+ var readResponseBodyWithTimeout = async (response, signal, timeoutMs, reader) => {
36106
+ let bodyCancelled = false;
36107
+ const cancelBody = () => {
36108
+ if (bodyCancelled) {
36109
+ return;
36110
+ }
36111
+ bodyCancelled = true;
36112
+ cancelResponseBody(response);
36113
+ };
36114
+ if (signal.aborted) {
36115
+ cancelBody();
36116
+ throw createTimeoutError(timeoutMs);
36117
+ }
36118
+ let removeAbortListener;
36119
+ const abortPromise = new Promise((_, reject) => {
36120
+ const onAbort = () => {
36121
+ cancelBody();
36122
+ reject(createTimeoutError(timeoutMs));
36123
+ };
36124
+ signal.addEventListener("abort", onAbort, { once: true });
36125
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
36126
+ });
36127
+ try {
36128
+ return await Promise.race([reader(), abortPromise]);
36129
+ } catch (error) {
36130
+ if (signal.aborted || isAbortError(error)) {
36131
+ cancelBody();
36132
+ throw createTimeoutError(timeoutMs);
36133
+ }
36134
+ throw error;
36135
+ } finally {
36136
+ removeAbortListener?.();
36137
+ }
36138
+ };
36139
+ async function fetchWithTimeout(input, init = {}, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) {
36140
+ const resolvedTimeout = resolveTimeoutMs(timeoutMs);
36141
+ const timedSignal = createTimedSignal(resolvedTimeout, init?.signal ?? void 0);
36142
+ try {
36143
+ return await fetch(input, { ...init, signal: timedSignal.signal });
36144
+ } catch (error) {
36145
+ if (isAbortError(error) || timedSignal.signal.aborted) {
36146
+ throw createTimeoutError(resolvedTimeout);
36147
+ }
36148
+ throw error;
36149
+ } finally {
36150
+ timedSignal.dispose();
36151
+ }
36152
+ }
36153
+ async function fetchWithTimeoutContext(input, init = {}, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) {
36154
+ const resolvedTimeout = resolveTimeoutMs(timeoutMs);
36155
+ const timedSignal = createTimedSignal(resolvedTimeout, init?.signal ?? void 0);
36156
+ try {
36157
+ const response = await fetch(input, { ...init, signal: timedSignal.signal });
36158
+ return {
36159
+ response,
36160
+ signal: timedSignal.signal,
36161
+ timeoutMs: resolvedTimeout,
36162
+ dispose: timedSignal.dispose
36163
+ };
36164
+ } catch (error) {
36165
+ timedSignal.dispose();
36166
+ if (isAbortError(error) || timedSignal.signal.aborted) {
36167
+ throw createTimeoutError(resolvedTimeout);
36168
+ }
36169
+ throw error;
36170
+ }
36171
+ }
36172
+ async function readResponseTextWithTimeout(response, signal, timeoutMs) {
36173
+ return await readResponseBodyWithTimeout(response, signal, timeoutMs, () => response.text());
36174
+ }
36175
+ async function readResponseJsonWithTimeout(response, signal, timeoutMs) {
36176
+ return await readResponseBodyWithTimeout(response, signal, timeoutMs, () => response.json());
36177
+ }
36178
+
35994
36179
  // src/cli/daemon.ts
35995
36180
  import { createServer as createServer2 } from "http";
35996
36181
  import { createHash as createHash6, timingSafeEqual as timingSafeEqual2 } from "crypto";
35997
36182
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync6 } from "fs";
35998
36183
  import { homedir as homedir7 } from "os";
35999
- import { join as join16, resolve as resolve7 } from "path";
36184
+ import { basename as basename2, dirname as dirname8, join as join16, resolve as resolve7 } from "path";
36000
36185
  import { fileURLToPath as fileURLToPath2 } from "url";
36001
36186
 
36002
36187
  // src/cli/daemon-commands.ts
@@ -36236,7 +36421,7 @@ function getBoolean(value) {
36236
36421
  return typeof value === "boolean" ? value : null;
36237
36422
  }
36238
36423
 
36239
- // src/providers/inspiredesign-capture.ts
36424
+ // src/inspiredesign/capture.ts
36240
36425
  var INSPIREDESIGN_CAPTURE_TIMEOUT_MS = 3e4;
36241
36426
  var INSPIREDESIGN_CAPTURE_MAX_CHARS = 12e3;
36242
36427
  var ACTIVE_SESSION_COOKIE_REUSE_UNAVAILABLE_MESSAGE = "Deep capture only honors configured provider cookie sources; active session cookies are not reused.";
@@ -36247,7 +36432,14 @@ var DOM_CAPTURE_EMPTY_MESSAGE = "DOM capture returned empty HTML.";
36247
36432
  var SKIPPED_AFTER_TRANSPORT_TIMEOUT_SUFFIX = "transport timeout.";
36248
36433
  var createRemainingCaptureTimeout = (timeoutMs) => {
36249
36434
  const startedAtMs = Date.now();
36250
- return () => Math.max(1, timeoutMs - Math.max(0, Date.now() - startedAtMs));
36435
+ let firstRead = true;
36436
+ return () => {
36437
+ if (firstRead) {
36438
+ firstRead = false;
36439
+ return timeoutMs;
36440
+ }
36441
+ return Math.max(1, timeoutMs - Math.max(0, Date.now() - startedAtMs));
36442
+ };
36251
36443
  };
36252
36444
  var clampInspiredesignCaptureTimeout = (timeoutMs) => {
36253
36445
  if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) return INSPIREDESIGN_CAPTURE_TIMEOUT_MS;
@@ -37172,119 +37364,6 @@ var getBindingRenewConfig = () => ({
37172
37364
  waitMaxMs: WAIT_MAX_MS
37173
37365
  });
37174
37366
 
37175
- // src/cli/utils/http.ts
37176
- var DEFAULT_HTTP_TIMEOUT_MS = 5e3;
37177
- function isAbortError(error) {
37178
- if (!error || typeof error !== "object") return false;
37179
- return "name" in error && error.name === "AbortError";
37180
- }
37181
- var resolveTimeoutMs = (timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) => {
37182
- return Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_HTTP_TIMEOUT_MS;
37183
- };
37184
- var createTimeoutError = (timeoutMs) => {
37185
- return new Error(`Request timed out after ${timeoutMs}ms`);
37186
- };
37187
- var createTimedSignal = (timeoutMs, upstreamSignal) => {
37188
- const controller = new AbortController();
37189
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
37190
- let removeAbortListener;
37191
- if (upstreamSignal) {
37192
- if (upstreamSignal.aborted) {
37193
- controller.abort(upstreamSignal.reason);
37194
- } else {
37195
- const onAbort = () => controller.abort(upstreamSignal.reason);
37196
- upstreamSignal.addEventListener("abort", onAbort, { once: true });
37197
- removeAbortListener = () => upstreamSignal.removeEventListener("abort", onAbort);
37198
- }
37199
- }
37200
- return {
37201
- signal: controller.signal,
37202
- dispose: () => {
37203
- clearTimeout(timeoutId);
37204
- removeAbortListener?.();
37205
- }
37206
- };
37207
- };
37208
- var cancelResponseBody = (response) => {
37209
- try {
37210
- void response.body?.cancel?.();
37211
- } catch {
37212
- }
37213
- };
37214
- var readResponseBodyWithTimeout = async (response, signal, timeoutMs, reader) => {
37215
- let bodyCancelled = false;
37216
- const cancelBody = () => {
37217
- if (bodyCancelled) {
37218
- return;
37219
- }
37220
- bodyCancelled = true;
37221
- cancelResponseBody(response);
37222
- };
37223
- if (signal.aborted) {
37224
- cancelBody();
37225
- throw createTimeoutError(timeoutMs);
37226
- }
37227
- let removeAbortListener;
37228
- const abortPromise = new Promise((_, reject) => {
37229
- const onAbort = () => {
37230
- cancelBody();
37231
- reject(createTimeoutError(timeoutMs));
37232
- };
37233
- signal.addEventListener("abort", onAbort, { once: true });
37234
- removeAbortListener = () => signal.removeEventListener("abort", onAbort);
37235
- });
37236
- try {
37237
- return await Promise.race([reader(), abortPromise]);
37238
- } catch (error) {
37239
- if (signal.aborted || isAbortError(error)) {
37240
- cancelBody();
37241
- throw createTimeoutError(timeoutMs);
37242
- }
37243
- throw error;
37244
- } finally {
37245
- removeAbortListener?.();
37246
- }
37247
- };
37248
- async function fetchWithTimeout(input, init = {}, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) {
37249
- const resolvedTimeout = resolveTimeoutMs(timeoutMs);
37250
- const timedSignal = createTimedSignal(resolvedTimeout, init?.signal ?? void 0);
37251
- try {
37252
- return await fetch(input, { ...init, signal: timedSignal.signal });
37253
- } catch (error) {
37254
- if (isAbortError(error) || timedSignal.signal.aborted) {
37255
- throw createTimeoutError(resolvedTimeout);
37256
- }
37257
- throw error;
37258
- } finally {
37259
- timedSignal.dispose();
37260
- }
37261
- }
37262
- async function fetchWithTimeoutContext(input, init = {}, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) {
37263
- const resolvedTimeout = resolveTimeoutMs(timeoutMs);
37264
- const timedSignal = createTimedSignal(resolvedTimeout, init?.signal ?? void 0);
37265
- try {
37266
- const response = await fetch(input, { ...init, signal: timedSignal.signal });
37267
- return {
37268
- response,
37269
- signal: timedSignal.signal,
37270
- timeoutMs: resolvedTimeout,
37271
- dispose: timedSignal.dispose
37272
- };
37273
- } catch (error) {
37274
- timedSignal.dispose();
37275
- if (isAbortError(error) || timedSignal.signal.aborted) {
37276
- throw createTimeoutError(resolvedTimeout);
37277
- }
37278
- throw error;
37279
- }
37280
- }
37281
- async function readResponseTextWithTimeout(response, signal, timeoutMs) {
37282
- return await readResponseBodyWithTimeout(response, signal, timeoutMs, () => response.text());
37283
- }
37284
- async function readResponseJsonWithTimeout(response, signal, timeoutMs) {
37285
- return await readResponseBodyWithTimeout(response, signal, timeoutMs, () => response.json());
37286
- }
37287
-
37288
37367
  // src/cli/daemon-commands.ts
37289
37368
  var createDaemonWorkflowRuntime = (core, options) => resolveBundledProviderRuntime({
37290
37369
  existingRuntime: core.providerRuntime,
@@ -37570,7 +37649,7 @@ async function handleDaemonCommand(core, request) {
37570
37649
  return core.manager.snapshot(
37571
37650
  requireString3(params2.sessionId, "sessionId"),
37572
37651
  requireSnapshotMode2(params2.mode),
37573
- optionalNumber(params2.maxChars, "maxChars") ?? 16e3,
37652
+ optionalPositiveInteger2(params2.maxChars, "maxChars") ?? 16e3,
37574
37653
  optionalString6(params2.cursor),
37575
37654
  targetId
37576
37655
  );
@@ -37581,7 +37660,7 @@ async function handleDaemonCommand(core, request) {
37581
37660
  manager: core.manager,
37582
37661
  sessionId: requireString3(params2.sessionId, "sessionId"),
37583
37662
  targetId: optionalString6(params2.targetId),
37584
- maxChars: optionalNumber(params2.maxChars, "maxChars") ?? core.config.snapshot.maxChars,
37663
+ maxChars: optionalPositiveInteger2(params2.maxChars, "maxChars") ?? core.config.snapshot.maxChars,
37585
37664
  cursor: optionalString6(params2.cursor)
37586
37665
  });
37587
37666
  case "nav.reviewDesktop":
@@ -37709,7 +37788,7 @@ async function handleDaemonCommand(core, request) {
37709
37788
  return core.manager.domGetHtml(
37710
37789
  requireString3(params2.sessionId, "sessionId"),
37711
37790
  requireString3(params2.ref, "ref"),
37712
- optionalNumber(params2.maxChars, "maxChars") ?? 8e3,
37791
+ optionalPositiveInteger2(params2.maxChars, "maxChars") ?? 8e3,
37713
37792
  optionalString6(params2.targetId)
37714
37793
  );
37715
37794
  case "dom.getText":
@@ -37717,7 +37796,7 @@ async function handleDaemonCommand(core, request) {
37717
37796
  return core.manager.domGetText(
37718
37797
  requireString3(params2.sessionId, "sessionId"),
37719
37798
  requireString3(params2.ref, "ref"),
37720
- optionalNumber(params2.maxChars, "maxChars") ?? 8e3,
37799
+ optionalPositiveInteger2(params2.maxChars, "maxChars") ?? 8e3,
37721
37800
  optionalString6(params2.targetId)
37722
37801
  );
37723
37802
  case "dom.getAttr":
@@ -37841,15 +37920,15 @@ async function handleDaemonCommand(core, request) {
37841
37920
  await authorizeSessionCommand(core, params2, request.name, bindingId);
37842
37921
  return core.manager.consolePoll(
37843
37922
  requireString3(params2.sessionId, "sessionId"),
37844
- optionalNumber(params2.sinceSeq, "sinceSeq"),
37845
- optionalNumber(params2.max, "max") ?? 50
37923
+ optionalNonNegativeInteger(params2.sinceSeq, "sinceSeq"),
37924
+ optionalPositiveInteger2(params2.max, "max") ?? 50
37846
37925
  );
37847
37926
  case "devtools.networkPoll":
37848
37927
  await authorizeSessionCommand(core, params2, request.name, bindingId);
37849
37928
  return core.manager.networkPoll(
37850
37929
  requireString3(params2.sessionId, "sessionId"),
37851
- optionalNumber(params2.sinceSeq, "sinceSeq"),
37852
- optionalNumber(params2.max, "max") ?? 50
37930
+ optionalNonNegativeInteger(params2.sinceSeq, "sinceSeq"),
37931
+ optionalPositiveInteger2(params2.max, "max") ?? 50
37853
37932
  );
37854
37933
  case "devtools.debugTraceSnapshot": {
37855
37934
  await authorizeSessionCommand(core, params2, request.name, bindingId);
@@ -38715,6 +38794,15 @@ function optionalPositiveInteger2(value, label) {
38715
38794
  }
38716
38795
  throw new Error(`Invalid ${label}`);
38717
38796
  }
38797
+ function optionalNonNegativeInteger(value, label) {
38798
+ if (typeof value === "undefined") {
38799
+ return void 0;
38800
+ }
38801
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
38802
+ return value;
38803
+ }
38804
+ throw new Error(`Invalid ${label}`);
38805
+ }
38718
38806
  function requirePointerPoint(value, label) {
38719
38807
  const point = requireRecord2(value, label);
38720
38808
  return {
@@ -39020,6 +39108,11 @@ async function resolveMacroExpression(options, config, manager, browserFallbackP
39020
39108
 
39021
39109
  // src/cli/daemon.ts
39022
39110
  var DEFAULT_DAEMON_PORT2 = 8788;
39111
+ var DAEMON_STOP_DEBUG_ENV = "OPDEVBROWSER_DEBUG_DAEMON_STOP";
39112
+ var DAEMON_FINGERPRINT_FILE = "daemon-fingerprint.json";
39113
+ var DAEMON_STOP_REASON_HEADER = "x-opendevbrowser-stop-reason";
39114
+ var DAEMON_STOP_CLIENT_PID_HEADER = "x-opendevbrowser-stop-client-pid";
39115
+ var DAEMON_STOP_FINGERPRINT_HEADER = "x-opendevbrowser-stop-fingerprint";
39023
39116
  var RECOVERABLE_PLAYWRIGHT_TRANSPORT_ERRORS = [
39024
39117
  "Cannot find context with specified id",
39025
39118
  "Detached while handling command."
@@ -39076,23 +39169,58 @@ function hashFileContents(entryPath) {
39076
39169
  return "missing";
39077
39170
  }
39078
39171
  }
39172
+ function resolveDaemonFingerprintDistRoot(modulePath) {
39173
+ let currentDir = dirname8(modulePath);
39174
+ while (true) {
39175
+ if (basename2(currentDir) === "dist") {
39176
+ return currentDir;
39177
+ }
39178
+ const parentDir = dirname8(currentDir);
39179
+ if (parentDir === currentDir) {
39180
+ break;
39181
+ }
39182
+ currentDir = parentDir;
39183
+ }
39184
+ return null;
39185
+ }
39186
+ function readDaemonFingerprintArtifact(modulePath) {
39187
+ const distRoot = resolveDaemonFingerprintDistRoot(modulePath);
39188
+ if (distRoot === null) {
39189
+ return null;
39190
+ }
39191
+ try {
39192
+ const content = readFileSync5(join16(distRoot, DAEMON_FINGERPRINT_FILE), "utf-8");
39193
+ const payload = JSON.parse(content);
39194
+ if (typeof payload.fingerprint === "string" && payload.fingerprint.trim().length > 0) {
39195
+ return payload.fingerprint.trim();
39196
+ }
39197
+ } catch {
39198
+ }
39199
+ return null;
39200
+ }
39079
39201
  function getCurrentDaemonFingerprint(options = {}) {
39080
- const entryPath = resolveCurrentDaemonEntrypointPath(options);
39081
39202
  const modulePath = resolve7(fileURLToPath2(options.moduleUrl ?? import.meta.url));
39203
+ const sharedFingerprint = readDaemonFingerprintArtifact(modulePath);
39082
39204
  const fingerprintParts = [
39083
39205
  DAEMON_FINGERPRINT_VERSION,
39084
- process.execPath,
39085
- entryPath,
39086
- hashFileContents(entryPath)
39206
+ sharedFingerprint ?? hashFileContents(modulePath)
39087
39207
  ];
39088
- if (modulePath !== entryPath) {
39089
- fingerprintParts.push(modulePath, hashFileContents(modulePath));
39090
- }
39091
39208
  return createHash6("sha256").update(fingerprintParts.join("\n")).digest("hex");
39092
39209
  }
39093
39210
  function isCurrentDaemonFingerprint(fingerprint) {
39094
39211
  return typeof fingerprint === "string" && fingerprint === getCurrentDaemonFingerprint();
39095
39212
  }
39213
+ function createDaemonStopHeaders(token, reason) {
39214
+ const headers = {
39215
+ Authorization: `Bearer ${token}`,
39216
+ [DAEMON_STOP_FINGERPRINT_HEADER]: getCurrentDaemonFingerprint(),
39217
+ [DAEMON_STOP_REASON_HEADER]: reason
39218
+ };
39219
+ if (process.env[DAEMON_STOP_DEBUG_ENV] === "1") {
39220
+ headers[DAEMON_STOP_CLIENT_PID_HEADER] = String(process.pid);
39221
+ }
39222
+ return headers;
39223
+ }
39096
39224
  function resolveDaemonFingerprint(...candidates) {
39097
39225
  for (const candidate of candidates) {
39098
39226
  if (typeof candidate === "string" && candidate.trim().length > 0) {
@@ -39134,6 +39262,20 @@ function sendJson(response, status, payload) {
39134
39262
  });
39135
39263
  response.end(JSON.stringify(payload));
39136
39264
  }
39265
+ function logDaemonStopDebug(message, details) {
39266
+ if (process.env[DAEMON_STOP_DEBUG_ENV] !== "1") {
39267
+ return;
39268
+ }
39269
+ const suffix = details ? ` ${JSON.stringify(details)}` : "";
39270
+ console.error(`[daemon-stop-debug] ${message}${suffix}`);
39271
+ }
39272
+ function readSingleHeader(request, name) {
39273
+ const value = request.headers[name];
39274
+ if (typeof value === "string") {
39275
+ return value;
39276
+ }
39277
+ return null;
39278
+ }
39137
39279
  var isDaemonCommandRequest = (value) => {
39138
39280
  if (typeof value.name !== "string") {
39139
39281
  return false;
@@ -39185,8 +39327,20 @@ async function startDaemon(options = {}) {
39185
39327
  return;
39186
39328
  }
39187
39329
  if (request.method === "POST" && url.pathname === "/stop") {
39330
+ const stopFingerprint = readSingleHeader(request, DAEMON_STOP_FINGERPRINT_HEADER);
39331
+ logDaemonStopDebug("http.stop", {
39332
+ remoteAddress: request.socket.remoteAddress ?? null,
39333
+ remotePort: request.socket.remotePort ?? null,
39334
+ reason: readSingleHeader(request, DAEMON_STOP_REASON_HEADER),
39335
+ clientPid: readSingleHeader(request, DAEMON_STOP_CLIENT_PID_HEADER),
39336
+ fingerprintMatches: stopFingerprint === fingerprint
39337
+ });
39338
+ if (stopFingerprint !== fingerprint) {
39339
+ sendJson(response, 409, { ok: false, error: "Stale daemon stop request." });
39340
+ return;
39341
+ }
39188
39342
  sendJson(response, 200, { ok: true });
39189
- await stop();
39343
+ await stop("http.stop");
39190
39344
  return;
39191
39345
  }
39192
39346
  if (request.method === "POST" && url.pathname === "/command") {
@@ -39240,7 +39394,7 @@ async function startDaemon(options = {}) {
39240
39394
  return;
39241
39395
  }
39242
39396
  console.error(error);
39243
- void stop().finally(() => {
39397
+ void stop("uncaughtException").finally(() => {
39244
39398
  process.exitCode = 1;
39245
39399
  });
39246
39400
  };
@@ -39249,15 +39403,16 @@ async function startDaemon(options = {}) {
39249
39403
  return;
39250
39404
  }
39251
39405
  console.error(reason);
39252
- void stop().finally(() => {
39406
+ void stop("unhandledRejection").finally(() => {
39253
39407
  process.exitCode = 1;
39254
39408
  });
39255
39409
  };
39256
- const stop = async () => {
39410
+ const stop = async (reason = "unknown") => {
39257
39411
  if (stopping) {
39258
39412
  return;
39259
39413
  }
39260
39414
  stopping = true;
39415
+ logDaemonStopDebug("stop.begin", { reason });
39261
39416
  clearDaemonMetadata();
39262
39417
  clearBinding();
39263
39418
  process.off("SIGINT", sigintHandler);
@@ -39268,13 +39423,14 @@ async function startDaemon(options = {}) {
39268
39423
  await new Promise((resolve9) => {
39269
39424
  server.close(() => resolve9());
39270
39425
  });
39426
+ logDaemonStopDebug("stop.complete", { reason });
39271
39427
  };
39272
39428
  const sigintHandler = () => {
39273
- stop().catch(() => {
39429
+ void stop("SIGINT").catch(() => {
39274
39430
  });
39275
39431
  };
39276
39432
  const sigtermHandler = () => {
39277
- stop().catch(() => {
39433
+ void stop("SIGTERM").catch(() => {
39278
39434
  });
39279
39435
  };
39280
39436
  process.on("SIGINT", sigintHandler);
@@ -39348,7 +39504,15 @@ function resolveExitCode(result) {
39348
39504
  return result.success ? EXIT_SUCCESS : EXIT_EXECUTION;
39349
39505
  }
39350
39506
 
39507
+ // src/cli/daemon-status-policy.ts
39508
+ var DEFAULT_DAEMON_STATUS_FETCH_OPTIONS = {
39509
+ timeoutMs: 5e3,
39510
+ retryAttempts: 5,
39511
+ retryDelayMs: 250
39512
+ };
39513
+
39351
39514
  // src/cli/daemon-status.ts
39515
+ var DEFAULT_DAEMON_STATUS_TIMEOUT_MS = DEFAULT_DAEMON_STATUS_FETCH_OPTIONS.timeoutMs;
39352
39516
  var sleep2 = async (delayMs) => {
39353
39517
  if (!(Number.isFinite(delayMs) && delayMs > 0)) {
39354
39518
  return;
@@ -39361,17 +39525,66 @@ var resolveRetryAttempts = (retryAttempts) => {
39361
39525
  var resolveRetryDelayMs = (retryDelayMs) => {
39362
39526
  return typeof retryDelayMs === "number" && Number.isFinite(retryDelayMs) && retryDelayMs > 0 ? retryDelayMs : 0;
39363
39527
  };
39528
+ var resolveStatusTimeoutMs = (timeoutMs) => {
39529
+ return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_DAEMON_STATUS_TIMEOUT_MS;
39530
+ };
39531
+ var withFingerprintCurrent = (status) => ({
39532
+ ...status,
39533
+ fingerprintCurrent: isCurrentDaemonFingerprint(status.fingerprint)
39534
+ });
39535
+ var readRemainingBudgetMs = (deadlineMs) => {
39536
+ return Math.max(0, deadlineMs - Date.now());
39537
+ };
39538
+ var readSeedTimeoutMs = (remainingBudgetMs, remainingSeedCount) => {
39539
+ if (remainingSeedCount <= 1) {
39540
+ return remainingBudgetMs;
39541
+ }
39542
+ return Math.max(1, Math.floor(remainingBudgetMs / remainingSeedCount));
39543
+ };
39544
+ var resolveDaemonStatusSeeds = (metadata, config) => {
39545
+ const seeds = [];
39546
+ const seen = /* @__PURE__ */ new Set();
39547
+ const addSeed = (seed) => {
39548
+ if (!seed) {
39549
+ return;
39550
+ }
39551
+ const key = `${seed.port}:${seed.token}`;
39552
+ if (seen.has(key)) {
39553
+ return;
39554
+ }
39555
+ seen.add(key);
39556
+ seeds.push(seed);
39557
+ };
39558
+ addSeed(
39559
+ config.daemonPort > 0 && config.daemonToken ? {
39560
+ port: config.daemonPort,
39561
+ token: config.daemonToken,
39562
+ relayPort: config.relayPort
39563
+ } : null
39564
+ );
39565
+ addSeed(metadata);
39566
+ return seeds;
39567
+ };
39364
39568
  async function fetchDaemonStatus(port, token, options = {}) {
39365
39569
  const attempts = resolveRetryAttempts(options.retryAttempts);
39366
39570
  const retryDelayMs = resolveRetryDelayMs(options.retryDelayMs);
39367
39571
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
39368
39572
  try {
39369
- const response = await fetchWithTimeout(`http://127.0.0.1:${port}/status`, {
39573
+ const timedResponse = await fetchWithTimeoutContext(`http://127.0.0.1:${port}/status`, {
39370
39574
  method: "GET",
39371
39575
  headers: { Authorization: `Bearer ${token}` }
39372
39576
  }, options.timeoutMs);
39373
- if (response.ok) {
39374
- return await response.json();
39577
+ try {
39578
+ if (timedResponse.response.ok) {
39579
+ const status = await readResponseJsonWithTimeout(
39580
+ timedResponse.response,
39581
+ timedResponse.signal,
39582
+ timedResponse.timeoutMs
39583
+ );
39584
+ return withFingerprintCurrent(status);
39585
+ }
39586
+ } finally {
39587
+ timedResponse.dispose();
39375
39588
  }
39376
39589
  } catch {
39377
39590
  }
@@ -39385,32 +39598,34 @@ async function fetchDaemonStatusFromMetadata(config, options = {}) {
39385
39598
  const resolvedConfig = config ?? loadGlobalConfig();
39386
39599
  const attempts = resolveRetryAttempts(options.retryAttempts);
39387
39600
  const retryDelayMs = resolveRetryDelayMs(options.retryDelayMs);
39601
+ const deadlineMs = Date.now() + resolveStatusTimeoutMs(options.timeoutMs);
39388
39602
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
39389
39603
  const metadata = readDaemonMetadata();
39390
- if (metadata) {
39391
- const status = await fetchDaemonStatus(metadata.port, metadata.token, { timeoutMs: options.timeoutMs });
39392
- if (status?.ok) {
39393
- persistDaemonStatusMetadata(metadata, status, resolvedConfig);
39394
- return status;
39604
+ const seeds = resolveDaemonStatusSeeds(metadata, resolvedConfig);
39605
+ for (let seedIndex = 0; seedIndex < seeds.length; seedIndex += 1) {
39606
+ const seed = seeds[seedIndex];
39607
+ if (!seed) {
39608
+ continue;
39395
39609
  }
39396
- }
39397
- if (resolvedConfig.daemonPort > 0 && resolvedConfig.daemonToken) {
39398
- const status = await fetchDaemonStatus(resolvedConfig.daemonPort, resolvedConfig.daemonToken, {
39399
- timeoutMs: options.timeoutMs
39610
+ const remainingBudgetMs = readRemainingBudgetMs(deadlineMs);
39611
+ if (remainingBudgetMs <= 0) {
39612
+ return null;
39613
+ }
39614
+ const timeoutMs = readSeedTimeoutMs(remainingBudgetMs, seeds.length - seedIndex);
39615
+ const status = await fetchDaemonStatus(seed.port, seed.token, {
39616
+ timeoutMs
39400
39617
  });
39401
39618
  if (status?.ok) {
39402
- persistDaemonStatusMetadata({
39403
- port: resolvedConfig.daemonPort,
39404
- token: resolvedConfig.daemonToken,
39405
- pid: status.pid,
39406
- relayPort: status.relay.port ?? resolvedConfig.relayPort,
39407
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
39408
- }, status, resolvedConfig);
39619
+ persistDaemonStatusMetadata(seed, status, resolvedConfig);
39409
39620
  return status;
39410
39621
  }
39411
39622
  }
39412
39623
  if (attempt < attempts) {
39413
- await sleep2(retryDelayMs);
39624
+ const remainingBudgetMs = readRemainingBudgetMs(deadlineMs);
39625
+ if (remainingBudgetMs <= 0) {
39626
+ break;
39627
+ }
39628
+ await sleep2(Math.min(retryDelayMs, remainingBudgetMs));
39414
39629
  }
39415
39630
  }
39416
39631
  return null;
@@ -39456,6 +39671,13 @@ var DAEMON_RECOVERY_READY_TIMEOUT_MS = 5e3;
39456
39671
  var DAEMON_RESTART_READY_TIMEOUT_MS = 15e3;
39457
39672
  var DAEMON_RESTART_POLL_DELAY_MS = 250;
39458
39673
  var cachedClientState;
39674
+ var logDaemonStopDebug2 = (message, details) => {
39675
+ if (process.env[DAEMON_STOP_DEBUG_ENV] !== "1") {
39676
+ return;
39677
+ }
39678
+ const suffix = details ? ` ${JSON.stringify(details)}` : "";
39679
+ console.error(`[daemon-stop-debug] ${message}${suffix}`);
39680
+ };
39459
39681
  var getClientStateFilePath = () => {
39460
39682
  const cacheRoot = getCacheRoot();
39461
39683
  return join17(cacheRoot, CLIENT_ID_FILE);
@@ -39579,7 +39801,7 @@ var DaemonClient = class {
39579
39801
  this.maybeTrackLease(name, params2, result);
39580
39802
  return result;
39581
39803
  }
39582
- if (!options.requireBinding && isBindingRequiredError(error)) {
39804
+ if (isBindingRequiredError(error)) {
39583
39805
  if (this.binding) {
39584
39806
  this.clearBinding();
39585
39807
  }
@@ -39703,7 +39925,9 @@ var DaemonClient = class {
39703
39925
  }
39704
39926
  async callRaw(name, params2, timeoutMs) {
39705
39927
  const budget = createTimeoutBudget(timeoutMs);
39706
- const connection = await resolveDaemonConnection(budget);
39928
+ const connection = await resolveDaemonConnection(budget, {
39929
+ preferConfiguredRecovery: requiresConfiguredRecovery(name)
39930
+ });
39707
39931
  let timedResponse;
39708
39932
  try {
39709
39933
  timedResponse = await openDaemonCommand(
@@ -39711,7 +39935,7 @@ var DaemonClient = class {
39711
39935
  connection.token,
39712
39936
  name,
39713
39937
  params2,
39714
- readRemainingBudgetMs(budget)
39938
+ readRemainingBudgetMs2(budget)
39715
39939
  );
39716
39940
  } catch (error) {
39717
39941
  if (isTransportTimeoutError2(error)) {
@@ -39756,7 +39980,7 @@ var createTimeoutBudget = (timeoutMs) => {
39756
39980
  const resolved = asPositiveNumber(timeoutMs);
39757
39981
  return resolved === void 0 ? null : { timeoutMs: resolved, deadlineMs: Date.now() + resolved };
39758
39982
  };
39759
- var readRemainingBudgetMs = (budget) => {
39983
+ var readRemainingBudgetMs2 = (budget) => {
39760
39984
  if (!budget) {
39761
39985
  return void 0;
39762
39986
  }
@@ -39767,7 +39991,7 @@ var readRemainingBudgetMs = (budget) => {
39767
39991
  return remainingMs;
39768
39992
  };
39769
39993
  var capTimeoutToBudget = (timeoutMs, budget) => {
39770
- const remainingMs = readRemainingBudgetMs(budget);
39994
+ const remainingMs = readRemainingBudgetMs2(budget);
39771
39995
  return remainingMs === void 0 ? timeoutMs : Math.max(1, Math.min(timeoutMs, remainingMs));
39772
39996
  };
39773
39997
  var resolveReadyDeadlineMs = (readyTimeoutMs, budget) => {
@@ -39864,7 +40088,7 @@ var fetchCurrentDaemonStatus = async (connection, options, budget = null) => {
39864
40088
  return status;
39865
40089
  }
39866
40090
  if (attempt < attempts) {
39867
- await sleep3(Math.min(retryDelayMs, readRemainingBudgetMs(budget) ?? retryDelayMs));
40091
+ await sleep3(Math.min(retryDelayMs, readRemainingBudgetMs2(budget) ?? retryDelayMs));
39868
40092
  }
39869
40093
  }
39870
40094
  return null;
@@ -39880,7 +40104,7 @@ var fetchAnyDaemonStatus = async (connection, options, budget = null) => {
39880
40104
  return status;
39881
40105
  }
39882
40106
  if (attempt < attempts) {
39883
- await sleep3(Math.min(retryDelayMs, readRemainingBudgetMs(budget) ?? retryDelayMs));
40107
+ await sleep3(Math.min(retryDelayMs, readRemainingBudgetMs2(budget) ?? retryDelayMs));
39884
40108
  }
39885
40109
  }
39886
40110
  return null;
@@ -39891,6 +40115,9 @@ var sleep3 = async (delayMs) => {
39891
40115
  }
39892
40116
  await new Promise((resolve9) => setTimeout(resolve9, delayMs));
39893
40117
  };
40118
+ var requiresConfiguredRecovery = (name) => {
40119
+ return name === "canvas.execute" || name === "inspiredesign.run";
40120
+ };
39894
40121
  var getConfiguredDaemonConnection = () => {
39895
40122
  const config = loadGlobalConfig();
39896
40123
  if (!(config.daemonPort > 0 && config.daemonToken)) {
@@ -39912,7 +40139,7 @@ var persistResolvedDaemonStatus = (connection, status) => {
39912
40139
  };
39913
40140
  var persistCurrentConfiguredConnection = async (configuredConnection, status, staleMetadata) => {
39914
40141
  if (staleMetadata && !sameDaemonConnection(staleMetadata.connection, configuredConnection)) {
39915
- void stopDaemonConnection(staleMetadata.connection).catch(() => void 0);
40142
+ void stopDaemonConnection(staleMetadata.connection, null, "persistCurrentConfiguredConnection.staleMetadata").catch(() => void 0);
39916
40143
  }
39917
40144
  persistResolvedDaemonStatus(configuredConnection, status);
39918
40145
  return configuredConnection;
@@ -39921,7 +40148,7 @@ var resolveConfiguredPreferenceOptions = (budget) => {
39921
40148
  if (!budget) {
39922
40149
  return DAEMON_CONFIG_PREFER_OPTIONS;
39923
40150
  }
39924
- const remainingMs = readRemainingBudgetMs(budget);
40151
+ const remainingMs = readRemainingBudgetMs2(budget);
39925
40152
  if (remainingMs === void 0 || remainingMs <= 1) {
39926
40153
  return null;
39927
40154
  }
@@ -39943,14 +40170,27 @@ var resolveConfiguredPreferenceOptions = (budget) => {
39943
40170
  retryDelayMs: retryAttempts > 1 ? retryDelayMs : 0
39944
40171
  };
39945
40172
  };
39946
- var stopDaemonConnection = async (connection, budget = null) => {
40173
+ var stopDaemonConnection = async (connection, budget = null, reason = "unknown") => {
39947
40174
  const stopTimeoutMs = capTimeoutToBudget(DAEMON_RESTART_STATUS_TIMEOUT_MS, budget);
40175
+ logDaemonStopDebug2("client.stop.request", { reason, port: connection.port });
39948
40176
  try {
39949
- await fetchWithTimeout(`http://127.0.0.1:${connection.port}/stop`, {
40177
+ const response = await fetchWithTimeout(`http://127.0.0.1:${connection.port}/stop`, {
39950
40178
  method: "POST",
39951
- headers: { Authorization: `Bearer ${connection.token}` }
40179
+ headers: createDaemonStopHeaders(connection.token, reason)
39952
40180
  }, stopTimeoutMs);
40181
+ if (response.status === 409) {
40182
+ logDaemonStopDebug2("client.stop.fingerprintRejected", { reason, port: connection.port });
40183
+ return "fingerprint_rejected";
40184
+ }
40185
+ if (!response.ok) {
40186
+ logDaemonStopDebug2("client.stop.rejected", { reason, port: connection.port, status: response.status });
40187
+ return "unreachable";
40188
+ }
40189
+ logDaemonStopDebug2("client.stop.complete", { reason, port: connection.port });
40190
+ return "stopped";
39953
40191
  } catch {
40192
+ logDaemonStopDebug2("client.stop.error", { reason, port: connection.port });
40193
+ return "unreachable";
39954
40194
  }
39955
40195
  };
39956
40196
  function resolveDaemonRestartCommand(options = {}) {
@@ -40056,7 +40296,7 @@ var resolveMetadataConnection = async (metadataConnection, configuredConnection,
40056
40296
  }
40057
40297
  return { connection: metadataConnection, status };
40058
40298
  };
40059
- var resolveFreshDaemonConnection = async (budget = null) => {
40299
+ var resolveFreshDaemonConnection = async (budget = null, options = {}) => {
40060
40300
  const configuredConnection = getConfiguredDaemonConnection();
40061
40301
  if (!configuredConnection) {
40062
40302
  throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
@@ -40069,9 +40309,22 @@ var resolveFreshDaemonConnection = async (budget = null) => {
40069
40309
  if (currentConfiguredStatus?.ok) {
40070
40310
  return await persistCurrentConfiguredConnection(configuredConnection, currentConfiguredStatus, staleMetadata);
40071
40311
  }
40072
- if (staleMetadata?.status.ok && isCurrentDaemonFingerprint(staleMetadata.status.fingerprint)) {
40312
+ if (options.preferConfiguredRecovery && staleMetadata) {
40313
+ currentConfiguredStatus = await waitForCurrentDaemonStatus(
40314
+ configuredConnection,
40315
+ DAEMON_RECOVERY_READY_TIMEOUT_MS,
40316
+ budget
40317
+ );
40318
+ if (currentConfiguredStatus?.ok) {
40319
+ return await persistCurrentConfiguredConnection(configuredConnection, currentConfiguredStatus, staleMetadata);
40320
+ }
40321
+ if (!configuredStatus?.ok) {
40322
+ throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
40323
+ }
40324
+ }
40325
+ if (!options.preferConfiguredRecovery && staleMetadata?.status.ok && isCurrentDaemonFingerprint(staleMetadata.status.fingerprint)) {
40073
40326
  if (configuredStatus?.ok) {
40074
- void stopDaemonConnection(configuredConnection, budget).catch(() => void 0);
40327
+ void stopDaemonConnection(configuredConnection, budget, "resolveFreshDaemonConnection.configuredCurrentMetadataPreferred").catch(() => void 0);
40075
40328
  }
40076
40329
  return staleMetadata.connection;
40077
40330
  }
@@ -40084,13 +40337,11 @@ var resolveFreshDaemonConnection = async (budget = null) => {
40084
40337
  if (currentConfiguredStatus?.ok) {
40085
40338
  return await persistCurrentConfiguredConnection(configuredConnection, currentConfiguredStatus, staleMetadata);
40086
40339
  }
40340
+ throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
40087
40341
  }
40088
40342
  const staleConnections = [];
40089
40343
  if (configuredStatus?.ok) {
40090
- staleConnections.push(configuredConnection);
40091
- }
40092
- if (staleMetadata && !sameDaemonConnection(staleMetadata.connection, configuredConnection)) {
40093
- staleConnections.push(staleMetadata.connection);
40344
+ staleConnections.push({ connection: configuredConnection, status: configuredStatus });
40094
40345
  }
40095
40346
  if (staleConnections.length === 0) {
40096
40347
  const recoveringStatus = await waitForCurrentDaemonStatus(
@@ -40105,7 +40356,16 @@ var resolveFreshDaemonConnection = async (budget = null) => {
40105
40356
  throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
40106
40357
  }
40107
40358
  for (const staleConnection of staleConnections) {
40108
- await stopDaemonConnection(staleConnection, budget);
40359
+ const stopOutcome = await stopDaemonConnection(
40360
+ staleConnection.connection,
40361
+ budget,
40362
+ "resolveFreshDaemonConnection.staleConnections"
40363
+ );
40364
+ if (stopOutcome === "fingerprint_rejected") {
40365
+ throw createDisconnectedError(
40366
+ `Daemon on 127.0.0.1:${staleConnection.connection.port} pid=${staleConnection.status.pid} is protected by a different opendevbrowser build. Start with \`opendevbrowser serve\`.`
40367
+ );
40368
+ }
40109
40369
  }
40110
40370
  if (configuredStatus?.ok) {
40111
40371
  const shutdownOutcome = await waitForDaemonShutdown(configuredConnection, DAEMON_RECOVERY_READY_TIMEOUT_MS, budget);
@@ -40125,7 +40385,7 @@ var resolveFreshDaemonConnection = async (budget = null) => {
40125
40385
  persistResolvedDaemonStatus(configuredConnection, refreshedStatus);
40126
40386
  return configuredConnection;
40127
40387
  };
40128
- var resolveDaemonConnection = async (budget = null) => {
40388
+ var resolveDaemonConnection = async (budget = null, options = {}) => {
40129
40389
  const metadata = readDaemonMetadata();
40130
40390
  if (metadata && isCurrentDaemonFingerprint(metadata.fingerprint)) {
40131
40391
  const metadataConnection = { port: metadata.port, token: metadata.token };
@@ -40135,6 +40395,9 @@ var resolveDaemonConnection = async (budget = null) => {
40135
40395
  }
40136
40396
  const configuredOptions = resolveConfiguredPreferenceOptions(budget);
40137
40397
  if (!configuredOptions) {
40398
+ if (options.preferConfiguredRecovery) {
40399
+ return await resolveFreshDaemonConnection(budget, options);
40400
+ }
40138
40401
  return metadataConnection;
40139
40402
  }
40140
40403
  const configuredStatus = await fetchCurrentDaemonStatus(configuredConnection, configuredOptions, budget);
@@ -40145,13 +40408,18 @@ var resolveDaemonConnection = async (budget = null) => {
40145
40408
  { connection: metadataConnection }
40146
40409
  );
40147
40410
  }
40411
+ if (options.preferConfiguredRecovery) {
40412
+ return await resolveFreshDaemonConnection(budget, options);
40413
+ }
40148
40414
  return metadataConnection;
40149
40415
  }
40150
- return await resolveFreshDaemonConnection(budget);
40416
+ return await resolveFreshDaemonConnection(budget, options);
40151
40417
  };
40152
40418
  var retryWithRefreshedConnection = async (name, params2, budget) => {
40153
- const connection = await resolveFreshDaemonConnection(budget);
40154
- return await openDaemonCommand(connection.port, connection.token, name, params2, readRemainingBudgetMs(budget));
40419
+ const connection = await resolveFreshDaemonConnection(budget, {
40420
+ preferConfiguredRecovery: requiresConfiguredRecovery(name)
40421
+ });
40422
+ return await openDaemonCommand(connection.port, connection.token, name, params2, readRemainingBudgetMs2(budget));
40155
40423
  };
40156
40424
  var openDaemonCommand = async (port, token, name, params2, timeoutMs) => {
40157
40425
  return await fetchWithTimeoutContext(`http://127.0.0.1:${port}/command`, {
@@ -40348,7 +40616,7 @@ var DEFAULT_WORKFLOW_TRANSPORT_TIMEOUT_MS = 12e4;
40348
40616
  // src/public-surface/generated-manifest.ts
40349
40617
  var PUBLIC_SURFACE_MANIFEST = {
40350
40618
  "schemaVersion": "2026-04-04",
40351
- "generatedAt": "2026-04-20T16:29:04.426Z",
40619
+ "generatedAt": "2026-04-28T03:51:39.553Z",
40352
40620
  "cli": {
40353
40621
  "groups": [
40354
40622
  {
@@ -40546,7 +40814,7 @@ var PUBLIC_SURFACE_MANIFEST = {
40546
40814
  },
40547
40815
  {
40548
40816
  "name": "update",
40549
- "description": "Clear cached plugin and refresh managed skill packs",
40817
+ "description": "Repair cached plugin pins and refresh managed skill packs",
40550
40818
  "usage": "npx opendevbrowser update [--global|--local] [--skills-global|--skills-local|--no-skills]",
40551
40819
  "flags": [
40552
40820
  "--global",
@@ -42126,7 +42394,7 @@ var PUBLIC_SURFACE_MANIFEST = {
42126
42394
  },
42127
42395
  {
42128
42396
  "name": "--dy",
42129
- "kind": "boolean"
42397
+ "kind": "value"
42130
42398
  },
42131
42399
  {
42132
42400
  "name": "--key",
@@ -42482,6 +42750,7 @@ var PUBLIC_SURFACE_MANIFEST = {
42482
42750
  "--since-network-seq",
42483
42751
  "--since-exception-seq",
42484
42752
  "--max",
42753
+ "--dy",
42485
42754
  "--target-id",
42486
42755
  "--window-id",
42487
42756
  "--tab-id",
@@ -43320,15 +43589,18 @@ export {
43320
43589
  executeMacroWithRuntime,
43321
43590
  fetchWithTimeout,
43322
43591
  readDaemonMetadata,
43323
- getCurrentDaemonFingerprint,
43592
+ isCurrentDaemonFingerprint,
43593
+ createDaemonStopHeaders,
43324
43594
  startDaemon,
43325
43595
  EXIT_USAGE,
43326
43596
  EXIT_EXECUTION,
43327
43597
  EXIT_DISCONNECTED,
43328
43598
  createUsageError,
43599
+ createDisconnectedError,
43329
43600
  toCliError,
43330
43601
  formatErrorPayload,
43331
43602
  resolveExitCode,
43603
+ DEFAULT_DAEMON_STATUS_FETCH_OPTIONS,
43332
43604
  fetchDaemonStatus,
43333
43605
  fetchDaemonStatusFromMetadata,
43334
43606
  DaemonClient,
@@ -43350,4 +43622,4 @@ export {
43350
43622
  TOOL_SURFACE_ENTRIES
43351
43623
  };
43352
43624
  /* v8 ignore next -- @preserve */
43353
- //# sourceMappingURL=chunk-7U63PZ4W.js.map
43625
+ //# sourceMappingURL=chunk-MWBDO2L5.js.map