frizz 0.12.12 → 0.13.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.
Files changed (156) hide show
  1. package/dist/frizz.js +752 -350
  2. package/package.json +12 -12
  3. package/dist/claude-agent-broker.js +0 -35911
  4. package/dist/codex-app-server-daemon.js +0 -423
  5. package/dist/dev-child.js +0 -56194
  6. package/runtime/board/agent-bindings.mjs +0 -287
  7. package/runtime/board/agent-liveness.mjs +0 -367
  8. package/runtime/board/agent-status.mjs +0 -178
  9. package/runtime/board/config.mjs +0 -993
  10. package/runtime/board/decisions.mjs +0 -97
  11. package/runtime/board/index.mjs +0 -704
  12. package/runtime/board/notify-shared.mjs +0 -90
  13. package/runtime/board/notify.mjs +0 -81
  14. package/runtime/board/ownership.mjs +0 -120
  15. package/runtime/board/rest-detect.mjs +0 -213
  16. package/runtime/board/thread-excerpt.mjs +0 -162
  17. package/runtime/board/thread-update.mjs +0 -289
  18. package/runtime/cc-worker/.claude-plugin/plugin.json +0 -10
  19. package/runtime/cc-worker/DECISIONS.md +0 -1172
  20. package/runtime/cc-worker/LICENSE +0 -21
  21. package/runtime/cc-worker/agents/high.md +0 -7
  22. package/runtime/cc-worker/agents/low.md +0 -7
  23. package/runtime/cc-worker/agents/max.md +0 -7
  24. package/runtime/cc-worker/agents/medium.md +0 -7
  25. package/runtime/cc-worker/agents/xhigh.md +0 -7
  26. package/runtime/cc-worker/bin/frizz +0 -17
  27. package/runtime/cc-worker/bin/frizz-mcp.mjs +0 -1612
  28. package/runtime/cc-worker/bin/frizz-update +0 -18
  29. package/runtime/cc-worker/hooks/agent-bind.mjs +0 -40
  30. package/runtime/cc-worker/hooks/agent-dispatch.mjs +0 -121
  31. package/runtime/cc-worker/hooks/bash-background.d.mts +0 -6
  32. package/runtime/cc-worker/hooks/bash-background.mjs +0 -247
  33. package/runtime/cc-worker/hooks/deny-ask.mjs +0 -39
  34. package/runtime/cc-worker/hooks/deny-plan.mjs +0 -62
  35. package/runtime/cc-worker/hooks/hooks.json +0 -102
  36. package/runtime/cc-worker/hooks/perm-policy.mjs +0 -211
  37. package/runtime/cc-worker/hooks/scratchpad.mjs +0 -417
  38. package/runtime/cc-worker/hooks/session-seed.mjs +0 -107
  39. package/runtime/cc-worker/scripts/frizz/agent-bindings.mjs +0 -9
  40. package/runtime/cc-worker/scripts/frizz/config.mjs +0 -12
  41. package/runtime/cc-worker/skills/gh/SKILL.md +0 -141
  42. package/runtime/cc-worker/skills/gh/scripts/ci-watch.mjs +0 -60
  43. package/runtime/cc-worker/skills/gh/scripts/github-watch.mjs +0 -130
  44. package/runtime/cc-worker/skills/gh/scripts/review-watch.mjs +0 -54
  45. package/web-dist/apple-touch-icon.png +0 -0
  46. package/web-dist/assets/TerminalPane-CTuT1746.js +0 -7
  47. package/web-dist/assets/abnfDiagram-VRR7QNED-C9lXEgoU.js +0 -1
  48. package/web-dist/assets/arc-BSyeo0Gb.js +0 -1
  49. package/web-dist/assets/architecture-TIHT7OUA-cPNCo9oK.js +0 -1
  50. package/web-dist/assets/architectureDiagram-ZJ3FMSHR-BmJyQxby.js +0 -36
  51. package/web-dist/assets/array-BifhSqXX.js +0 -1
  52. package/web-dist/assets/blockDiagram-677ZJIJ3-DYTepy7i.js +0 -132
  53. package/web-dist/assets/c4Diagram-LMCZKHZV-CNaJlw58.js +0 -10
  54. package/web-dist/assets/channel-D5dgm6BA.js +0 -1
  55. package/web-dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +0 -1
  56. package/web-dist/assets/chunk-32BRIVSS-uAMm81q2.js +0 -1
  57. package/web-dist/assets/chunk-52WLFC77-DjszZCE7.js +0 -10
  58. package/web-dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +0 -15
  59. package/web-dist/assets/chunk-7BUUIJ7U-Bb538aSH.js +0 -1
  60. package/web-dist/assets/chunk-C7G6YPKG-BuHoup14.js +0 -1
  61. package/web-dist/assets/chunk-EX3LRPZG-Bg9UEQvR.js +0 -231
  62. package/web-dist/assets/chunk-FWX5IMBZ-CHnQfTLw.js +0 -2
  63. package/web-dist/assets/chunk-HOUHSVGY-ByjeQLq2.js +0 -1
  64. package/web-dist/assets/chunk-ICXQ74PX-DeM9b_DJ.js +0 -2
  65. package/web-dist/assets/chunk-JWPE2WC7-DVXcaiue.js +0 -1
  66. package/web-dist/assets/chunk-KEIR6QF5-BfrZ3jm6.js +0 -161
  67. package/web-dist/assets/chunk-MOJQB5TN-DxLERy2q.js +0 -88
  68. package/web-dist/assets/chunk-OGEWGWER-sBPjsAxh.js +0 -1
  69. package/web-dist/assets/chunk-PUDLZKDR-CarEdLd9.js +0 -156
  70. package/web-dist/assets/chunk-Q4XR5HBZ-BfXpvkfr.js +0 -70
  71. package/web-dist/assets/chunk-RYQCIY6F-Cu_KplZW.js +0 -1
  72. package/web-dist/assets/chunk-V7JOEXUC-B3FaJqr_.js +0 -206
  73. package/web-dist/assets/chunk-VAUOI2AC-DHsDLPEL.js +0 -1
  74. package/web-dist/assets/chunk-VR4S4FIN-ObNNjO-z.js +0 -1
  75. package/web-dist/assets/chunk-WYO6CB5R-JZxgPfBf.js +0 -127
  76. package/web-dist/assets/chunk-XXDRQBXY-DYlTP5J-.js +0 -1
  77. package/web-dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +0 -1
  78. package/web-dist/assets/chunk-ZGVPDNZ5-rLSAye5U.js +0 -62
  79. package/web-dist/assets/chunk-ZIRB5QZD-C6fEPe3t.js +0 -32
  80. package/web-dist/assets/classDiagram-OUVF2IWQ-Bj_Hjv7y.js +0 -1
  81. package/web-dist/assets/classDiagram-v2-EOCWNBFH-Bj_Hjv7y.js +0 -1
  82. package/web-dist/assets/cose-bilkent-JH36ORCC-BUIsLrGc.js +0 -1
  83. package/web-dist/assets/cynefin-VYW2F7L2-B0-Xy-xW.js +0 -1
  84. package/web-dist/assets/cynefinDiagram-TSTJHNR4-Dd3QCTMp.js +0 -62
  85. package/web-dist/assets/cytoscape.esm-B3I8pqwA.js +0 -321
  86. package/web-dist/assets/dagre-CXRCoUWR.js +0 -1
  87. package/web-dist/assets/dagre-VKFMJZFB-c_sDjRhh.js +0 -4
  88. package/web-dist/assets/defaultLocale-C8Fc0cco.js +0 -1
  89. package/web-dist/assets/diagram-FQU43EPY-DlrOA7Q2.js +0 -3
  90. package/web-dist/assets/diagram-G47NLZAW-DNwGdtwN.js +0 -24
  91. package/web-dist/assets/diagram-NH7WQ7WH-CPNunIAd.js +0 -24
  92. package/web-dist/assets/diagram-OA4YK3LP-BZVJWnon.js +0 -30
  93. package/web-dist/assets/diagram-WEI45ONY-B8UEevkd.js +0 -41
  94. package/web-dist/assets/dist-DoH_9pyS.js +0 -1
  95. package/web-dist/assets/ebnfDiagram-CCIWWBDH-BGCxDQf1.js +0 -1
  96. package/web-dist/assets/erDiagram-Q63AITRT-CKnUuelS.js +0 -85
  97. package/web-dist/assets/eventmodeling-45OFAUF4-DvqeSNur.js +0 -1
  98. package/web-dist/assets/flowDiagram-23GEKE2U-DT_LFo10.js +0 -1
  99. package/web-dist/assets/ganttDiagram-NO4QXBWP-2-k1ZZhN.js +0 -292
  100. package/web-dist/assets/gitGraph-TEB2WS4Q-Ci3lt5M_.js +0 -1
  101. package/web-dist/assets/gitGraphDiagram-IHSO6WYX-B_AQwuvk.js +0 -106
  102. package/web-dist/assets/graphlib-B8gBHxth.js +0 -1
  103. package/web-dist/assets/index-CSVjp00Q.css +0 -1
  104. package/web-dist/assets/index-D5VGTEZg.js +0 -319
  105. package/web-dist/assets/info-DKCQHKI2-m3mT757b.js +0 -1
  106. package/web-dist/assets/infoDiagram-FWYZ7A6U-CQw6f3Hj.js +0 -2
  107. package/web-dist/assets/init-D6jRqBbL.js +0 -1
  108. package/web-dist/assets/ishikawaDiagram-FXEZZL3T-C-7JgXGg.js +0 -70
  109. package/web-dist/assets/journeyDiagram-5HDEW3XC-DhPxGm-x.js +0 -139
  110. package/web-dist/assets/kanban-definition-HUTT4EX6-pYAdpAN-.js +0 -89
  111. package/web-dist/assets/katex-CddkPoXu.js +0 -257
  112. package/web-dist/assets/line-DtHJLrdk.js +0 -1
  113. package/web-dist/assets/linear-z2V0wJk9.js +0 -1
  114. package/web-dist/assets/map-DsCK-0Cs.js +0 -1
  115. package/web-dist/assets/mermaid-parser.core-CiDWeD1W.js +0 -7
  116. package/web-dist/assets/mermaid.core-BIRnljFs.js +0 -11
  117. package/web-dist/assets/mindmap-definition-LN4V7U3C-AwCgXi_v.js +0 -96
  118. package/web-dist/assets/ordinal-hYBb2elL.js +0 -1
  119. package/web-dist/assets/packet-7NZHBO7P-CnOMRKg1.js +0 -1
  120. package/web-dist/assets/path-BWPyau1x.js +0 -1
  121. package/web-dist/assets/pegDiagram-2B236MQR-KwEiKY6p.js +0 -1
  122. package/web-dist/assets/pie-RZYD4A2V-BXVw5n_F.js +0 -1
  123. package/web-dist/assets/pieDiagram-ENE6RG2P-BKeGzq7n.js +0 -39
  124. package/web-dist/assets/quadrantDiagram-ABIIQ3AL-_B_KnoiC.js +0 -7
  125. package/web-dist/assets/radar-I7S5WNFK-BDV-2CJh.js +0 -1
  126. package/web-dist/assets/railroad-3IZDKUUU-CJLCawbl.js +0 -1
  127. package/web-dist/assets/railroad-abnf-AHOZXSZD-BLgEAPud.js +0 -1
  128. package/web-dist/assets/railroad-ebnf-EBAXGLYW-Czt3Iqq1.js +0 -1
  129. package/web-dist/assets/railroad-peg-LSFZ7HO6-HPyLt5Zu.js +0 -1
  130. package/web-dist/assets/railroadDiagram-RFXS5EU6-BVV6ZmA7.js +0 -1
  131. package/web-dist/assets/requirementDiagram-TGXJPOKE-D3JoXNiN.js +0 -84
  132. package/web-dist/assets/rolldown-runtime-Bh1tDfsg.js +0 -1
  133. package/web-dist/assets/rough.esm-CSKSodPl.js +0 -1
  134. package/web-dist/assets/sankeyDiagram-HTMAVEWB-D7r0TcuX.js +0 -40
  135. package/web-dist/assets/sequenceDiagram-DBY2YBRQ-B35koHCj.js +0 -162
  136. package/web-dist/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +0 -1
  137. package/web-dist/assets/src-C4XfhTaE.js +0 -1
  138. package/web-dist/assets/stateDiagram-2N3HPSRC-DrFIFrPb.js +0 -1
  139. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-D57MsDPe.js +0 -1
  140. package/web-dist/assets/swimlanes-5IMT3BWC-D14gjDvr.js +0 -2
  141. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DC2nRhL8.js +0 -8
  142. package/web-dist/assets/timeline-definition-FHXFAJF6-DYuBi6LS.js +0 -120
  143. package/web-dist/assets/treeView-QDETBFTQ-DWCgtuaU.js +0 -1
  144. package/web-dist/assets/treemap-6X3UGDF4-QuJpQ-gt.js +0 -1
  145. package/web-dist/assets/vennDiagram-L72KCM5P-BgtM82oJ.js +0 -34
  146. package/web-dist/assets/wardley-OPB4EBWU-CWh_aioC.js +0 -1
  147. package/web-dist/assets/wardleyDiagram-EHGQE667-BNWSkBva.js +0 -78
  148. package/web-dist/assets/xychartDiagram-FW5EYKEG-Bc6i6hg5.js +0 -7
  149. package/web-dist/favicon-16.png +0 -0
  150. package/web-dist/favicon-32.png +0 -0
  151. package/web-dist/favicon.svg +0 -34
  152. package/web-dist/icon-192.png +0 -0
  153. package/web-dist/icon-512.png +0 -0
  154. package/web-dist/icon-maskable-512.png +0 -0
  155. package/web-dist/index.html +0 -44
  156. package/web-dist/manifest.webmanifest +0 -16
package/dist/frizz.js CHANGED
@@ -4175,10 +4175,10 @@ var require_resolve_block_map = __commonJS({
4175
4175
  let offset = bm.offset;
4176
4176
  let commentEnd = null;
4177
4177
  for (const collItem of bm.items) {
4178
- const { start, key, sep: sep4, value } = collItem;
4178
+ const { start, key, sep: sep5, value } = collItem;
4179
4179
  const keyProps = resolveProps.resolveProps(start, {
4180
4180
  indicator: "explicit-key-ind",
4181
- next: key ?? sep4?.[0],
4181
+ next: key ?? sep5?.[0],
4182
4182
  offset,
4183
4183
  onError,
4184
4184
  parentIndent: bm.indent,
@@ -4192,7 +4192,7 @@ var require_resolve_block_map = __commonJS({
4192
4192
  else if ("indent" in key && key.indent !== bm.indent)
4193
4193
  onError(offset, "BAD_INDENT", startColMsg);
4194
4194
  }
4195
- if (!keyProps.anchor && !keyProps.tag && !sep4) {
4195
+ if (!keyProps.anchor && !keyProps.tag && !sep5) {
4196
4196
  commentEnd = keyProps.end;
4197
4197
  if (keyProps.comment) {
4198
4198
  if (map.comment)
@@ -4216,7 +4216,7 @@ var require_resolve_block_map = __commonJS({
4216
4216
  ctx.atKey = false;
4217
4217
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
4218
4218
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
4219
- const valueProps = resolveProps.resolveProps(sep4 ?? [], {
4219
+ const valueProps = resolveProps.resolveProps(sep5 ?? [], {
4220
4220
  indicator: "map-value-ind",
4221
4221
  next: value,
4222
4222
  offset: keyNode.range[2],
@@ -4232,7 +4232,7 @@ var require_resolve_block_map = __commonJS({
4232
4232
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
4233
4233
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
4234
4234
  }
4235
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep4, null, valueProps, onError);
4235
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep5, null, valueProps, onError);
4236
4236
  if (ctx.schema.compat)
4237
4237
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
4238
4238
  offset = valueNode.range[2];
@@ -4323,7 +4323,7 @@ var require_resolve_end = __commonJS({
4323
4323
  let comment = "";
4324
4324
  if (end) {
4325
4325
  let hasSpace = false;
4326
- let sep4 = "";
4326
+ let sep5 = "";
4327
4327
  for (const token of end) {
4328
4328
  const { source, type } = token;
4329
4329
  switch (type) {
@@ -4337,13 +4337,13 @@ var require_resolve_end = __commonJS({
4337
4337
  if (!comment)
4338
4338
  comment = cb;
4339
4339
  else
4340
- comment += sep4 + cb;
4341
- sep4 = "";
4340
+ comment += sep5 + cb;
4341
+ sep5 = "";
4342
4342
  break;
4343
4343
  }
4344
4344
  case "newline":
4345
4345
  if (comment)
4346
- sep4 += source;
4346
+ sep5 += source;
4347
4347
  hasSpace = true;
4348
4348
  break;
4349
4349
  default:
@@ -4386,18 +4386,18 @@ var require_resolve_flow_collection = __commonJS({
4386
4386
  let offset = fc.offset + fc.start.source.length;
4387
4387
  for (let i = 0; i < fc.items.length; ++i) {
4388
4388
  const collItem = fc.items[i];
4389
- const { start, key, sep: sep4, value } = collItem;
4389
+ const { start, key, sep: sep5, value } = collItem;
4390
4390
  const props = resolveProps.resolveProps(start, {
4391
4391
  flow: fcName,
4392
4392
  indicator: "explicit-key-ind",
4393
- next: key ?? sep4?.[0],
4393
+ next: key ?? sep5?.[0],
4394
4394
  offset,
4395
4395
  onError,
4396
4396
  parentIndent: fc.indent,
4397
4397
  startOnNewline: false
4398
4398
  });
4399
4399
  if (!props.found) {
4400
- if (!props.anchor && !props.tag && !sep4 && !value) {
4400
+ if (!props.anchor && !props.tag && !sep5 && !value) {
4401
4401
  if (i === 0 && props.comma)
4402
4402
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
4403
4403
  else if (i < fc.items.length - 1)
@@ -4451,8 +4451,8 @@ var require_resolve_flow_collection = __commonJS({
4451
4451
  }
4452
4452
  }
4453
4453
  }
4454
- if (!isMap && !sep4 && !props.found) {
4455
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep4, null, props, onError);
4454
+ if (!isMap && !sep5 && !props.found) {
4455
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep5, null, props, onError);
4456
4456
  coll.items.push(valueNode);
4457
4457
  offset = valueNode.range[2];
4458
4458
  if (isBlock(value))
@@ -4464,7 +4464,7 @@ var require_resolve_flow_collection = __commonJS({
4464
4464
  if (isBlock(key))
4465
4465
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
4466
4466
  ctx.atKey = false;
4467
- const valueProps = resolveProps.resolveProps(sep4 ?? [], {
4467
+ const valueProps = resolveProps.resolveProps(sep5 ?? [], {
4468
4468
  flow: fcName,
4469
4469
  indicator: "map-value-ind",
4470
4470
  next: value,
@@ -4475,8 +4475,8 @@ var require_resolve_flow_collection = __commonJS({
4475
4475
  });
4476
4476
  if (valueProps.found) {
4477
4477
  if (!isMap && !props.found && ctx.options.strict) {
4478
- if (sep4)
4479
- for (const st of sep4) {
4478
+ if (sep5)
4479
+ for (const st of sep5) {
4480
4480
  if (st === valueProps.found)
4481
4481
  break;
4482
4482
  if (st.type === "newline") {
@@ -4493,7 +4493,7 @@ var require_resolve_flow_collection = __commonJS({
4493
4493
  else
4494
4494
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
4495
4495
  }
4496
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep4, null, valueProps, onError) : null;
4496
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep5, null, valueProps, onError) : null;
4497
4497
  if (valueNode) {
4498
4498
  if (isBlock(value))
4499
4499
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -4673,7 +4673,7 @@ var require_resolve_block_scalar = __commonJS({
4673
4673
  chompStart = i + 1;
4674
4674
  }
4675
4675
  let value = "";
4676
- let sep4 = "";
4676
+ let sep5 = "";
4677
4677
  let prevMoreIndented = false;
4678
4678
  for (let i = 0; i < contentStart; ++i)
4679
4679
  value += lines[i][0].slice(trimIndent) + "\n";
@@ -4690,24 +4690,24 @@ var require_resolve_block_scalar = __commonJS({
4690
4690
  indent = "";
4691
4691
  }
4692
4692
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
4693
- value += sep4 + indent.slice(trimIndent) + content;
4694
- sep4 = "\n";
4693
+ value += sep5 + indent.slice(trimIndent) + content;
4694
+ sep5 = "\n";
4695
4695
  } else if (indent.length > trimIndent || content[0] === " ") {
4696
- if (sep4 === " ")
4697
- sep4 = "\n";
4698
- else if (!prevMoreIndented && sep4 === "\n")
4699
- sep4 = "\n\n";
4700
- value += sep4 + indent.slice(trimIndent) + content;
4701
- sep4 = "\n";
4696
+ if (sep5 === " ")
4697
+ sep5 = "\n";
4698
+ else if (!prevMoreIndented && sep5 === "\n")
4699
+ sep5 = "\n\n";
4700
+ value += sep5 + indent.slice(trimIndent) + content;
4701
+ sep5 = "\n";
4702
4702
  prevMoreIndented = true;
4703
4703
  } else if (content === "") {
4704
- if (sep4 === "\n")
4704
+ if (sep5 === "\n")
4705
4705
  value += "\n";
4706
4706
  else
4707
- sep4 = "\n";
4707
+ sep5 = "\n";
4708
4708
  } else {
4709
- value += sep4 + content;
4710
- sep4 = " ";
4709
+ value += sep5 + content;
4710
+ sep5 = " ";
4711
4711
  prevMoreIndented = false;
4712
4712
  }
4713
4713
  }
@@ -4889,25 +4889,25 @@ var require_resolve_flow_scalar = __commonJS({
4889
4889
  if (!match)
4890
4890
  return source;
4891
4891
  let res = match[1];
4892
- let sep4 = " ";
4892
+ let sep5 = " ";
4893
4893
  let pos = first.lastIndex;
4894
4894
  line.lastIndex = pos;
4895
4895
  while (match = line.exec(source)) {
4896
4896
  if (match[1] === "") {
4897
- if (sep4 === "\n")
4898
- res += sep4;
4897
+ if (sep5 === "\n")
4898
+ res += sep5;
4899
4899
  else
4900
- sep4 = "\n";
4900
+ sep5 = "\n";
4901
4901
  } else {
4902
- res += sep4 + match[1];
4903
- sep4 = " ";
4902
+ res += sep5 + match[1];
4903
+ sep5 = " ";
4904
4904
  }
4905
4905
  pos = line.lastIndex;
4906
4906
  }
4907
4907
  const last = /[ \t]*(.*)/sy;
4908
4908
  last.lastIndex = pos;
4909
4909
  match = last.exec(source);
4910
- return res + sep4 + (match?.[1] ?? "");
4910
+ return res + sep5 + (match?.[1] ?? "");
4911
4911
  }
4912
4912
  function doubleQuotedValue(source, onError) {
4913
4913
  let res = "";
@@ -5717,14 +5717,14 @@ var require_cst_stringify = __commonJS({
5717
5717
  }
5718
5718
  }
5719
5719
  }
5720
- function stringifyItem({ start, key, sep: sep4, value }) {
5720
+ function stringifyItem({ start, key, sep: sep5, value }) {
5721
5721
  let res = "";
5722
5722
  for (const st of start)
5723
5723
  res += st.source;
5724
5724
  if (key)
5725
5725
  res += stringifyToken(key);
5726
- if (sep4)
5727
- for (const st of sep4)
5726
+ if (sep5)
5727
+ for (const st of sep5)
5728
5728
  res += st.source;
5729
5729
  if (value)
5730
5730
  res += stringifyToken(value);
@@ -6891,18 +6891,18 @@ var require_parser = __commonJS({
6891
6891
  if (this.type === "map-value-ind") {
6892
6892
  const prev = getPrevProps(this.peek(2));
6893
6893
  const start = getFirstKeyStartProps(prev);
6894
- let sep4;
6894
+ let sep5;
6895
6895
  if (scalar.end) {
6896
- sep4 = scalar.end;
6897
- sep4.push(this.sourceToken);
6896
+ sep5 = scalar.end;
6897
+ sep5.push(this.sourceToken);
6898
6898
  delete scalar.end;
6899
6899
  } else
6900
- sep4 = [this.sourceToken];
6900
+ sep5 = [this.sourceToken];
6901
6901
  const map = {
6902
6902
  type: "block-map",
6903
6903
  offset: scalar.offset,
6904
6904
  indent: scalar.indent,
6905
- items: [{ start, key: scalar, sep: sep4 }]
6905
+ items: [{ start, key: scalar, sep: sep5 }]
6906
6906
  };
6907
6907
  this.onKeyLine = true;
6908
6908
  this.stack[this.stack.length - 1] = map;
@@ -7055,15 +7055,15 @@ var require_parser = __commonJS({
7055
7055
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
7056
7056
  const start2 = getFirstKeyStartProps(it.start);
7057
7057
  const key = it.key;
7058
- const sep4 = it.sep;
7059
- sep4.push(this.sourceToken);
7058
+ const sep5 = it.sep;
7059
+ sep5.push(this.sourceToken);
7060
7060
  delete it.key;
7061
7061
  delete it.sep;
7062
7062
  this.stack.push({
7063
7063
  type: "block-map",
7064
7064
  offset: this.offset,
7065
7065
  indent: this.indent,
7066
- items: [{ start: start2, key, sep: sep4 }]
7066
+ items: [{ start: start2, key, sep: sep5 }]
7067
7067
  });
7068
7068
  } else if (start.length > 0) {
7069
7069
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -7257,13 +7257,13 @@ var require_parser = __commonJS({
7257
7257
  const prev = getPrevProps(parent);
7258
7258
  const start = getFirstKeyStartProps(prev);
7259
7259
  fixFlowSeqItems(fc);
7260
- const sep4 = fc.end.splice(1, fc.end.length);
7261
- sep4.push(this.sourceToken);
7260
+ const sep5 = fc.end.splice(1, fc.end.length);
7261
+ sep5.push(this.sourceToken);
7262
7262
  const map = {
7263
7263
  type: "block-map",
7264
7264
  offset: fc.offset,
7265
7265
  indent: fc.indent,
7266
- items: [{ start, key: fc, sep: sep4 }]
7266
+ items: [{ start, key: fc, sep: sep5 }]
7267
7267
  };
7268
7268
  this.onKeyLine = true;
7269
7269
  this.stack[this.stack.length - 1] = map;
@@ -16829,7 +16829,7 @@ var require_websocket = __commonJS({
16829
16829
  var http = __require("http");
16830
16830
  var net = __require("net");
16831
16831
  var tls = __require("tls");
16832
- var { randomBytes: randomBytes2, createHash: createHash4 } = __require("crypto");
16832
+ var { randomBytes: randomBytes2, createHash: createHash5 } = __require("crypto");
16833
16833
  var { Duplex, Readable } = __require("stream");
16834
16834
  var { URL: URL2 } = __require("url");
16835
16835
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -17497,7 +17497,7 @@ var require_websocket = __commonJS({
17497
17497
  abortHandshake(websocket, socket, "Invalid Upgrade header");
17498
17498
  return;
17499
17499
  }
17500
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
17500
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
17501
17501
  if (res.headers["sec-websocket-accept"] !== digest) {
17502
17502
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
17503
17503
  return;
@@ -17866,7 +17866,7 @@ var require_websocket_server = __commonJS({
17866
17866
  var EventEmitter = __require("events");
17867
17867
  var http = __require("http");
17868
17868
  var { Duplex } = __require("stream");
17869
- var { createHash: createHash4 } = __require("crypto");
17869
+ var { createHash: createHash5 } = __require("crypto");
17870
17870
  var extension2 = require_extension();
17871
17871
  var PerMessageDeflate2 = require_permessage_deflate();
17872
17872
  var subprotocol2 = require_subprotocol();
@@ -18173,7 +18173,7 @@ var require_websocket_server = __commonJS({
18173
18173
  );
18174
18174
  }
18175
18175
  if (this._state > RUNNING) return abortHandshake(socket, 503);
18176
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
18176
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
18177
18177
  const headers = [
18178
18178
  "HTTP/1.1 101 Switching Protocols",
18179
18179
  "Upgrade: websocket",
@@ -18341,11 +18341,11 @@ function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Dat
18341
18341
  }
18342
18342
  }
18343
18343
  }
18344
- function formatDiskLine(record) {
18345
- const at = new Date(record.at);
18344
+ function formatDiskLine(record2) {
18345
+ const at = new Date(record2.at);
18346
18346
  const pad = (value, width = 2) => String(value).padStart(width, "0");
18347
18347
  const clock = `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}.${pad(at.getMilliseconds(), 3)}`;
18348
- return `${clock} ${record.level.toUpperCase().padEnd(5)} ${record.scope.padEnd(12)} ${record.message}
18348
+ return `${clock} ${record2.level.toUpperCase().padEnd(5)} ${record2.scope.padEnd(12)} ${record2.message}
18349
18349
  `;
18350
18350
  }
18351
18351
  function openLogFile(path) {
@@ -18372,10 +18372,10 @@ function createLogger(options2 = {}) {
18372
18372
  const listeners = /* @__PURE__ */ new Set();
18373
18373
  const emit = (recordLevel, scope, message) => {
18374
18374
  if (LEVEL_ORDER[recordLevel] < threshold) return;
18375
- const record = { at: now(), level: recordLevel, scope, message };
18375
+ const record2 = { at: now(), level: recordLevel, scope, message };
18376
18376
  if (fd !== null) {
18377
18377
  try {
18378
- const line = formatDiskLine(record);
18378
+ const line = formatDiskLine(record2);
18379
18379
  if (written + line.length > maxBytes) {
18380
18380
  writeSync(fd, `--- log truncated at ${maxBytes} bytes ---
18381
18381
  `);
@@ -18390,7 +18390,7 @@ function createLogger(options2 = {}) {
18390
18390
  }
18391
18391
  for (const listener of listeners) {
18392
18392
  try {
18393
- listener(record);
18393
+ listener(record2);
18394
18394
  } catch {
18395
18395
  }
18396
18396
  }
@@ -18417,13 +18417,13 @@ function setAmbientLogger(logger2) {
18417
18417
  ambient = logger2;
18418
18418
  return logger2;
18419
18419
  }
18420
- function formatFeedLine(record) {
18421
- return formatDiskLine(record).trimEnd();
18420
+ function formatFeedLine(record2) {
18421
+ return formatDiskLine(record2).trimEnd();
18422
18422
  }
18423
18423
  function attachTerminalMirror(logger2, enabled = process.env.FRIZZ_DEBUG === "1") {
18424
18424
  if (!enabled) return () => {
18425
18425
  };
18426
- return logger2.onRecord((record) => process.stderr.write(`${formatFeedLine(record)}
18426
+ return logger2.onRecord((record2) => process.stderr.write(`${formatFeedLine(record2)}
18427
18427
  `));
18428
18428
  }
18429
18429
  function ambientLogger() {
@@ -19120,13 +19120,13 @@ function acquireMutationGuard(stateDir, timeoutMs = GUARD_TIMEOUT_MS, adapter =
19120
19120
  let fd;
19121
19121
  try {
19122
19122
  fd = openSync3(path, "wx", 384);
19123
- const record = {
19123
+ const record2 = {
19124
19124
  version: 1,
19125
19125
  token,
19126
19126
  at: new Date(adapter.now()).toISOString(),
19127
19127
  ...generation
19128
19128
  };
19129
- writeFileSync6(fd, `${JSON.stringify(record)}
19129
+ writeFileSync6(fd, `${JSON.stringify(record2)}
19130
19130
  `, "utf8");
19131
19131
  fsyncSync2(fd);
19132
19132
  closeSync2(fd);
@@ -19170,13 +19170,13 @@ function parseDelegates(value) {
19170
19170
  const seen = /* @__PURE__ */ new Set();
19171
19171
  for (const candidate of value) {
19172
19172
  if (!candidate || typeof candidate !== "object") return null;
19173
- const record = candidate;
19174
- const generation = parseGeneration(record);
19175
- if (!generation || !validDelegateRole(record.role) || !validText(record.registeredAt, 128)) return null;
19173
+ const record2 = candidate;
19174
+ const generation = parseGeneration(record2);
19175
+ if (!generation || !validDelegateRole(record2.role) || !validText(record2.registeredAt, 128)) return null;
19176
19176
  const key = `${generation.pid}:${generation.processStart}`;
19177
19177
  if (seen.has(key)) return null;
19178
19178
  seen.add(key);
19179
- delegates.push({ ...generation, role: record.role, registeredAt: record.registeredAt });
19179
+ delegates.push({ ...generation, role: record2.role, registeredAt: record2.registeredAt });
19180
19180
  }
19181
19181
  return delegates;
19182
19182
  }
@@ -19210,11 +19210,11 @@ function projectLaunchOwnerPath(stateDir) {
19210
19210
  function readProjectLaunchOwner(stateDir) {
19211
19211
  return parseOwner(projectLaunchOwnerPath(stateDir));
19212
19212
  }
19213
- function sameTarget(record, target2) {
19214
- return record.projectId === target2.projectId && record.projectDir === target2.projectDir;
19213
+ function sameTarget(record2, target2) {
19214
+ return record2.projectId === target2.projectId && record2.projectDir === target2.projectDir;
19215
19215
  }
19216
- function sameProjectIdentity(record, target2) {
19217
- return record.projectId === target2.projectId;
19216
+ function sameProjectIdentity(record2, target2) {
19217
+ return record2.projectId === target2.projectId;
19218
19218
  }
19219
19219
  function projectLaunchTokenProof(target2, token) {
19220
19220
  if (!UUID_RE.test(token)) throw new Error("invalid Frizz project launch owner token");
@@ -19230,11 +19230,11 @@ function removeStatusesForToken(stateDir, token) {
19230
19230
  }
19231
19231
  }
19232
19232
  }
19233
- function writeNewOwner(path, record) {
19233
+ function writeNewOwner(path, record2) {
19234
19234
  let fd;
19235
19235
  try {
19236
19236
  fd = openSync3(path, "wx", 384);
19237
- writeFileSync6(fd, `${JSON.stringify(record)}
19237
+ writeFileSync6(fd, `${JSON.stringify(record2)}
19238
19238
  `, "utf8");
19239
19239
  fsyncSync2(fd);
19240
19240
  closeSync2(fd);
@@ -19250,21 +19250,21 @@ function writeNewOwner(path, record) {
19250
19250
  throw error;
19251
19251
  }
19252
19252
  }
19253
- function makeLease(target2, record, adapter) {
19253
+ function makeLease(target2, record2, adapter) {
19254
19254
  let released = false;
19255
19255
  return {
19256
19256
  target: target2,
19257
- token: record.token,
19258
- pid: record.pid,
19259
- processStart: record.processStart,
19260
- role: record.role,
19257
+ token: record2.token,
19258
+ pid: record2.pid,
19259
+ processStart: record2.processStart,
19260
+ role: record2.role,
19261
19261
  release: () => {
19262
19262
  if (released) return false;
19263
19263
  const unlock = acquireMutationGuard(target2.stateDir, GUARD_TIMEOUT_MS, adapter);
19264
19264
  try {
19265
19265
  const path = projectLaunchOwnerPath(target2.stateDir);
19266
19266
  const current = parseOwner(path);
19267
- if (!current || !sameTarget(current, target2) || current.token !== record.token || current.pid !== record.pid || current.processStart !== record.processStart) {
19267
+ if (!current || !sameTarget(current, target2) || current.token !== record2.token || current.pid !== record2.pid || current.processStart !== record2.processStart) {
19268
19268
  released = true;
19269
19269
  return false;
19270
19270
  }
@@ -19299,15 +19299,15 @@ function newOwnerRecord(target2, role, adapter) {
19299
19299
  }
19300
19300
  function installNewOwner(target2, role, adapter) {
19301
19301
  const path = projectLaunchOwnerPath(target2.stateDir);
19302
- const record = newOwnerRecord(target2, role, adapter);
19302
+ const record2 = newOwnerRecord(target2, role, adapter);
19303
19303
  try {
19304
- writeNewOwner(path, record);
19304
+ writeNewOwner(path, record2);
19305
19305
  } catch (error) {
19306
19306
  if (errorCode3(error) === "EEXIST") return { kind: "contended", owner: parseOwner(path) };
19307
19307
  throw error;
19308
19308
  }
19309
19309
  const committed = parseOwner(path);
19310
- if (!committed || committed.token !== record.token) return { kind: "contended", owner: committed };
19310
+ if (!committed || committed.token !== record2.token) return { kind: "contended", owner: committed };
19311
19311
  return { kind: "acquired", lease: makeLease(target2, committed, adapter) };
19312
19312
  }
19313
19313
  function drainDelegates(delegates, options2) {
@@ -19828,12 +19828,19 @@ var init_project_registry = __esm({
19828
19828
  }
19829
19829
  });
19830
19830
 
19831
+ // packages/server/src/native-helper.ts
19832
+ var init_native_helper = __esm({
19833
+ "packages/server/src/native-helper.ts"() {
19834
+ "use strict";
19835
+ }
19836
+ });
19837
+
19831
19838
  // src/production.ts
19832
19839
  init_local_origin();
19833
- import { readFileSync as readFileSync13 } from "node:fs";
19840
+ import { readFileSync as readFileSync15 } from "node:fs";
19834
19841
  import { homedir as homedir11 } from "node:os";
19835
19842
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19836
- import { join as join16 } from "node:path";
19843
+ import { join as join17 } from "node:path";
19837
19844
 
19838
19845
  // src/browser.ts
19839
19846
  import { spawn, execFile } from "node:child_process";
@@ -19995,12 +20002,12 @@ function manifestIdFor(url) {
19995
20002
  return url.endsWith("/") ? url : `${url}/`;
19996
20003
  }
19997
20004
  function execFileP(cmd, args2) {
19998
- return new Promise((resolve6, reject) => {
20005
+ return new Promise((resolve7, reject) => {
19999
20006
  execFile(
20000
20007
  cmd,
20001
20008
  args2,
20002
20009
  { encoding: "utf8", timeout: 1e4 },
20003
- (err, stdout) => err ? reject(err) : resolve6(stdout)
20010
+ (err, stdout) => err ? reject(err) : resolve7(stdout)
20004
20011
  );
20005
20012
  });
20006
20013
  }
@@ -20079,9 +20086,9 @@ function openCdpSession(browserPath, dataPath) {
20079
20086
  cmdPipe.on("error", failAll);
20080
20087
  resPipe.on("error", failAll);
20081
20088
  child.on("exit", () => failAll(new Error("chrome exited before the CDP handshake completed")));
20082
- const call2 = (method, params = {}, timeoutMs = 2e4) => new Promise((resolve6, reject) => {
20089
+ const call2 = (method, params = {}, timeoutMs = 2e4) => new Promise((resolve7, reject) => {
20083
20090
  const id = ++seq;
20084
- pending.set(id, { resolve: resolve6, reject });
20091
+ pending.set(id, { resolve: resolve7, reject });
20085
20092
  cmdPipe.write(JSON.stringify({ id, method, params }) + "\0");
20086
20093
  setTimeout(() => {
20087
20094
  if (pending.has(id)) {
@@ -20094,15 +20101,15 @@ function openCdpSession(browserPath, dataPath) {
20094
20101
  await call2("Browser.close").catch(() => {
20095
20102
  });
20096
20103
  if (child.exitCode !== null || child.signalCode !== null) return;
20097
- await new Promise((resolve6) => {
20104
+ await new Promise((resolve7) => {
20098
20105
  const t = setTimeout(() => {
20099
20106
  child.kill();
20100
- resolve6();
20107
+ resolve7();
20101
20108
  }, 1e4);
20102
20109
  t.unref();
20103
20110
  child.once("exit", () => {
20104
20111
  clearTimeout(t);
20105
- resolve6();
20112
+ resolve7();
20106
20113
  });
20107
20114
  });
20108
20115
  };
@@ -22619,6 +22626,7 @@ var RestartSupervisorProxy = class {
22619
22626
  status() {
22620
22627
  const delegated = this.options.status?.();
22621
22628
  const updateVersion = this.options.updateVersion?.();
22629
+ const version = typeof this.options.version === "function" ? this.options.version() : this.options.version;
22622
22630
  const owner = { state: this.state, ...this.message ? { message: this.message } : {} };
22623
22631
  const reported = this.transition === "update" && delegated ? { ...delegated, ...delegated.state === "ready" ? { preparing: true } : {} } : this.restartInFlight !== null || this.state === "failed" ? owner : delegated ?? owner;
22624
22632
  return {
@@ -22630,9 +22638,10 @@ var RestartSupervisorProxy = class {
22630
22638
  // Sent ONLY when the launcher can actually answer it, so an older client — and frizz-dev, which
22631
22639
  // has no notion of "already current" — keeps today's behaviour on its absence.
22632
22640
  ...this.options.updateAvailable ? { updateAvailable: this.options.updateAvailable() === true } : {},
22633
- // Version numbers ride the same launcher-only contract: absent for frizz-dev and legacy
22634
- // supervisors, so their clients render exactly what they render today.
22635
- ...this.options.version ? { version: this.options.version } : {},
22641
+ // The active SERVER version changes on a child-only update. Its absence keeps frizz-dev and
22642
+ // old monolithic supervisors' status payload and versionless UI exactly as they were.
22643
+ ...version ? { version } : {},
22644
+ ...this.options.launcherVersion ? { launcherVersion: this.options.launcherVersion } : {},
22636
22645
  ...updateVersion ? { updateVersion } : {},
22637
22646
  // Sent only when TRUE, so a published Frizz's payload is byte-identical to what it sends today
22638
22647
  // and an older client is unaffected. Absent therefore means "not a development build".
@@ -23288,7 +23297,7 @@ function serveRelayRequest(frame, options2) {
23288
23297
  const publicUrl = new URL(options2.publicOrigin);
23289
23298
  let cancelled = false;
23290
23299
  let abort;
23291
- const done = new Promise((resolve6) => {
23300
+ const done = new Promise((resolve7) => {
23292
23301
  const send = (upFrame) => {
23293
23302
  if (!cancelled) options2.send(upFrame);
23294
23303
  };
@@ -23322,11 +23331,11 @@ function serveRelayRequest(frame, options2) {
23322
23331
  });
23323
23332
  res.on("end", () => {
23324
23333
  send({ t: "res-end", id: frame.id });
23325
- resolve6();
23334
+ resolve7();
23326
23335
  });
23327
23336
  res.on("error", () => {
23328
23337
  send({ t: "res-end", id: frame.id });
23329
- resolve6();
23338
+ resolve7();
23330
23339
  });
23331
23340
  return;
23332
23341
  }
@@ -23343,7 +23352,7 @@ function serveRelayRequest(frame, options2) {
23343
23352
  ...body.byteLength > 0 ? { body: encodeBody(new Uint8Array(body)) } : {},
23344
23353
  end: true
23345
23354
  });
23346
- resolve6();
23355
+ resolve7();
23347
23356
  return;
23348
23357
  }
23349
23358
  send({ t: "res", id: frame.id, status: res.statusCode ?? 502, headers: outHeaders, end: false });
@@ -23355,11 +23364,11 @@ function serveRelayRequest(frame, options2) {
23355
23364
  });
23356
23365
  }
23357
23366
  send({ t: "res-end", id: frame.id });
23358
- resolve6();
23367
+ resolve7();
23359
23368
  });
23360
23369
  res.on("error", () => {
23361
23370
  send({ t: "res", id: frame.id, status: 502, headers: [], end: true });
23362
- resolve6();
23371
+ resolve7();
23363
23372
  });
23364
23373
  }
23365
23374
  );
@@ -23372,13 +23381,13 @@ function serveRelayRequest(frame, options2) {
23372
23381
  body: encodeBody(new TextEncoder().encode(`Frizz is not answering on this machine: ${error.message}`)),
23373
23382
  end: true
23374
23383
  });
23375
- resolve6();
23384
+ resolve7();
23376
23385
  });
23377
23386
  if (frame.body) req.write(Buffer.from(decodeBody(frame.body)));
23378
23387
  req.end();
23379
23388
  abort = () => {
23380
23389
  req.destroy();
23381
- resolve6();
23390
+ resolve7();
23382
23391
  };
23383
23392
  });
23384
23393
  return {
@@ -23512,9 +23521,9 @@ function connectRelay(options2) {
23512
23521
  };
23513
23522
  const scheduleRetry = (reason) => {
23514
23523
  if (stopped) return;
23515
- const delay4 = backoff(attempt++);
23516
- options2.onStatus?.("retrying", `${reason}; retrying in ${Math.round(delay4 / 1e3)}s`);
23517
- retryHandle = setTimer(() => void open(), delay4);
23524
+ const delay3 = backoff(attempt++);
23525
+ options2.onStatus?.("retrying", `${reason}; retrying in ${Math.round(delay3 / 1e3)}s`);
23526
+ retryHandle = setTimer(() => void open(), delay3);
23518
23527
  };
23519
23528
  const open = async () => {
23520
23529
  if (stopped) return;
@@ -23937,9 +23946,9 @@ function createRemoteController(options2) {
23937
23946
  import { execFile as execFile3 } from "node:child_process";
23938
23947
  var PROBE_TIMEOUT_MS = 4e3;
23939
23948
  function run2(command, args2) {
23940
- return new Promise((resolve6) => {
23949
+ return new Promise((resolve7) => {
23941
23950
  execFile3(command, args2, { encoding: "utf8", timeout: PROBE_TIMEOUT_MS }, (error, stdout) => {
23942
- resolve6(error ? null : stdout);
23951
+ resolve7(error ? null : stdout);
23943
23952
  });
23944
23953
  });
23945
23954
  }
@@ -25183,6 +25192,8 @@ var DEV_CRASH_STABLE_MS = 5e3;
25183
25192
  var DEV_CRASH_RETRY_BASE_MS = 500;
25184
25193
  var DEV_CRASH_RETRY_MAX_MS = 1e4;
25185
25194
  var CHILD_STOP_TIMEOUT_MS = 15e3;
25195
+ var STABLE_UPDATE_READY_TIMEOUT_MS = 3e4;
25196
+ var STABLE_UPDATE_STABILIZE_MS = 1e3;
25186
25197
  var CHILD_KILL_GRACE_MS = 5e3;
25187
25198
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".mjs", ".cjs", ".json"]);
25188
25199
  var CONFIG_NAMES = /* @__PURE__ */ new Set(["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", ".npmrc"]);
@@ -25389,6 +25400,12 @@ var Supervisor = class {
25389
25400
  debounce = null;
25390
25401
  restartRunning = false;
25391
25402
  restartAgain = false;
25403
+ /** Resolves when a pre-existing watcher restart has released the one child slot. */
25404
+ restartCompletion = null;
25405
+ /** Serializes an update candidate with watcher/browser restart requests. */
25406
+ updateChildRunning = false;
25407
+ /** A candidate never joins crash-retry until its selection has committed. */
25408
+ updateCandidate = null;
25392
25409
  reloadLauncher = false;
25393
25410
  crashAttempts = 0;
25394
25411
  crashStableTimer = null;
@@ -25424,6 +25441,10 @@ var Supervisor = class {
25424
25441
  lastActivity;
25425
25442
  publicProxy;
25426
25443
  updateRestart;
25444
+ updateMode;
25445
+ commitUpdate;
25446
+ updateReadyTimeoutMs;
25447
+ updateStabilizeMs;
25427
25448
  rollbackUpdate;
25428
25449
  durableReexec;
25429
25450
  /** Environment of the generation that actually reached ready, never merely the latest pointer. */
@@ -25464,6 +25485,10 @@ var Supervisor = class {
25464
25485
  this.errorLine = opts.error ?? ((line) => log.error("supervisor", stripPrefix(line)));
25465
25486
  this.onActivity = opts.onActivity;
25466
25487
  this.updateRestart = opts.updateRestart;
25488
+ this.updateMode = opts.updateMode ?? "durableReexec";
25489
+ this.commitUpdate = opts.commitUpdate;
25490
+ this.updateReadyTimeoutMs = Math.max(1, opts.updateReadyTimeoutMs ?? STABLE_UPDATE_READY_TIMEOUT_MS);
25491
+ this.updateStabilizeMs = Math.max(0, opts.updateStabilizeMs ?? STABLE_UPDATE_STABILIZE_MS);
25467
25492
  this.rollbackUpdate = opts.rollbackUpdate;
25468
25493
  this.durableReexec = opts.durableReexec;
25469
25494
  this.publicProxy = new RestartSupervisorProxy({
@@ -25479,6 +25504,7 @@ var Supervisor = class {
25479
25504
  updateRestart: this.updateRestart ? () => this.updateFromBrowser() : void 0,
25480
25505
  updateAvailable: opts.updateAvailable,
25481
25506
  version: opts.version,
25507
+ launcherVersion: opts.launcherVersion,
25482
25508
  updateVersion: opts.updateVersion,
25483
25509
  dev: opts.dev,
25484
25510
  status: () => {
@@ -25570,6 +25596,10 @@ var Supervisor = class {
25570
25596
  }
25571
25597
  requestRestart(reason, immediate = false, reloadLauncher = false, delayMs = this.debounceMs) {
25572
25598
  if (this.closed) return;
25599
+ if (this.updateChildRunning) {
25600
+ this.restartAgain = true;
25601
+ return;
25602
+ }
25573
25603
  this.reloadLauncher ||= reloadLauncher;
25574
25604
  if (this.debounce) clearTimeout(this.debounce);
25575
25605
  const run3 = () => {
@@ -25585,11 +25615,20 @@ var Supervisor = class {
25585
25615
  else this.debounce = setTimeout(run3, delayMs);
25586
25616
  }
25587
25617
  async restart() {
25618
+ if (this.updateChildRunning) {
25619
+ this.restartAgain = true;
25620
+ return;
25621
+ }
25588
25622
  if (this.restartRunning) {
25589
25623
  this.restartAgain = true;
25590
25624
  return;
25591
25625
  }
25592
25626
  this.restartRunning = true;
25627
+ let completeRestart;
25628
+ const completion = new Promise((resolveCompletion) => {
25629
+ completeRestart = resolveCompletion;
25630
+ });
25631
+ this.restartCompletion = completion;
25593
25632
  try {
25594
25633
  let shouldReloadLauncher = false;
25595
25634
  do {
@@ -25597,7 +25636,7 @@ var Supervisor = class {
25597
25636
  shouldReloadLauncher ||= this.reloadLauncher;
25598
25637
  this.reloadLauncher = false;
25599
25638
  await this.stopChild();
25600
- if (!this.closed) {
25639
+ if (!this.closed && !this.updateChildRunning) {
25601
25640
  const ready = await this.spawnChild();
25602
25641
  if (!ready) return;
25603
25642
  }
@@ -25605,6 +25644,8 @@ var Supervisor = class {
25605
25644
  if (shouldReloadLauncher && !this.closed) await this.reexecLauncher();
25606
25645
  } finally {
25607
25646
  this.restartRunning = false;
25647
+ completeRestart();
25648
+ if (this.restartCompletion === completion) this.restartCompletion = null;
25608
25649
  }
25609
25650
  }
25610
25651
  /** Called only by the public supervisor control endpoint, never by ordinary document reloads. */
@@ -25635,6 +25676,7 @@ var Supervisor = class {
25635
25676
  }
25636
25677
  async updateFromBrowser() {
25637
25678
  if (!this.updateRestart) return { state: "failed", message: "Update & Restart is unavailable in this launcher mode" };
25679
+ if (this.updateMode === "child") return this.updateChildFromBrowser();
25638
25680
  this.emitActivity("updating", "preparing the update \u2014 the running Frizz is untouched until it is ready");
25639
25681
  const candidate = await this.updateRestart();
25640
25682
  if (candidate.state !== "ready") {
@@ -25661,6 +25703,114 @@ var Supervisor = class {
25661
25703
  this.handoffDraining = false;
25662
25704
  }
25663
25705
  }
25706
+ /**
25707
+ * Update only the disposable application server while retaining the public listener and launcher.
25708
+ * There is deliberately no blue/green overlap: a Frizz child opens SQLite, tailers and schedulers
25709
+ * before it can report ready, so a candidate is started only after the old child is fully gone.
25710
+ */
25711
+ async updateChildFromBrowser() {
25712
+ if (!this.commitUpdate || !this.rollbackUpdate) {
25713
+ return { state: "failed", message: "child-only Update & Restart requires commit and rollback callbacks" };
25714
+ }
25715
+ this.updateChildRunning = true;
25716
+ try {
25717
+ await this.restartCompletion;
25718
+ this.emitActivity("updating", "preparing the new build \u2014 the running one is untouched until it is ready");
25719
+ let prepared;
25720
+ try {
25721
+ prepared = await this.updateRestart();
25722
+ } catch (error) {
25723
+ return this.failChildUpdate(`the update could not be prepared: ${error instanceof Error ? error.message : error}`, false);
25724
+ }
25725
+ if (prepared.state !== "ready") {
25726
+ return this.failChildUpdate(prepared.message ?? "the update could not be prepared", false);
25727
+ }
25728
+ if (this.closed) return this.failChildUpdate("Frizz supervisor stopped while preparing the update", false);
25729
+ if (prepared.message) this.emitActivity("updating", prepared.message);
25730
+ this.handoffDraining = true;
25731
+ await this.stopChild();
25732
+ if (this.closed) return this.failChildUpdate("Frizz supervisor stopped while starting the update", true);
25733
+ const ready = await this.spawnChild({
25734
+ updateCandidate: true,
25735
+ readinessTimeoutMs: this.updateReadyTimeoutMs
25736
+ });
25737
+ const candidate = this.child;
25738
+ if (!ready || !candidate) {
25739
+ return this.failChildUpdate(this.lastRestartFailure ?? "the update candidate did not become ready", true);
25740
+ }
25741
+ if (!await this.stabilizeUpdateCandidate(candidate)) {
25742
+ return this.failChildUpdate("the update candidate stopped before it became stable", true);
25743
+ }
25744
+ try {
25745
+ await this.commitUpdate();
25746
+ } catch (error) {
25747
+ return this.failChildUpdate(`the update could not be committed: ${error instanceof Error ? error.message : error}`, true);
25748
+ }
25749
+ if (this.closed || this.child !== candidate || candidate.exitCode !== null || candidate.signalCode !== null) {
25750
+ return this.failChildUpdate("the update candidate stopped while committing the update", true);
25751
+ }
25752
+ this.updateCandidate = null;
25753
+ this.lastRestartFailure = void 0;
25754
+ return { state: "ready" };
25755
+ } finally {
25756
+ this.updateChildRunning = false;
25757
+ this.handoffDraining = false;
25758
+ if (this.restartAgain && !this.closed) {
25759
+ this.restartAgain = false;
25760
+ this.requestRestart("changes received while updating", true);
25761
+ }
25762
+ }
25763
+ }
25764
+ /** Wait past a ready event so an immediate post-boot crash cannot be made durable. */
25765
+ async stabilizeUpdateCandidate(candidate) {
25766
+ if (this.child !== candidate || candidate.exitCode !== null || candidate.signalCode !== null) return false;
25767
+ if (this.updateStabilizeMs === 0) return true;
25768
+ return new Promise((resolveStable) => {
25769
+ let settled = false;
25770
+ const finish = (stable) => {
25771
+ if (settled) return;
25772
+ settled = true;
25773
+ clearTimeout(timer);
25774
+ candidate.removeListener("exit", exited);
25775
+ resolveStable(stable);
25776
+ };
25777
+ const exited = () => finish(false);
25778
+ const timer = setTimeout(() => finish(this.child === candidate && !this.closed), this.updateStabilizeMs);
25779
+ timer.unref();
25780
+ candidate.once("exit", exited);
25781
+ });
25782
+ }
25783
+ /** Restore the prepared selection and, if it was drained, put the known-good child back. */
25784
+ async failChildUpdate(message, restoreChild) {
25785
+ const failures = [];
25786
+ const candidate = this.updateCandidate;
25787
+ if (candidate && this.child === candidate) await this.stopChild();
25788
+ this.updateCandidate = null;
25789
+ let rolledBack = false;
25790
+ if (!this.rollbackUpdate) failures.push("rollback callback is unavailable");
25791
+ else {
25792
+ try {
25793
+ await this.rollbackUpdate();
25794
+ rolledBack = true;
25795
+ } catch (error) {
25796
+ failures.push(`rollback failed: ${error instanceof Error ? error.message : error}`);
25797
+ }
25798
+ }
25799
+ if (restoreChild && rolledBack && !this.closed) {
25800
+ try {
25801
+ const restored = await this.spawnChild();
25802
+ if (!restored) failures.push(`control plane recovery failed: ${this.lastRestartFailure ?? "the previous child did not become ready"}`);
25803
+ } catch (error) {
25804
+ failures.push(`control plane recovery failed: ${error instanceof Error ? error.message : error}`);
25805
+ }
25806
+ } else if (restoreChild && !rolledBack) {
25807
+ failures.push("control plane was not restarted because rollback did not restore a known-good selection");
25808
+ }
25809
+ const detail = failures.length > 0 ? `${message}; ${failures.join("; ")}` : message;
25810
+ this.errorLine(`[frizz] ${detail}`);
25811
+ if (!this.closed) this.writeStatus("failed", detail, this.boot);
25812
+ return { state: "failed", message: detail };
25813
+ }
25664
25814
  /**
25665
25815
  * A promoted pointer is not committed until its durable owner has actually been replaced. If
25666
25816
  * that handoff cannot happen, put the previous pointer back before reporting the failure. When
@@ -25709,7 +25859,7 @@ var Supervisor = class {
25709
25859
  await this.publicProxy.close();
25710
25860
  this.writeStatus("restarting", "immutable artifact promoted; re-executing durable supervisor", null);
25711
25861
  }
25712
- async spawnChild() {
25862
+ async spawnChild(options2 = {}) {
25713
25863
  const privatePort = await allocatePrivateDevPort(this.port);
25714
25864
  let launch;
25715
25865
  try {
@@ -25748,16 +25898,34 @@ var Supervisor = class {
25748
25898
  return;
25749
25899
  }
25750
25900
  this.child = child;
25901
+ if (options2.updateCandidate) this.updateCandidate = child;
25751
25902
  this.childPort = privatePort;
25752
25903
  this.boot = null;
25753
25904
  let started = false;
25754
25905
  let ownershipRejected = false;
25755
25906
  let spawnSettled = false;
25907
+ let readinessTimer;
25756
25908
  const settleSpawn = (ready) => {
25757
25909
  if (spawnSettled) return;
25758
25910
  spawnSettled = true;
25911
+ if (readinessTimer) clearTimeout(readinessTimer);
25759
25912
  settled(ready);
25760
25913
  };
25914
+ if (options2.readinessTimeoutMs !== void 0) {
25915
+ readinessTimer = setTimeout(() => {
25916
+ if (spawnSettled) return;
25917
+ const message = `update candidate did not become ready within ${options2.readinessTimeoutMs}ms`;
25918
+ this.lastRestartFailure = message;
25919
+ this.errorLine(`[frizz] ${message}`);
25920
+ this.writeStatus("failed", message, null);
25921
+ try {
25922
+ child.kill("SIGTERM");
25923
+ } catch {
25924
+ }
25925
+ settleSpawn(false);
25926
+ }, options2.readinessTimeoutMs);
25927
+ readinessTimer.unref();
25928
+ }
25761
25929
  child.on("message", (message) => {
25762
25930
  if (stopOwnerMessage(message) && message.token === this.ownerToken) {
25763
25931
  this.resolveStopRequested();
@@ -25799,7 +25967,7 @@ var Supervisor = class {
25799
25967
  this.boot = null;
25800
25968
  this.activeChildEnvironment = {};
25801
25969
  }
25802
- const expected2 = this.closed || this.stopping === child || ownershipRejected;
25970
+ const expected2 = this.closed || this.stopping === child || ownershipRejected || this.updateCandidate === child;
25803
25971
  this.clearCrashStability();
25804
25972
  if (!expected2) {
25805
25973
  const why = signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`;
@@ -26012,15 +26180,11 @@ async function startDevSupervisor(opts) {
26012
26180
  }
26013
26181
 
26014
26182
  // src/production-update.ts
26015
- init_src();
26016
26183
  import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
26017
26184
  import { existsSync as existsSync7 } from "node:fs";
26018
26185
  import { dirname as dirname12, isAbsolute as isAbsolute5, join as join14 } from "node:path";
26019
- import { setTimeout as delay3 } from "node:timers/promises";
26186
+ init_src();
26020
26187
  var PRODUCTION_REEXEC_FLAG = "--_frizz-production-reexec";
26021
- function canReexecInPlace(platform = process.platform, execve = process.execve) {
26022
- return platform !== "win32" && typeof execve === "function";
26023
- }
26024
26188
  var PRODUCTION_PRINT_LAUNCHER_FLAG = "--_frizz-print-launcher";
26025
26189
  function resolveNpmInvocation(env = process.env, execPath = process.execPath, exists = existsSync7, platform = process.platform) {
26026
26190
  const nodeDir = dirname12(execPath);
@@ -26040,9 +26204,11 @@ function resolveNpmInvocation(env = process.env, execPath = process.execPath, ex
26040
26204
  }
26041
26205
  function compareReleaseVersions(a, b) {
26042
26206
  const parse2 = (value) => {
26043
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value);
26207
+ const match = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(value);
26044
26208
  if (!match) return null;
26045
- return { numeric: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease: match[4] };
26209
+ const prerelease = match[4]?.split(".");
26210
+ if (prerelease?.some((part) => /^0\d+$/u.test(part))) return null;
26211
+ return { numeric: [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])], prerelease };
26046
26212
  };
26047
26213
  const left = parse2(a);
26048
26214
  const right = parse2(b);
@@ -26051,26 +26217,22 @@ function compareReleaseVersions(a, b) {
26051
26217
  const delta = left.numeric[index] - right.numeric[index];
26052
26218
  if (delta) return delta < 0 ? -1 : 1;
26053
26219
  }
26054
- if (left.prerelease === right.prerelease) return 0;
26220
+ if (!left.prerelease && !right.prerelease) return 0;
26055
26221
  if (!left.prerelease) return 1;
26056
26222
  if (!right.prerelease) return -1;
26057
- return comparePrerelease(left.prerelease, right.prerelease);
26058
- }
26059
- function comparePrerelease(a, b) {
26060
- const left = a.split(".");
26061
- const right = b.split(".");
26062
- for (let index = 0; index < Math.min(left.length, right.length); index++) {
26063
- const l = left[index];
26064
- const r = right[index];
26065
- if (l === r) continue;
26066
- const lNumeric = /^\d+$/u.test(l);
26067
- const rNumeric = /^\d+$/u.test(r);
26068
- if (lNumeric && rNumeric) return Number(l) < Number(r) ? -1 : 1;
26069
- if (lNumeric !== rNumeric) return lNumeric ? -1 : 1;
26070
- return l < r ? -1 : 1;
26071
- }
26072
- if (left.length === right.length) return 0;
26073
- return left.length < right.length ? -1 : 1;
26223
+ for (let index = 0; index < Math.max(left.prerelease.length, right.prerelease.length); index++) {
26224
+ const a2 = left.prerelease[index];
26225
+ const b2 = right.prerelease[index];
26226
+ if (a2 === b2) continue;
26227
+ if (a2 === void 0) return -1;
26228
+ if (b2 === void 0) return 1;
26229
+ const aNumeric = /^\d+$/u.test(a2);
26230
+ const bNumeric = /^\d+$/u.test(b2);
26231
+ if (aNumeric && bNumeric) return BigInt(a2) < BigInt(b2) ? -1 : 1;
26232
+ if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
26233
+ return a2 < b2 ? -1 : 1;
26234
+ }
26235
+ return 0;
26074
26236
  }
26075
26237
  async function planRegistryUpdate(packageName, currentVersion, adapter) {
26076
26238
  const latestVersion = (await adapter.latestVersion(packageName)).trim();
@@ -26080,9 +26242,6 @@ async function planRegistryUpdate(packageName, currentVersion, adapter) {
26080
26242
  if (comparison >= 0) return null;
26081
26243
  return { packageName, currentVersion, latestVersion, packageSpec: `${packageName}@${latestVersion}` };
26082
26244
  }
26083
- function successorArgs(port) {
26084
- return [PRODUCTION_REEXEC_FLAG, "--port", String(port)];
26085
- }
26086
26245
  function reexecArgv(rawArgs2) {
26087
26246
  const kept = [];
26088
26247
  for (let index = 0; index < rawArgs2.length; index++) {
@@ -26097,110 +26256,6 @@ function reexecArgv(rawArgs2) {
26097
26256
  }
26098
26257
  return kept;
26099
26258
  }
26100
- async function resolveRegistrySuccessor(plan, request, adapter) {
26101
- const result = await adapter.npmExec({
26102
- packageSpec: plan.packageSpec,
26103
- // The published bin name tracks the package name (frizz). Never hardcode a stale bin here or
26104
- // a renamed release would resolve the new package but invoke a bin that no longer exists.
26105
- bin: plan.packageName,
26106
- args: [PRODUCTION_PRINT_LAUNCHER_FLAG],
26107
- cwd: request.cwd,
26108
- env: request.env
26109
- });
26110
- if (result.code !== 0) {
26111
- const how = result.signal ? `signal ${result.signal}` : `exit ${result.code}`;
26112
- const tail = result.stderr.trim().split("\n").slice(-6).join("\n").trim();
26113
- throw new Error(`npm exec ${plan.packageSpec} failed (${how})${tail ? `: ${tail}` : ""}`);
26114
- }
26115
- const entry = result.stdout.trim().split("\n").map((line) => line.trim()).filter(Boolean).at(-1) ?? "";
26116
- if (!isAbsolute5(entry) || !existsSync7(entry))
26117
- throw new Error(`${plan.packageSpec} did not report its launcher entry (got ${JSON.stringify(entry)})`);
26118
- return { plan, entry };
26119
- }
26120
- function reexecIntoRegistrySuccessor(successor, request, execve = process.execve.bind(process)) {
26121
- return execve(process.execPath, [process.execPath, successor.entry, ...successorArgs(request.port)], {
26122
- ...request.env,
26123
- FRIZZ_REGISTRY_PACKAGE: successor.plan.packageName,
26124
- FRIZZ_REGISTRY_VERSION: successor.plan.latestVersion
26125
- });
26126
- }
26127
- function handoffToRegistrySuccessor(successor, request, adapter) {
26128
- const child = adapter.spawnDetached({
26129
- entry: successor.entry,
26130
- args: successorArgs(request.port),
26131
- cwd: request.cwd,
26132
- env: {
26133
- ...request.env,
26134
- FRIZZ_REGISTRY_PACKAGE: successor.plan.packageName,
26135
- FRIZZ_REGISTRY_VERSION: successor.plan.latestVersion
26136
- }
26137
- });
26138
- child.once("error", () => {
26139
- });
26140
- if (child.pid === void 0) throw new Error(`could not start Frizz ${successor.plan.latestVersion} from ${successor.entry}`);
26141
- child.unref();
26142
- return child;
26143
- }
26144
- var SUCCESSOR_READY_TIMEOUT_MS = 6e4;
26145
- async function awaitRegistrySuccessor(child, expected2, watch) {
26146
- const deadlineMs = watch.deadlineMs ?? SUCCESSOR_READY_TIMEOUT_MS;
26147
- const intervalMs = watch.intervalMs ?? 250;
26148
- const now = watch.now ?? Date.now;
26149
- const sleep = watch.sleep ?? ((ms) => delay3(ms));
26150
- const where = expected2.logFile ? `; its log is ${expected2.logFile}` : "";
26151
- let exited = child.exitCode !== null || child.signalCode !== null ? describeExit(child.exitCode, child.signalCode) : void 0;
26152
- const onExit = (code, signal) => {
26153
- exited = describeExit(code, signal);
26154
- };
26155
- child.once("exit", onExit);
26156
- try {
26157
- const started = now();
26158
- for (; ; ) {
26159
- if (exited) throw new Error(`Frizz ${expected2.version} exited (${exited}) before it answered on port ${expected2.port}${where}`);
26160
- const status = await watch.status();
26161
- if (status?.version === expected2.version) return;
26162
- if (now() - started >= deadlineMs) {
26163
- try {
26164
- child.kill();
26165
- } catch {
26166
- }
26167
- throw new Error(
26168
- `Frizz ${expected2.version} did not answer on port ${expected2.port} within ${Math.round(deadlineMs / 1e3)}s${where}`
26169
- );
26170
- }
26171
- await sleep(intervalMs);
26172
- }
26173
- } finally {
26174
- child.off("exit", onExit);
26175
- }
26176
- }
26177
- function describeExit(code, signal) {
26178
- return signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`;
26179
- }
26180
- function launcherControlHeaders(port) {
26181
- return { origin: `http://127.0.0.1:${port}`, "cache-control": "no-store" };
26182
- }
26183
- async function readLauncherStatus(port, fetcher = fetch) {
26184
- const controller = new AbortController();
26185
- const timeout = setTimeout(() => controller.abort(), 1e3);
26186
- timeout.unref?.();
26187
- try {
26188
- const response = await fetcher(`http://127.0.0.1:${port}${FRIZZ_ROUTE_PREFIX}/control/status`, {
26189
- signal: controller.signal,
26190
- headers: launcherControlHeaders(port)
26191
- });
26192
- if (!response.ok) return void 0;
26193
- const body = await response.json();
26194
- return {
26195
- ...typeof body.version === "string" ? { version: body.version } : {},
26196
- ...typeof body.state === "string" ? { state: body.state } : {}
26197
- };
26198
- } catch {
26199
- return void 0;
26200
- } finally {
26201
- clearTimeout(timeout);
26202
- }
26203
- }
26204
26259
  function isGlobalInstall(bundleDir, env = process.env) {
26205
26260
  if (env.npm_execpath) return false;
26206
26261
  const segments = bundleDir.split(/[\\/]/u);
@@ -26294,11 +26349,386 @@ npm exec ${packageSpec} did not finish within ${formatTimeout(execTimeoutMs)} an
26294
26349
  }
26295
26350
  var npmRegistryReleaseAdapter = createNpmRegistryReleaseAdapter();
26296
26351
 
26352
+ // src/server-release.ts
26353
+ init_frizz_paths();
26354
+ import { execFile as execFile5 } from "node:child_process";
26355
+ import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
26356
+ import {
26357
+ closeSync as closeSync5,
26358
+ existsSync as existsSync8,
26359
+ fsyncSync as fsyncSync5,
26360
+ mkdirSync as mkdirSync12,
26361
+ openSync as openSync6,
26362
+ readFileSync as readFileSync13,
26363
+ realpathSync as realpathSync6,
26364
+ renameSync as renameSync6,
26365
+ rmSync as rmSync9,
26366
+ statSync as statSync5,
26367
+ writeFileSync as writeFileSync11
26368
+ } from "node:fs";
26369
+ import { delimiter as delimiter2, dirname as dirname13, isAbsolute as isAbsolute6, join as join15, relative as relative3, resolve as resolve6, sep as sep4 } from "node:path";
26370
+ var SERVER_PROTOCOL = 1;
26371
+ var SERVER_DATA_EPOCH = 1;
26372
+ var VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
26373
+ var PACKAGE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
26374
+ var GENERATION = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
26375
+ var CURRENT_COMPATIBILITY = { protocol: SERVER_PROTOCOL, dataEpoch: SERVER_DATA_EPOCH };
26376
+ function assertSpecShape(spec) {
26377
+ if (typeof spec.package !== "string" || typeof spec.version !== "string" || !PACKAGE.test(spec.package) || !VERSION.test(spec.version))
26378
+ throw new Error("invalid exact Frizz server package/version");
26379
+ if (!Number.isSafeInteger(spec.protocol) || spec.protocol < 1 || !Number.isSafeInteger(spec.dataEpoch) || spec.dataEpoch < 1)
26380
+ throw new Error("invalid Frizz server protocol/data epoch");
26381
+ }
26382
+ function assertSpec(spec, compatibility = CURRENT_COMPATIBILITY) {
26383
+ assertSpecShape(spec);
26384
+ if (spec.protocol !== compatibility.protocol || spec.dataEpoch !== compatibility.dataEpoch)
26385
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26386
+ }
26387
+ function readJson(path) {
26388
+ return JSON.parse(readFileSync13(path, "utf8"));
26389
+ }
26390
+ function record(value) {
26391
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid Frizz server manifest");
26392
+ return value;
26393
+ }
26394
+ function serverReleaseSpec(manifest, packageOverride) {
26395
+ const metadata = record(record(manifest).frizzServer);
26396
+ const spec = {
26397
+ package: packageOverride ?? metadata.package,
26398
+ version: metadata.version,
26399
+ protocol: metadata.protocol,
26400
+ dataEpoch: metadata.dataEpoch
26401
+ };
26402
+ assertSpec(spec);
26403
+ return spec;
26404
+ }
26405
+ function containedFile(root, name, directory = false) {
26406
+ const path = realpathSync6(join15(root, name));
26407
+ const rel = relative3(realpathSync6(root), path);
26408
+ if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute6(rel))
26409
+ throw new Error(`Frizz server artifact escapes its package: ${name}`);
26410
+ const stat = statSync5(path);
26411
+ if (directory ? !stat.isDirectory() : !stat.isFile()) throw new Error(`invalid Frizz server artifact: ${name}`);
26412
+ return path;
26413
+ }
26414
+ function validateServerGeneration(root, spec, id, compatibility = CURRENT_COMPATIBILITY) {
26415
+ assertSpec(spec, compatibility);
26416
+ if (!GENERATION.test(id)) throw new Error("invalid Frizz server generation id");
26417
+ const manifest = record(readJson(join15(root, "package.json")));
26418
+ const metadata = record(manifest.frizzServer);
26419
+ if (manifest.name !== spec.package || manifest.version !== spec.version)
26420
+ throw new Error(`Frizz server install did not contain ${spec.package}@${spec.version}`);
26421
+ if (metadata.protocol !== spec.protocol || metadata.dataEpoch !== spec.dataEpoch)
26422
+ throw new Error("Frizz server protocol/data epoch is incompatible; the current server has not been stopped");
26423
+ const entry = containedFile(root, "dist/dev-child.js");
26424
+ containedFile(root, "dist/codex-app-server-daemon.js");
26425
+ containedFile(root, "dist/claude-agent-broker.js");
26426
+ containedFile(root, "web-dist/index.html");
26427
+ containedFile(root, "runtime/board/index.mjs");
26428
+ containedFile(root, "runtime/cc-worker/.claude-plugin/plugin.json");
26429
+ return { ...spec, id, root: realpathSync6(root), entry };
26430
+ }
26431
+ function serverGenerationLaunch(generation) {
26432
+ return {
26433
+ entry: generation.entry,
26434
+ environment: {
26435
+ FRIZZ_STABLE_ARTIFACT: `npm:${generation.package}@${generation.version}`,
26436
+ FRIZZ_STABLE_WEB_DIST: join15(generation.root, "web-dist"),
26437
+ FRIZZ_SCRIPTS_DIR: join15(generation.root, "runtime/board"),
26438
+ FRIZZ_WORKER_PLUGIN_DIR: join15(generation.root, "runtime/cc-worker")
26439
+ }
26440
+ };
26441
+ }
26442
+ function resolveNpmCli(env = process.env, executable = process.execPath) {
26443
+ const candidates = [];
26444
+ if (env.npm_execpath?.endsWith("npm-cli.js")) candidates.push(env.npm_execpath);
26445
+ for (const directory of [dirname13(executable), ...(env.PATH ?? env.Path ?? "").split(delimiter2).filter(Boolean)]) {
26446
+ candidates.push(join15(directory, "node_modules/npm/bin/npm-cli.js"), join15(directory, "../lib/node_modules/npm/bin/npm-cli.js"));
26447
+ try {
26448
+ const path = realpathSync6(join15(directory, "npm"));
26449
+ if (path.endsWith("npm-cli.js")) candidates.push(path);
26450
+ } catch {
26451
+ }
26452
+ }
26453
+ const found = candidates.find((path) => existsSync8(path) && statSync5(path).isFile());
26454
+ if (!found) throw new Error("npm was not found beside Node or on PATH; install Node with npm to install Frizz server releases");
26455
+ return resolve6(found);
26456
+ }
26457
+ function npmServerPackageInstaller(env = process.env) {
26458
+ const run3 = (args2, cwd) => new Promise((resolveOutput, reject) => {
26459
+ let cli;
26460
+ try {
26461
+ cli = resolveNpmCli(env);
26462
+ } catch (error) {
26463
+ reject(error);
26464
+ return;
26465
+ }
26466
+ execFile5(process.execPath, [cli, ...args2], {
26467
+ env,
26468
+ cwd,
26469
+ encoding: "utf8",
26470
+ windowsHide: true,
26471
+ timeout: 18e4,
26472
+ killSignal: "SIGKILL",
26473
+ maxBuffer: 4 * 1024 * 1024
26474
+ }, (error, stdout, stderr) => {
26475
+ if (error) {
26476
+ const tail = stderr.trim().split("\n").slice(-8).join("\n");
26477
+ reject(new Error(`Frizz server npm operation failed: ${tail || error.message}`));
26478
+ } else resolveOutput(stdout);
26479
+ });
26480
+ });
26481
+ return {
26482
+ async install(prefix, spec) {
26483
+ assertSpec(spec);
26484
+ await run3([
26485
+ "install",
26486
+ "--prefix",
26487
+ prefix,
26488
+ "--global=false",
26489
+ "--ignore-scripts",
26490
+ "--no-audit",
26491
+ "--no-fund",
26492
+ "--engine-strict",
26493
+ "--install-strategy=hoisted",
26494
+ "--include=optional",
26495
+ "--omit=dev",
26496
+ "--save-exact",
26497
+ `${spec.package}@${spec.version}`
26498
+ ], prefix);
26499
+ },
26500
+ async latestVersion(packageName) {
26501
+ if (!PACKAGE.test(packageName)) throw new Error("invalid Frizz server package name");
26502
+ const version = JSON.parse(await run3(["view", `${packageName}@latest`, "version", "--json"]));
26503
+ if (typeof version !== "string" || !VERSION.test(version)) throw new Error("npm returned an invalid Frizz server version");
26504
+ return version;
26505
+ }
26506
+ };
26507
+ }
26508
+ function atomicJson2(path, value) {
26509
+ mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
26510
+ const temp = `${path}.${randomUUID6()}.tmp`;
26511
+ try {
26512
+ const fd = openSync6(temp, "wx", 384);
26513
+ try {
26514
+ writeFileSync11(fd, `${JSON.stringify(value)}
26515
+ `);
26516
+ fsyncSync5(fd);
26517
+ } finally {
26518
+ closeSync5(fd);
26519
+ }
26520
+ renameSync6(temp, path);
26521
+ try {
26522
+ const directory = openSync6(dirname13(path), "r");
26523
+ try {
26524
+ fsyncSync5(directory);
26525
+ } finally {
26526
+ closeSync5(directory);
26527
+ }
26528
+ } catch {
26529
+ }
26530
+ } finally {
26531
+ rmSync9(temp, { force: true });
26532
+ }
26533
+ }
26534
+ var ServerReleaseStore = class {
26535
+ constructor(spec, installer, roots = frizzPaths(), compatibility = CURRENT_COMPATIBILITY) {
26536
+ this.spec = spec;
26537
+ this.installer = installer;
26538
+ this.roots = roots;
26539
+ this.compatibility = compatibility;
26540
+ assertSpec(spec, compatibility);
26541
+ const key = createHash4("sha256").update(spec.package).digest("hex").slice(0, 16);
26542
+ this.generations = join15(roots.cache, "server-releases", key);
26543
+ this.selection = join15(roots.state, "server-releases", key, "active.json");
26544
+ this.compatibilityMarker = join15(roots.state, "server-releases", "data-compatibility.json");
26545
+ }
26546
+ generations;
26547
+ selection;
26548
+ /** One global data marker: every package override opens the same Frizz databases. */
26549
+ compatibilityMarker;
26550
+ packageRoot(id) {
26551
+ return join15(this.generations, id, "node_modules", this.spec.package);
26552
+ }
26553
+ readCompatibilityMarker() {
26554
+ if (!existsSync8(this.compatibilityMarker)) return void 0;
26555
+ try {
26556
+ const marker = record(readJson(this.compatibilityMarker));
26557
+ const compatibility = { protocol: marker.protocol, dataEpoch: marker.dataEpoch };
26558
+ assertSpecShape({ package: this.spec.package, version: this.spec.version, ...compatibility });
26559
+ return compatibility;
26560
+ } catch {
26561
+ throw new Error("invalid saved Frizz server data compatibility marker; refusing to risk a data downgrade");
26562
+ }
26563
+ }
26564
+ assertMarkerIsSupported() {
26565
+ const marker = this.readCompatibilityMarker();
26566
+ if (!marker) return;
26567
+ if (marker.protocol !== this.compatibility.protocol || marker.dataEpoch > this.compatibility.dataEpoch)
26568
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26569
+ }
26570
+ /**
26571
+ * Advance the global epoch only after the exact replacement has been staged and validated, but
26572
+ * before handing it to the caller that can launch it. A crash before first boot must never let an
26573
+ * older shell reopen the database the candidate may already have migrated.
26574
+ */
26575
+ markCompatibilityBeforeLaunch() {
26576
+ const marker = this.readCompatibilityMarker();
26577
+ if (marker?.protocol !== void 0 && marker.protocol !== this.compatibility.protocol)
26578
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26579
+ if ((marker?.dataEpoch ?? 0) > this.compatibility.dataEpoch)
26580
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26581
+ if (marker?.dataEpoch === this.compatibility.dataEpoch) return;
26582
+ atomicJson2(this.compatibilityMarker, this.compatibility);
26583
+ }
26584
+ async prepareInitial(version) {
26585
+ const generation = await this.prepare(version);
26586
+ this.markCompatibilityBeforeLaunch();
26587
+ return generation;
26588
+ }
26589
+ async load() {
26590
+ this.assertMarkerIsSupported();
26591
+ if (!existsSync8(this.selection)) return this.prepareInitial(this.spec.version);
26592
+ const selected = record(readJson(this.selection));
26593
+ const spec = selected;
26594
+ assertSpecShape(spec);
26595
+ if (spec.package !== this.spec.package || typeof selected.id !== "string" || !GENERATION.test(selected.id))
26596
+ throw new Error("invalid saved Frizz server selection; refusing to fall back to an older server");
26597
+ if (spec.protocol !== this.compatibility.protocol)
26598
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26599
+ if (spec.dataEpoch < this.compatibility.dataEpoch) return this.prepareInitial(this.spec.version);
26600
+ if (spec.dataEpoch > this.compatibility.dataEpoch)
26601
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26602
+ const root = this.packageRoot(selected.id);
26603
+ const generation = !existsSync8(root) ? await this.prepare(spec.version) : validateServerGeneration(root, spec, selected.id, this.compatibility);
26604
+ this.markCompatibilityBeforeLaunch();
26605
+ return generation;
26606
+ }
26607
+ async prepare(version) {
26608
+ const spec = { ...this.spec, version };
26609
+ assertSpec(spec, this.compatibility);
26610
+ mkdirSync12(this.generations, { recursive: true, mode: 448 });
26611
+ const id = randomUUID6();
26612
+ const staging = join15(this.generations, `${id}.staging`);
26613
+ const destination = join15(this.generations, id);
26614
+ mkdirSync12(staging, { mode: 448 });
26615
+ try {
26616
+ writeFileSync11(join15(staging, "package.json"), '{"private":true}\n', { mode: 384 });
26617
+ await this.installer.install(staging, spec);
26618
+ validateServerGeneration(join15(staging, "node_modules", spec.package), spec, id, this.compatibility);
26619
+ renameSync6(staging, destination);
26620
+ return validateServerGeneration(this.packageRoot(id), spec, id, this.compatibility);
26621
+ } finally {
26622
+ rmSync9(staging, { recursive: true, force: true });
26623
+ }
26624
+ }
26625
+ commit(generation) {
26626
+ if (generation.package !== this.spec.package || generation.root !== realpathSync6(this.packageRoot(generation.id)))
26627
+ throw new Error("cannot select a Frizz server outside this release store");
26628
+ validateServerGeneration(generation.root, generation, generation.id, this.compatibility);
26629
+ const { package: packageName, version, protocol, dataEpoch, id } = generation;
26630
+ atomicJson2(this.selection, { package: packageName, version, protocol, dataEpoch, id });
26631
+ }
26632
+ };
26633
+
26634
+ // src/server-owner.ts
26635
+ init_frizz_paths();
26636
+ init_project_launch();
26637
+ import { randomUUID as randomUUID7 } from "node:crypto";
26638
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, realpathSync as realpathSync7 } from "node:fs";
26639
+ import { join as join16 } from "node:path";
26640
+ var STABLE_SERVER_OWNER_NAME = "frizz-server";
26641
+ var STABLE_SERVER_OWNER_PROJECT_ID = "c1fd5810-0f8a-4c1d-91a0-6d7445d28e5a";
26642
+ var ADDRESS_NAME = "address.json";
26643
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
26644
+ function rootsFor(roots) {
26645
+ return roots ?? frizzPaths();
26646
+ }
26647
+ function stableServerOwnerTarget(roots) {
26648
+ const resolved = rootsFor(roots);
26649
+ mkdirSync13(resolved.data, { recursive: true, mode: 448 });
26650
+ return {
26651
+ projectId: STABLE_SERVER_OWNER_PROJECT_ID,
26652
+ projectDir: realpathSync7(resolved.data),
26653
+ stateDir: join16(resolved.state, STABLE_SERVER_OWNER_NAME)
26654
+ };
26655
+ }
26656
+ function matchingTarget(owner, target2) {
26657
+ return owner.projectId === target2.projectId && owner.projectDir === target2.projectDir;
26658
+ }
26659
+ function readAddress(path) {
26660
+ try {
26661
+ const value = JSON.parse(readFileSync14(path, "utf8"));
26662
+ if (value.version !== 1 || typeof value.ownerToken !== "string" || !UUID_RE2.test(value.ownerToken) || typeof value.publisherToken !== "string" || !UUID_RE2.test(value.publisherToken) || typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0 || typeof value.processStart !== "string" || value.processStart.length === 0 || typeof value.port !== "number" || !Number.isInteger(value.port) || value.port < 1 || value.port > 65535) return null;
26663
+ return value;
26664
+ } catch {
26665
+ return null;
26666
+ }
26667
+ }
26668
+ function readStableServerOwner(roots) {
26669
+ const target2 = stableServerOwnerTarget(roots);
26670
+ const owner = readProjectLaunchOwner(target2.stateDir);
26671
+ if (!owner || !matchingTarget(owner, target2) || processGenerationIsStale(owner)) return { kind: "idle" };
26672
+ const address = readAddress(join16(target2.stateDir, ADDRESS_NAME));
26673
+ if (!address || address.ownerToken !== owner.token || address.pid !== owner.pid || address.processStart !== owner.processStart) return { kind: "busy", owner };
26674
+ return { kind: "running", port: address.port, owner };
26675
+ }
26676
+ function wrapLease(lease) {
26677
+ return {
26678
+ target: lease.target,
26679
+ token: lease.token,
26680
+ pid: lease.pid,
26681
+ processStart: lease.processStart,
26682
+ publisherToken: randomUUID7()
26683
+ };
26684
+ }
26685
+ function acquireStableServerOwner(roots) {
26686
+ const target2 = stableServerOwnerTarget(roots);
26687
+ const attempt = tryAcquireProjectLaunchOwner(target2, "server");
26688
+ if (attempt.kind === "acquired") return { kind: "acquired", lease: holdLease(attempt.lease) };
26689
+ const observed = readStableServerOwner(roots);
26690
+ return observed.kind === "running" || observed.kind === "busy" ? observed : { kind: "busy", owner: attempt.owner };
26691
+ }
26692
+ function publishStableServerAddress(lease, port) {
26693
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`invalid stable Frizz server port: ${port}`);
26694
+ const owner = readProjectLaunchOwner(lease.target.stateDir);
26695
+ if (!owner || !matchingTarget(owner, lease.target) || owner.token !== lease.token || owner.pid !== lease.pid || owner.processStart !== lease.processStart || processGenerationIsStale(owner)) throw new Error("cannot publish an address without the exact live stable Frizz server owner");
26696
+ writeProjectStatus(join16(lease.target.stateDir, ADDRESS_NAME), {
26697
+ version: 1,
26698
+ ownerToken: lease.token,
26699
+ pid: lease.pid,
26700
+ processStart: lease.processStart,
26701
+ publisherToken: lease.publisherToken,
26702
+ port
26703
+ });
26704
+ }
26705
+ function releaseStableServerOwner(lease) {
26706
+ return releaseHeldLease(lease);
26707
+ }
26708
+ var heldLeases = /* @__PURE__ */ new Map();
26709
+ function holdLease(lease) {
26710
+ const wrapped = wrapLease(lease);
26711
+ heldLeases.set(wrapped.publisherToken, lease);
26712
+ return wrapped;
26713
+ }
26714
+ function releaseHeldLease(lease) {
26715
+ const held = heldLeases.get(lease.publisherToken);
26716
+ if (!held || held.token !== lease.token || held.pid !== lease.pid || held.processStart !== lease.processStart) return false;
26717
+ const released = held.release();
26718
+ if (!released) return false;
26719
+ heldLeases.delete(lease.publisherToken);
26720
+ removeProjectStatus(join16(lease.target.stateDir, ADDRESS_NAME), {
26721
+ ownerToken: lease.token,
26722
+ pid: lease.pid,
26723
+ processStart: lease.processStart,
26724
+ publisherToken: lease.publisherToken
26725
+ });
26726
+ return true;
26727
+ }
26728
+
26297
26729
  // src/preflight.ts
26730
+ init_native_helper();
26298
26731
  import { spawnSync } from "node:child_process";
26299
- import { chmodSync as chmodSync2, statSync as statSync5 } from "node:fs";
26300
- import { createRequire } from "node:module";
26301
- import { dirname as dirname13, join as join15 } from "node:path";
26302
26732
  var SUPPORTED_NODE_LINES = [
26303
26733
  { major: 22, minor: 13 },
26304
26734
  { major: 23, minor: 4 }
@@ -26342,25 +26772,6 @@ function assertLaunchPrerequisites(options2 = {}) {
26342
26772
  );
26343
26773
  assertRequiredExecutables(options2.command ?? commandIsAvailable);
26344
26774
  }
26345
- function ensureNativeHelperPermissions(options2 = {}) {
26346
- const platform = options2.platform ?? process.platform;
26347
- if (platform === "win32") return;
26348
- const stat = options2.stat ?? ((path) => statSync5(path));
26349
- const chmod = options2.chmod ?? ((path, mode) => chmodSync2(path, mode));
26350
- const resolvePty = options2.resolvePty ?? (() => createRequire(import.meta.url).resolve("node-pty/package.json"));
26351
- try {
26352
- const helper = join15(
26353
- dirname13(resolvePty()),
26354
- "prebuilds",
26355
- `${platform}-${options2.arch ?? process.arch}`,
26356
- "spawn-helper"
26357
- );
26358
- const mode = stat(helper).mode;
26359
- if ((mode & 73) === 73) return;
26360
- chmod(helper, mode | 493);
26361
- } catch {
26362
- }
26363
- }
26364
26775
 
26365
26776
  // src/production.ts
26366
26777
  init_src();
@@ -26369,7 +26780,7 @@ init_project_identity();
26369
26780
  var PACKAGE_NAME = process.env.FRIZZ_REGISTRY_PACKAGE ?? "frizz";
26370
26781
  function resolvePackageVersion() {
26371
26782
  try {
26372
- const manifest = JSON.parse(readFileSync13(join16(import.meta.dirname, "..", "package.json"), "utf8"));
26783
+ const manifest = JSON.parse(readFileSync15(join17(import.meta.dirname, "..", "package.json"), "utf8"));
26373
26784
  if (typeof manifest.version === "string" && manifest.version.trim()) return manifest.version.trim();
26374
26785
  } catch {
26375
26786
  }
@@ -26433,10 +26844,13 @@ var sandbox = options.sandbox ? prepareSandbox() : null;
26433
26844
  if (sandbox) process.on("exit", () => cleanupSandbox(sandbox.home));
26434
26845
  var remote = null;
26435
26846
  var paneHost = null;
26436
- var rearmPaneHost = null;
26437
26847
  var globalInstall = process.env[GLOBAL_INSTALL_ENV] === "1" || isGlobalInstall(import.meta.dirname);
26438
26848
  var accessPane = null;
26439
26849
  var activeAccessLink = null;
26850
+ var serverOwner;
26851
+ process.on("exit", () => {
26852
+ if (serverOwner) releaseStableServerOwner(serverOwner);
26853
+ });
26440
26854
  var launchIntent;
26441
26855
  var workspace = (() => {
26442
26856
  try {
@@ -26522,15 +26936,15 @@ if (options.link) {
26522
26936
  var logger = setAmbientLogger(
26523
26937
  process.env.FRIZZ_LOG_FILE ? createLogger({ file: process.env.FRIZZ_LOG_FILE, owner: false }) : createLogger({ file: runLogPath(workspace.stateDir) })
26524
26938
  );
26525
- var readout = reexec || process.env.FRIZZ_PRODUCTION_SUPERVISOR === "1" ? void 0 : new Readout({ debug: options.debug, version: PACKAGE_VERSION });
26526
- var activityReadout = readout ?? (reexec && process.stdout.isTTY ? noticeOnlyReadout({ version: PACKAGE_VERSION }) : void 0);
26939
+ var readout = reexec || process.env.FRIZZ_PRODUCTION_SUPERVISOR === "1" ? void 0 : new Readout({ debug: options.debug });
26940
+ var activityReadout = readout ?? (reexec && process.stdout.isTTY ? noticeOnlyReadout({}) : void 0);
26527
26941
  attachTerminalMirror(logger, options.debug || process.env.FRIZZ_DEBUG === "1");
26528
26942
  readout?.plan([
26529
26943
  { key: "server", label: "Server" },
26530
26944
  { key: "browser", label: options.noApp ? "Address" : "Browser" }
26531
26945
  ]);
26532
26946
  readout?.begin("server", "starting");
26533
- logger.info("launcher", `frizz ${PACKAGE_VERSION} starting for ${workspace.root}`);
26947
+ logger.info("launcher", `Frizz launcher ${PACKAGE_VERSION} starting for ${workspace.root}`);
26534
26948
  var target = workspaceLaunchTarget(workspace);
26535
26949
  var expected = expectedOwnerHealth(target, readProjectLaunchOwner(workspace.stateDir));
26536
26950
  async function existingPort() {
@@ -26579,7 +26993,7 @@ async function openOrPrint(port, reused, path = "") {
26579
26993
  readout?.begin("browser", options.appMode ? "requesting app window" : "requesting default browser");
26580
26994
  try {
26581
26995
  if (options.appMode) {
26582
- await launchApp(url, { dataPath: join16(workspace.stateDir, "browser-profile") });
26996
+ await launchApp(url, { dataPath: join17(workspace.stateDir, "browser-profile") });
26583
26997
  browser = reused ? "focused the Frizz app window" : "opened the Frizz app window";
26584
26998
  } else {
26585
26999
  await launchBrowserTab(url);
@@ -26598,7 +27012,7 @@ async function openOrPrint(port, reused, path = "") {
26598
27012
  const warnings = [];
26599
27013
  if (sandbox) warnings.push(`Sandbox: everything here is throwaway (${sandbox.home}) and is deleted when this terminal closes.`);
26600
27014
  if (!readout) {
26601
- console.log(`${reused ? "reusing" : "started"} Frizz ${PACKAGE_VERSION} for ${workspace.root}`);
27015
+ console.log(`${reused ? "reusing" : "started"} Frizz for ${workspace.root}`);
26602
27016
  console.log(url);
26603
27017
  if (publicOrigin) console.log(activeAccessLink?.url ?? `${publicOrigin}/`);
26604
27018
  for (const warning of warnings) console.log(warning);
@@ -26623,31 +27037,40 @@ async function openOrPrint(port, reused, path = "") {
26623
27037
  }
26624
27038
  );
26625
27039
  }
26626
- async function runSupervisor(port, token) {
27040
+ async function runSupervisor(port, token, onPrepared = () => {
27041
+ }) {
26627
27042
  assertLaunchPrerequisites();
26628
- ensureNativeHelperPermissions();
27043
+ if (!serverOwner) throw new Error("stable server launch is missing global ownership");
26629
27044
  const owner = adoptProjectLaunchOwner(target, token, "supervisor");
26630
27045
  const env = projectLaunchEnvironment(
26631
27046
  {
26632
27047
  ...process.env,
26633
27048
  FRIZZ_PRODUCTION_SUPERVISOR: "1",
27049
+ FRIZZ_SERVER_OWNERSHIP: JSON.stringify(projectLaunchEnvironment({}, serverOwner.target, serverOwner.token)),
26634
27050
  ...logEnvironment(logger, options.debug ? "debug" : "info"),
26635
27051
  ...options.debug ? { FRIZZ_DEBUG: "1" } : {}
26636
27052
  },
26637
27053
  target,
26638
27054
  owner.token
26639
27055
  );
26640
- const webDist = join16(import.meta.dirname, "..", "web-dist");
26641
- const runtimeDir = join16(import.meta.dirname, "..", "runtime");
26642
- const scriptsDir = join16(runtimeDir, "board");
26643
- const workerPluginDir = join16(runtimeDir, "cc-worker");
26644
- const childEntry = fileURLToPath2(new URL("./dev-child.js", import.meta.url));
26645
- let plannedSuccessor;
27056
+ const spec = serverReleaseSpec(
27057
+ JSON.parse(readFileSync15(join17(import.meta.dirname, "..", "package.json"), "utf8")),
27058
+ process.env.FRIZZ_SERVER_PACKAGE
27059
+ );
27060
+ const installer = npmServerPackageInstaller(env);
27061
+ const store = new ServerReleaseStore(spec, installer);
27062
+ activityReadout?.notice("progress", "Starting", "loading the selected Frizz server release");
27063
+ let active = await store.load();
27064
+ let pending;
27065
+ let previous = active;
27066
+ onPrepared();
26646
27067
  let updateAvailable = false;
26647
27068
  let updateVersion;
26648
27069
  const refreshUpdateAvailable = async () => {
26649
27070
  try {
26650
- const plan = await planRegistryUpdate(PACKAGE_NAME, PACKAGE_VERSION, npmRegistryReleaseAdapter);
27071
+ const checkedVersion = active.version;
27072
+ const plan = await planRegistryUpdate(spec.package, checkedVersion, installer);
27073
+ if (active.version !== checkedVersion) return;
26651
27074
  updateAvailable = plan !== null;
26652
27075
  updateVersion = plan?.latestVersion;
26653
27076
  } catch {
@@ -26673,83 +27096,48 @@ async function runSupervisor(port, token) {
26673
27096
  launchOwnerToken: owner.token,
26674
27097
  env,
26675
27098
  watch: false,
26676
- childEntry,
26677
- childEnvironment: () => ({ FRIZZ_STABLE_WEB_DIST: webDist, FRIZZ_STABLE_ARTIFACT: `npm:${PACKAGE_NAME}@${PACKAGE_VERSION}`, FRIZZ_SCRIPTS_DIR: scriptsDir, FRIZZ_WORKER_PLUGIN_DIR: workerPluginDir }),
27099
+ childLaunchProvider: () => serverGenerationLaunch(pending ?? active),
27100
+ updateMode: "child",
26678
27101
  updateAvailable: () => updateAvailable,
26679
- version: PACKAGE_VERSION,
27102
+ version: () => active.version,
27103
+ launcherVersion: PACKAGE_VERSION,
26680
27104
  updateVersion: () => updateVersion,
26681
27105
  // The terminal that owns the board says so when the board goes down and comes back. Everything
26682
27106
  // here is triggered from a browser tab or by a crash, so without this the foreground process is
26683
27107
  // the last place to learn what happened to it.
26684
27108
  onActivity: (event) => renderSupervisorActivity(activityReadout, event),
26685
27109
  updateRestart: async () => {
27110
+ previous = active;
26686
27111
  try {
26687
- const plan = await planRegistryUpdate(PACKAGE_NAME, PACKAGE_VERSION, npmRegistryReleaseAdapter);
27112
+ const plan = await planRegistryUpdate(spec.package, active.version, installer);
26688
27113
  if (!plan) {
26689
27114
  updateAvailable = false;
26690
27115
  updateVersion = void 0;
26691
- return { state: "failed", message: `Frizz ${PACKAGE_VERSION} is already current` };
27116
+ return { state: "failed", message: `Frizz ${active.version} is already current` };
26692
27117
  }
27118
+ updateAvailable = true;
26693
27119
  updateVersion = plan.latestVersion;
26694
- plannedSuccessor = await resolveRegistrySuccessor(plan, { cwd: workspace.root, env }, npmRegistryReleaseAdapter);
26695
- return { state: "ready", message: `Frizz ${plan.latestVersion} is installed in its own npm execution cache` };
27120
+ pending = await store.prepare(plan.latestVersion);
27121
+ return { state: "ready", message: `Frizz ${plan.latestVersion} is installed and compatible` };
26696
27122
  } catch (error) {
26697
27123
  return { state: "failed", message: error instanceof Error ? error.message : String(error) };
26698
27124
  }
26699
27125
  },
26700
- // The registry launcher promotes no pointer — the release sits in npm's own execution cache and
26701
- // the running package is never touched — so there is nothing to roll back. Present and empty
26702
- // rather than absent: the supervisor appends "rollback callback is unavailable" to every failed
26703
- // handoff it cannot roll back, which for this launcher was noise on a message that already said
26704
- // what went wrong (audit 2026-09-11, finding 4).
26705
- rollbackUpdate: () => {
27126
+ commitUpdate: () => {
27127
+ if (!pending) throw new Error("no server update was prepared");
27128
+ store.commit(pending);
27129
+ active = pending;
27130
+ pending = void 0;
27131
+ updateAvailable = false;
27132
+ updateVersion = void 0;
26706
27133
  },
26707
- durableReexec: async () => {
26708
- const successor = plannedSuccessor;
26709
- if (!successor) throw new Error("no update was prepared");
26710
- const { plan } = successor;
26711
- const successorEnv = {
26712
- ...env,
26713
- FRIZZ_REGISTRY_PACKAGE: plan.packageName,
26714
- FRIZZ_REGISTRY_VERSION: plan.latestVersion,
26715
- ...globalInstall ? { [GLOBAL_INSTALL_ENV]: "1" } : {}
26716
- };
26717
- if (canReexecInPlace()) {
26718
- activityReadout?.notice("progress", "Updating", `reloading this launcher in place on Frizz ${plan.latestVersion}`);
26719
- paneHost?.dispose();
26720
- remote?.stop();
26721
- try {
26722
- reexecIntoRegistrySuccessor(successor, { port, env: successorEnv });
26723
- } catch (error) {
26724
- rearmPaneHost?.();
26725
- try {
26726
- await remote?.serveSaved();
26727
- } catch (remoteError) {
26728
- const message = remoteError instanceof Error ? remoteError.message : String(remoteError);
26729
- logger.error("remote", `could not re-arm the saved remote setup after a failed update: ${message}`);
26730
- }
26731
- throw error;
26732
- }
26733
- }
26734
- const child = handoffToRegistrySuccessor(successor, { port, cwd: workspace.root, env: successorEnv }, npmRegistryReleaseAdapter);
26735
- activityReadout?.notice("progress", "Updating", `starting Frizz ${plan.latestVersion} in the background and waiting for it to answer on port ${port}`);
26736
- await awaitRegistrySuccessor(
26737
- child,
26738
- { version: plan.latestVersion, port, logFile: successorEnv.FRIZZ_LOG_FILE },
26739
- { status: () => readLauncherStatus(port) }
26740
- );
26741
- paneHost?.dispose();
26742
- remote?.stop();
26743
- activityReadout?.notice("done", "Updated", `Frizz ${plan.latestVersion} is serving on port ${port}`);
26744
- activityReadout?.note(
26745
- `
26746
- Frizz ${plan.latestVersion} now runs in the background \u2014 ctrl-c here no longer reaches it. Stop it with ${PACKAGE_NAME} --stop.${globalInstall ? `
26747
- Update: ${keepUpdateHint(PACKAGE_NAME, plan.latestVersion)}.` : ""}
26748
- `
26749
- );
26750
- process.exit(0);
27134
+ rollbackUpdate: () => {
27135
+ if (active.id !== previous.id) store.commit(previous);
27136
+ active = previous;
27137
+ pending = void 0;
26751
27138
  }
26752
27139
  });
27140
+ publishStableServerAddress(serverOwner, port);
26753
27141
  remote = createRemoteController({ host: supervisor, port, log: logger, say: (message) => console.error(`frizz: ${message}`) });
26754
27142
  try {
26755
27143
  await remote.serveSaved();
@@ -26770,16 +27158,14 @@ async function runSupervisor(port, token) {
26770
27158
  onChanged: (config) => logger.info("remote", config ? `reached at https://${config.hostname}` : "loopback only"),
26771
27159
  sandbox: sandbox !== null
26772
27160
  });
26773
- const bindings = { l: accessPane, L: accessPane, r: remotePane, R: remotePane };
26774
- const armPaneHost = () => {
26775
- paneHost = installPaneHost({ bindings });
26776
- };
26777
- armPaneHost();
26778
- rearmPaneHost = armPaneHost;
27161
+ paneHost = installPaneHost({ bindings: { l: accessPane, L: accessPane, r: remotePane, R: remotePane } });
26779
27162
  const stop = createSupervisorShutdownHandler({
26780
27163
  close: () => supervisor.close(),
26781
27164
  force: () => supervisor.forceStop(),
26782
- release: () => owner.release(),
27165
+ release: () => {
27166
+ owner.release();
27167
+ if (serverOwner) releaseStableServerOwner(serverOwner);
27168
+ },
26783
27169
  // Acknowledge the first signal on the spot; the drain that follows is bounded but not instant.
26784
27170
  onStop: () => {
26785
27171
  logger.info("launcher", "stop signal received; draining the control plane");
@@ -26809,17 +27195,28 @@ async function runSupervisor(port, token) {
26809
27195
  process.on("SIGHUP", stop);
26810
27196
  void supervisor.stopRequested.then(stop);
26811
27197
  await supervisor.firstBoot;
27198
+ store.commit(active);
27199
+ activityReadout?.notice("done", "Server", `${active.version} \xB7 launcher ${PACKAGE_VERSION}`);
26812
27200
  if (reexec) {
26813
27201
  activityReadout?.notice(
26814
27202
  "done",
26815
27203
  "Updated",
26816
- `Frizz ${PACKAGE_VERSION} is serving on port ${port} \xB7 ctrl-c to stop${globalInstall ? ` \xB7 ${keepUpdateHint(PACKAGE_NAME, PACKAGE_VERSION)}` : ""}`
27204
+ `Frizz ${active.version} is serving on port ${port} \xB7 ctrl-c to stop${globalInstall ? ` \xB7 ${keepUpdateHint(PACKAGE_NAME, PACKAGE_VERSION)}` : ""}`
26817
27205
  );
26818
27206
  }
26819
27207
  return await new Promise(() => {
26820
27208
  });
26821
27209
  }
26822
27210
  try {
27211
+ const globalClaim = acquireStableServerOwner();
27212
+ if (globalClaim.kind === "busy") throw new Error("Frizz is starting or recovering on this machine; retry shortly");
27213
+ if (globalClaim.kind === "running") {
27214
+ if (options.port && options.port !== globalClaim.port)
27215
+ throw new Error(`Frizz is already running on port ${globalClaim.port}; --port cannot change a running server`);
27216
+ await openOrPrint(globalClaim.port, true, slugPath());
27217
+ process.exit(0);
27218
+ }
27219
+ serverOwner = globalClaim.lease;
26823
27220
  if (process.env.FRIZZ_PRODUCTION_SUPERVISOR === "1" || reexec) {
26824
27221
  if (!options.port) throw new Error("internal registry supervisor launch is missing --port");
26825
27222
  const token = projectLaunchOwnerTokenFromEnvironment(process.env);
@@ -26844,9 +27241,14 @@ try {
26844
27241
  const allocation = await allocatePort(options.port, readPreferredPort(workspace.stateDir), { host: LOOPBACK_BIND_HOST });
26845
27242
  const port = allocation.port;
26846
27243
  portReservation = allocation.release;
26847
- const running = runSupervisor(port, claim.lease.token);
27244
+ let didPrepare;
27245
+ const prepared = new Promise((resolve7) => {
27246
+ didPrepare = resolve7;
27247
+ });
27248
+ const running = runSupervisor(port, claim.lease.token, didPrepare);
26848
27249
  release();
26849
27250
  release = void 0;
27251
+ await Promise.race([prepared, running]);
26850
27252
  await Promise.race([
26851
27253
  waitForWorkspace(
26852
27254
  port,