frizz 0.12.13 → 0.13.1

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 +872 -369
  2. package/package.json +13 -13
  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-Cv0c_SNS.js +0 -7
  47. package/web-dist/assets/abnfDiagram-VRR7QNED-B-wED8ht.js +0 -1
  48. package/web-dist/assets/arc-BSyeo0Gb.js +0 -1
  49. package/web-dist/assets/architecture-TIHT7OUA-nsH-QfGr.js +0 -1
  50. package/web-dist/assets/architectureDiagram-ZJ3FMSHR-kHm_O2sR.js +0 -36
  51. package/web-dist/assets/array-BifhSqXX.js +0 -1
  52. package/web-dist/assets/blockDiagram-677ZJIJ3-CdazUaKU.js +0 -132
  53. package/web-dist/assets/c4Diagram-LMCZKHZV-rauLKZbJ.js +0 -10
  54. package/web-dist/assets/channel-fvwjeUt3.js +0 -1
  55. package/web-dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +0 -1
  56. package/web-dist/assets/chunk-32BRIVSS-DjOqyEDX.js +0 -1
  57. package/web-dist/assets/chunk-52WLFC77-f1SThegZ.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-BfQsesjz.js +0 -1
  61. package/web-dist/assets/chunk-EX3LRPZG-BIzZW7OQ.js +0 -231
  62. package/web-dist/assets/chunk-FWX5IMBZ-CATINZFC.js +0 -2
  63. package/web-dist/assets/chunk-HOUHSVGY-DbMBRCCE.js +0 -1
  64. package/web-dist/assets/chunk-ICXQ74PX-COcoSWx4.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-CVnI-o_4.js +0 -88
  68. package/web-dist/assets/chunk-OGEWGWER-U0vdZqB6.js +0 -1
  69. package/web-dist/assets/chunk-PUDLZKDR-Bukb7rem.js +0 -156
  70. package/web-dist/assets/chunk-Q4XR5HBZ-Bl019PHd.js +0 -70
  71. package/web-dist/assets/chunk-RYQCIY6F-Cu_KplZW.js +0 -1
  72. package/web-dist/assets/chunk-V7JOEXUC-YJR0vzI6.js +0 -206
  73. package/web-dist/assets/chunk-VAUOI2AC-CK6NU9kj.js +0 -1
  74. package/web-dist/assets/chunk-VR4S4FIN-B3LBv3C3.js +0 -1
  75. package/web-dist/assets/chunk-WYO6CB5R-DjRPyRuS.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-BUJCayKP.js +0 -62
  79. package/web-dist/assets/chunk-ZIRB5QZD-C6fEPe3t.js +0 -32
  80. package/web-dist/assets/classDiagram-OUVF2IWQ-BRZQ_xnN.js +0 -1
  81. package/web-dist/assets/classDiagram-v2-EOCWNBFH-BRZQ_xnN.js +0 -1
  82. package/web-dist/assets/cose-bilkent-JH36ORCC-BUIsLrGc.js +0 -1
  83. package/web-dist/assets/cynefin-VYW2F7L2-CE3ce51v.js +0 -1
  84. package/web-dist/assets/cynefinDiagram-TSTJHNR4-DSXSPZQV.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-VwBQdjum.js +0 -4
  88. package/web-dist/assets/defaultLocale-C8Fc0cco.js +0 -1
  89. package/web-dist/assets/diagram-FQU43EPY-7Tf5_WDu.js +0 -3
  90. package/web-dist/assets/diagram-G47NLZAW-mu4aclWA.js +0 -24
  91. package/web-dist/assets/diagram-NH7WQ7WH-B3kAVNwv.js +0 -24
  92. package/web-dist/assets/diagram-OA4YK3LP-DAaV-2ou.js +0 -30
  93. package/web-dist/assets/diagram-WEI45ONY-DijPfgly.js +0 -41
  94. package/web-dist/assets/dist-DoH_9pyS.js +0 -1
  95. package/web-dist/assets/ebnfDiagram-CCIWWBDH-B1o42Wp-.js +0 -1
  96. package/web-dist/assets/erDiagram-Q63AITRT-Dqej9Wi3.js +0 -85
  97. package/web-dist/assets/eventmodeling-45OFAUF4-B8xeGcXU.js +0 -1
  98. package/web-dist/assets/flowDiagram-23GEKE2U-Cnyfcz6Z.js +0 -1
  99. package/web-dist/assets/ganttDiagram-NO4QXBWP-UoyrLPUT.js +0 -292
  100. package/web-dist/assets/gitGraph-TEB2WS4Q-CwjIwQ3T.js +0 -1
  101. package/web-dist/assets/gitGraphDiagram-IHSO6WYX-BxU758_g.js +0 -106
  102. package/web-dist/assets/graphlib-B8gBHxth.js +0 -1
  103. package/web-dist/assets/index-CT6k_A5y.css +0 -1
  104. package/web-dist/assets/index-k8HCkyvh.js +0 -319
  105. package/web-dist/assets/info-DKCQHKI2-CWtSI6aK.js +0 -1
  106. package/web-dist/assets/infoDiagram-FWYZ7A6U-n5xRfy_d.js +0 -2
  107. package/web-dist/assets/init-D6jRqBbL.js +0 -1
  108. package/web-dist/assets/ishikawaDiagram-FXEZZL3T-DU1LTQY-.js +0 -70
  109. package/web-dist/assets/journeyDiagram-5HDEW3XC-BzZWHoPv.js +0 -139
  110. package/web-dist/assets/kanban-definition-HUTT4EX6-CeneyR7c.js +0 -89
  111. package/web-dist/assets/katex-CddkPoXu.js +0 -257
  112. package/web-dist/assets/line-DTfkLWnw.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-CHzoJ5LT.js +0 -7
  116. package/web-dist/assets/mermaid.core-Cl84DLN2.js +0 -11
  117. package/web-dist/assets/mindmap-definition-LN4V7U3C-C39sBNqB.js +0 -96
  118. package/web-dist/assets/ordinal-hYBb2elL.js +0 -1
  119. package/web-dist/assets/packet-7NZHBO7P-Bj73rlQn.js +0 -1
  120. package/web-dist/assets/path-BWPyau1x.js +0 -1
  121. package/web-dist/assets/pegDiagram-2B236MQR-BEEM1vSv.js +0 -1
  122. package/web-dist/assets/pie-RZYD4A2V-CJ-fEfdy.js +0 -1
  123. package/web-dist/assets/pieDiagram-ENE6RG2P-Dcqj3oyh.js +0 -39
  124. package/web-dist/assets/quadrantDiagram-ABIIQ3AL-D5cljQ3D.js +0 -7
  125. package/web-dist/assets/radar-I7S5WNFK-G8JqqGPf.js +0 -1
  126. package/web-dist/assets/railroad-3IZDKUUU-CaGNL0iN.js +0 -1
  127. package/web-dist/assets/railroad-abnf-AHOZXSZD-DWZmdy-r.js +0 -1
  128. package/web-dist/assets/railroad-ebnf-EBAXGLYW-C-XoPuo_.js +0 -1
  129. package/web-dist/assets/railroad-peg-LSFZ7HO6-BGpxqg46.js +0 -1
  130. package/web-dist/assets/railroadDiagram-RFXS5EU6-BZGZXMY4.js +0 -1
  131. package/web-dist/assets/requirementDiagram-TGXJPOKE-Dj5yn_9e.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-7iJ_NDNf.js +0 -40
  135. package/web-dist/assets/sequenceDiagram-DBY2YBRQ-Bl54CknA.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-DuvjeNgJ.js +0 -1
  139. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BR-vrMgH.js +0 -1
  140. package/web-dist/assets/swimlanes-5IMT3BWC-B7-Jj4Sk.js +0 -2
  141. package/web-dist/assets/swimlanesDiagram-G3AALYLV-C1HvMf2k.js +0 -8
  142. package/web-dist/assets/timeline-definition-FHXFAJF6-jsey2pEG.js +0 -120
  143. package/web-dist/assets/treeView-QDETBFTQ-DhPmOzHr.js +0 -1
  144. package/web-dist/assets/treemap-6X3UGDF4-Dw2IaXqK.js +0 -1
  145. package/web-dist/assets/vennDiagram-L72KCM5P-CXKZkBab.js +0 -34
  146. package/web-dist/assets/wardley-OPB4EBWU-CTCdDwyE.js +0 -1
  147. package/web-dist/assets/wardleyDiagram-EHGQE667-B1w0xIqB.js +0 -78
  148. package/web-dist/assets/xychartDiagram-FW5EYKEG-gULk-uIl.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;
@@ -11775,7 +11775,7 @@ var init_interactions = __esm({
11775
11775
  InteractionFieldId = external_exports.string().min(1).max(128).regex(/^[A-Za-z_][A-Za-z0-9_.:-]*$/).refine((value) => !["__proto__", "constructor", "prototype"].includes(value), "reserved field id");
11776
11776
  InteractionDecisionId = external_exports.string().min(1).max(80).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
11777
11777
  InteractionProvider = external_exports.object({
11778
- kind: external_exports.enum(["claude", "codex", "frizz"]),
11778
+ kind: external_exports.enum(["claude", "codex", "acp", "frizz"]),
11779
11779
  name: InteractionLabel.optional(),
11780
11780
  version: InteractionOpaqueId.optional()
11781
11781
  }).strict();
@@ -12466,10 +12466,15 @@ var init_shell_writes = __esm({
12466
12466
  });
12467
12467
 
12468
12468
  // packages/shared/src/index.ts
12469
+ function requireEffortOutsideAcp(profile, ctx) {
12470
+ if (profile.backend !== "acp" && profile.effort === void 0) {
12471
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["effort"], message: `effort is required for a ${profile.backend} profile` });
12472
+ }
12473
+ }
12469
12474
  function fallbackPort(base) {
12470
12475
  return base + 1e4;
12471
12476
  }
12472
- var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, AskQuestionKind, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountLogoutInput, AccountLogoutResult, AccountLoginStartInput, AccountLoginStartResult, AccountLoginStatusInput, AccountLoginStatusResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, HUMAN_GAP_FLOOR_MS, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT;
12477
+ var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, AcpAgent, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, AskQuestionKind, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, AccountLoginStartInput, AccountLoginStartResult, AccountLoginStatusInput, AccountLoginStatusResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, HUMAN_GAP_FLOOR_MS, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT;
12473
12478
  var init_src = __esm({
12474
12479
  "packages/shared/src/index.ts"() {
12475
12480
  "use strict";
@@ -12588,7 +12593,7 @@ var init_src = __esm({
12588
12593
  "exited"
12589
12594
  // the worker process that owned this session is gone, or is no longer driving its turn
12590
12595
  ]);
12591
- Backend = external_exports.enum(["claude", "codex"]);
12596
+ Backend = external_exports.enum(["claude", "codex", "acp"]);
12592
12597
  CodexModel = external_exports.object({
12593
12598
  slug: external_exports.string(),
12594
12599
  displayName: external_exports.string(),
@@ -12602,6 +12607,23 @@ var init_src = __esm({
12602
12607
  contextWindow: external_exports.number().int().positive().optional(),
12603
12608
  maxContextWindow: external_exports.number().int().positive().optional()
12604
12609
  });
12610
+ AcpAgent = external_exports.object({
12611
+ id: external_exports.string(),
12612
+ label: external_exports.string(),
12613
+ command: external_exports.string(),
12614
+ available: external_exports.boolean()
12615
+ });
12616
+ AcpAgentModel = external_exports.object({ id: external_exports.string(), name: external_exports.string() });
12617
+ AcpAgentModels = external_exports.object({
12618
+ agentId: external_exports.string(),
12619
+ models: external_exports.array(AcpAgentModel),
12620
+ /** The model the agent opens a session on when Frizz asks for none. */
12621
+ current: external_exports.string().optional(),
12622
+ /** Why the list is empty, when the probe could not open a session (not installed, not logged in…). */
12623
+ error: external_exports.string().optional(),
12624
+ probedAt: external_exports.string()
12625
+ });
12626
+ AcpAgentModelsInput = external_exports.object({ agentId: external_exports.string().min(1).max(100), refresh: external_exports.boolean().optional() }).strict();
12605
12627
  ThreadProfileOption = external_exports.object({
12606
12628
  model: external_exports.string().min(1),
12607
12629
  label: external_exports.string().min(1),
@@ -12779,7 +12801,7 @@ var init_src = __esm({
12779
12801
  questions: external_exports.array(AskQuestion)
12780
12802
  });
12781
12803
  AwaitingHint = external_exports.object({
12782
- kind: external_exports.enum(["shell", "agent", "timer", "pr", "for", "title"]),
12804
+ kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "for", "title"]),
12783
12805
  value: external_exports.string()
12784
12806
  });
12785
12807
  RETIRED_AWAITING_KINDS = ["watch", "pr-watch", "human", "ci", "session", "shell", "agent", "timer", "pr", "reason"];
@@ -12929,6 +12951,20 @@ var init_src = __esm({
12929
12951
  * must hear about. Empty when nothing is failing. */
12930
12952
  failureSig: external_exports.string().optional()
12931
12953
  }).strict();
12954
+ GithubIssueStatus = external_exports.object({
12955
+ /** OPEN | CLOSED, lowercased. A closed issue ends the wait outright. */
12956
+ state: external_exports.enum(["open", "closed"]),
12957
+ /** GitHub's own reason for a close — `completed`, `not_planned`, `duplicate` — lowercased, when it
12958
+ * gave one. Absent while open. */
12959
+ stateReason: external_exports.string().optional(),
12960
+ /** The issue's title, as of the last poll — the one fact a human needs to place a bare `owner/repo#N`
12961
+ * on the card. Capped, because a title is a headline and the card is not the place to read one. */
12962
+ title: external_exports.string().max(200).optional(),
12963
+ /** The conversation's size, so the card can say "14 comments" the way GitHub's list does. */
12964
+ comments: external_exports.number().int().nonnegative(),
12965
+ /** When frizz last heard from GitHub, for the same reason `GithubWatchStatus.polledAt` carries it. */
12966
+ polledAt: external_exports.string()
12967
+ }).strict();
12932
12968
  ThreadWatchView = external_exports.object({
12933
12969
  id: external_exports.string(),
12934
12970
  kind: ThreadWatchKind,
@@ -12937,6 +12973,15 @@ var init_src = __esm({
12937
12973
  createdAt: external_exports.string(),
12938
12974
  /** `github` rows only, and absent until the first successful poll. */
12939
12975
  github: GithubWatchStatus.optional(),
12976
+ /** `github` rows only: WHAT the row watches. A pull request and an issue share the kind — they are one
12977
+ * registry, one ref grammar (`owner/repo#N`) and one place on every surface that lists waits — and
12978
+ * differ in what the poll can read off them, which is what this field lets a card branch on.
12979
+ * Optional, and absent means a pull request — a row from a server that predates issues reads as the
12980
+ * PR it always was, and the non-github kinds never carry it. */
12981
+ subject: external_exports.enum(["pull", "issue"]).optional(),
12982
+ /** `github` rows watching an ISSUE only, and absent until the first successful poll — the twin of the
12983
+ * `github` half above, for the rows whose subject has no checks to report. */
12984
+ issue: GithubIssueStatus.optional(),
12940
12985
  /** `timer` rows only: the armed timer's own registration, which is everything the row renders — the
12941
12986
  * worker's prompt is the row's NAME (a `tmr_…` id names nothing to a human) and the fire instant is
12942
12987
  * its status. Unlike `github` there is no polled half to be absent: a timer that exists is fully
@@ -12949,10 +12994,15 @@ var init_src = __esm({
12949
12994
  id: external_exports.string(),
12950
12995
  /** `owner/repo#N`, normalized — the same string the board's row and the status book are keyed by. */
12951
12996
  target: external_exports.string(),
12997
+ /** A pull request or an issue. Both live in one registry (`pr_watch`) and answer to one drop verb;
12998
+ * `watch_pr` and `watch_issue` each list only their own. Defaulted for a row an older server sends. */
12999
+ kind: external_exports.enum(["pull", "issue"]).default("pull"),
12952
13000
  state: external_exports.enum(["armed", "dropped", "settled"]),
12953
13001
  createdAt: external_exports.string(),
12954
13002
  /** The PR's checks/mergeability as the poller last saw it. Absent until the first successful poll. */
12955
- github: GithubWatchStatus.optional()
13003
+ github: GithubWatchStatus.optional(),
13004
+ /** An ISSUE's state as the poller last saw it — `kind: "issue"` rows only, absent until polled. */
13005
+ issue: GithubIssueStatus.optional()
12956
13006
  }).strict();
12957
13007
  AddOwnPrWatchInput = external_exports.object({
12958
13008
  slug: ThreadSlug,
@@ -12977,7 +13027,13 @@ var init_src = __esm({
12977
13027
  * already running, with no recourse from inside those threads. Absent ⇒ PR_WATCH_DEFAULT_FOR, which
12978
13028
  * still BOUNDS the poll; the point of the field is that a worker chooses, and one that cannot choose
12979
13029
  * is better bounded than broken. */
12980
- for: external_exports.string().trim().min(1).max(16).optional()
13030
+ for: external_exports.string().trim().min(1).max(16).optional(),
13031
+ /** What `target` names. `issue` (from `mcp__frizz__watch_issue`, 2026-09-14) registers a watcher on a
13032
+ * GitHub issue — same registry, same poll cadence, same drop verb, but a different probe (`gh issue
13033
+ * view`), a different GraphQL fragment and a report with no CI in it. ABSENT MEANS A PULL REQUEST,
13034
+ * for the same reason `for` is optional on the wire: every `watch_pr` binary dispatched before this
13035
+ * field existed sends nothing here and must go on meaning what it always did. */
13036
+ kind: external_exports.enum(["pull", "issue"]).optional()
12981
13037
  }).strict();
12982
13038
  AddOwnPrWatchResult = external_exports.object({
12983
13039
  id: external_exports.string(),
@@ -13525,14 +13581,15 @@ var init_src = __esm({
13525
13581
  codex: ProviderAuth,
13526
13582
  emails: AccountEmails
13527
13583
  });
13528
- AccountLogoutInput = external_exports.object({ backend: external_exports.enum(["claude", "codex"]) }).strict();
13584
+ AccountBackend = external_exports.enum(["claude", "codex"]);
13585
+ AccountLogoutInput = external_exports.object({ backend: AccountBackend }).strict();
13529
13586
  AccountLogoutResult = external_exports.object({
13530
13587
  status: external_exports.enum(["done", "blocked", "failed"]),
13531
13588
  auth: ProviderAuth,
13532
13589
  activeThreads: external_exports.number().int().positive().optional(),
13533
13590
  detail: external_exports.string().max(200).optional()
13534
13591
  });
13535
- AccountLoginStartInput = external_exports.object({ backend: external_exports.enum(["claude", "codex"]) }).strict();
13592
+ AccountLoginStartInput = external_exports.object({ backend: AccountBackend }).strict();
13536
13593
  AccountLoginStartResult = external_exports.object({ attemptId: ThreadSlug });
13537
13594
  AccountLoginStatusInput = external_exports.object({ attemptId: ThreadSlug }).strict();
13538
13595
  AccountLoginStatusResult = external_exports.object({
@@ -13607,6 +13664,15 @@ var init_src = __esm({
13607
13664
  // it (codex compacts at 90% of the resolved window). Unset ⇒ nothing is sent and the model's stock
13608
13665
  // window applies. Optional so an old blob parses; defaultSettings leaves it unset.
13609
13666
  codexContextWindow: external_exports.number().int().positive().optional(),
13667
+ // The operator's OWN Agent Client Protocol agents, merged over the built-in catalogue by id
13668
+ // (server/backend/acp-agents.ts). `command` is an executable name on PATH or an absolute path;
13669
+ // `args` is what puts it into ACP mode (`["acp"]`, `["--acp"]`). Optional so an old blob parses.
13670
+ acpAgents: external_exports.array(external_exports.object({
13671
+ id: external_exports.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9._-]*$/i),
13672
+ label: external_exports.string().trim().min(1).max(80),
13673
+ command: external_exports.string().trim().min(1).max(1024),
13674
+ args: external_exports.array(external_exports.string().max(1024)).max(32).optional()
13675
+ })).max(32).optional(),
13610
13676
  // The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
13611
13677
  // unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
13612
13678
  // the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
@@ -13633,28 +13699,38 @@ var init_src = __esm({
13633
13699
  DispatchPreferences = external_exports.object({
13634
13700
  backend: Backend,
13635
13701
  claude: DispatchProviderPreferences,
13636
- codex: DispatchProviderPreferences
13702
+ codex: DispatchProviderPreferences,
13703
+ // Optional because every record written before the ACP backend existed lacks it, and a required
13704
+ // key would fail those records' parse and silently reset the operator's saved profile.
13705
+ acp: DispatchProviderPreferences.optional()
13637
13706
  });
13638
- DispatchProfileSnapshot = external_exports.object({
13707
+ DispatchProfileSnapshotShape = external_exports.object({
13639
13708
  backend: Backend,
13640
13709
  model: external_exports.string().trim().min(1).max(200),
13641
- effort: Settings.shape.effort.unwrap(),
13710
+ // Required for Claude and Codex (the refinement below); absent for an ACP agent, which runs with
13711
+ // whatever model and effort its own CLI is configured for — Frizz has no effort axis to offer there.
13712
+ effort: Settings.shape.effort,
13642
13713
  // IGNORED: dispatch permission is decided server-side (workerDispatchPermission) from the
13643
13714
  // non-interactive floor plus the operator's Settings choice, never per dispatch. Optional so old
13644
13715
  // clients that still send it parse.
13645
13716
  permissionMode: PermissionMode.optional()
13646
13717
  }).strict();
13718
+ DispatchProfileSnapshot = DispatchProfileSnapshotShape.superRefine(requireEffortOutsideAcp);
13647
13719
  SetDispatchPreferenceInput = external_exports.discriminatedUnion("field", [
13648
13720
  external_exports.object({ field: external_exports.literal("backend"), value: Backend }),
13649
13721
  external_exports.object({
13650
13722
  field: external_exports.literal("profile"),
13651
13723
  backend: Backend,
13652
13724
  model: external_exports.string().trim().min(1).max(200),
13653
- effort: Settings.shape.effort.unwrap()
13725
+ // Optional ONLY for an ACP profile (see DispatchProfileSnapshot); a Claude/Codex profile without
13726
+ // an effort is refused below rather than silently stored as "default".
13727
+ effort: Settings.shape.effort
13654
13728
  }),
13655
13729
  external_exports.object({ field: external_exports.literal("model"), backend: Backend, value: external_exports.string().trim().min(1).max(200) }),
13656
13730
  external_exports.object({ field: external_exports.literal("effort"), backend: Backend, value: Settings.shape.effort.unwrap() })
13657
- ]);
13731
+ ]).superRefine((update, ctx) => {
13732
+ if (update.field === "profile") requireEffortOutsideAcp(update, ctx);
13733
+ });
13658
13734
  DispatchInput = external_exports.object({
13659
13735
  // Optional: when omitted, dispatch derives a fallback title from the prompt (Claude later renames
13660
13736
  // the session via ai-title, which the UI prefers for display). The thread FILE always gets a
@@ -13806,7 +13882,7 @@ var init_src = __esm({
13806
13882
  timers: external_exports.array(ThreadTimerView)
13807
13883
  }).strict();
13808
13884
  ThreadActivityItem = external_exports.object({
13809
- kind: external_exports.enum(["shell", "agent", "timer", "pr"]),
13885
+ kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue"]),
13810
13886
  /** The string a `<kind>:` fence line must carry. For a shell that is the runtime task id the worker
13811
13887
  * was shown; for a PR, `owner/repo#N`; for a timer, its `tmr_…` row id. */
13812
13888
  id: external_exports.string(),
@@ -14044,9 +14120,9 @@ ${DISPATCH_TASK_BANNER}
14044
14120
  page: external_exports.number().int().positive(),
14045
14121
  pageCount: external_exports.number().int().positive()
14046
14122
  });
14047
- GithubBatchInput = DispatchProfileSnapshot.extend({
14123
+ GithubBatchInput = DispatchProfileSnapshotShape.extend({
14048
14124
  items: external_exports.array(external_exports.object({ kind: external_exports.enum(["issue", "pr"]), number: external_exports.number().int().positive() })).min(1)
14049
- }).strict();
14125
+ }).strict().superRefine(requireEffortOutsideAcp);
14050
14126
  GithubBatchResult = external_exports.object({
14051
14127
  dispatched: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), slug: ThreadSlug })),
14052
14128
  failed: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), error: external_exports.string() }))
@@ -16829,7 +16905,7 @@ var require_websocket = __commonJS({
16829
16905
  var http = __require("http");
16830
16906
  var net = __require("net");
16831
16907
  var tls = __require("tls");
16832
- var { randomBytes: randomBytes2, createHash: createHash4 } = __require("crypto");
16908
+ var { randomBytes: randomBytes2, createHash: createHash5 } = __require("crypto");
16833
16909
  var { Duplex, Readable } = __require("stream");
16834
16910
  var { URL: URL2 } = __require("url");
16835
16911
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -17497,7 +17573,7 @@ var require_websocket = __commonJS({
17497
17573
  abortHandshake(websocket, socket, "Invalid Upgrade header");
17498
17574
  return;
17499
17575
  }
17500
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
17576
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
17501
17577
  if (res.headers["sec-websocket-accept"] !== digest) {
17502
17578
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
17503
17579
  return;
@@ -17866,7 +17942,7 @@ var require_websocket_server = __commonJS({
17866
17942
  var EventEmitter = __require("events");
17867
17943
  var http = __require("http");
17868
17944
  var { Duplex } = __require("stream");
17869
- var { createHash: createHash4 } = __require("crypto");
17945
+ var { createHash: createHash5 } = __require("crypto");
17870
17946
  var extension2 = require_extension();
17871
17947
  var PerMessageDeflate2 = require_permessage_deflate();
17872
17948
  var subprotocol2 = require_subprotocol();
@@ -18173,7 +18249,7 @@ var require_websocket_server = __commonJS({
18173
18249
  );
18174
18250
  }
18175
18251
  if (this._state > RUNNING) return abortHandshake(socket, 503);
18176
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
18252
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
18177
18253
  const headers = [
18178
18254
  "HTTP/1.1 101 Switching Protocols",
18179
18255
  "Upgrade: websocket",
@@ -18341,11 +18417,11 @@ function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Dat
18341
18417
  }
18342
18418
  }
18343
18419
  }
18344
- function formatDiskLine(record) {
18345
- const at = new Date(record.at);
18420
+ function formatDiskLine(record2) {
18421
+ const at = new Date(record2.at);
18346
18422
  const pad = (value, width = 2) => String(value).padStart(width, "0");
18347
18423
  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}
18424
+ return `${clock} ${record2.level.toUpperCase().padEnd(5)} ${record2.scope.padEnd(12)} ${record2.message}
18349
18425
  `;
18350
18426
  }
18351
18427
  function openLogFile(path) {
@@ -18372,10 +18448,10 @@ function createLogger(options2 = {}) {
18372
18448
  const listeners = /* @__PURE__ */ new Set();
18373
18449
  const emit = (recordLevel, scope, message) => {
18374
18450
  if (LEVEL_ORDER[recordLevel] < threshold) return;
18375
- const record = { at: now(), level: recordLevel, scope, message };
18451
+ const record2 = { at: now(), level: recordLevel, scope, message };
18376
18452
  if (fd !== null) {
18377
18453
  try {
18378
- const line = formatDiskLine(record);
18454
+ const line = formatDiskLine(record2);
18379
18455
  if (written + line.length > maxBytes) {
18380
18456
  writeSync(fd, `--- log truncated at ${maxBytes} bytes ---
18381
18457
  `);
@@ -18390,7 +18466,7 @@ function createLogger(options2 = {}) {
18390
18466
  }
18391
18467
  for (const listener of listeners) {
18392
18468
  try {
18393
- listener(record);
18469
+ listener(record2);
18394
18470
  } catch {
18395
18471
  }
18396
18472
  }
@@ -18417,13 +18493,13 @@ function setAmbientLogger(logger2) {
18417
18493
  ambient = logger2;
18418
18494
  return logger2;
18419
18495
  }
18420
- function formatFeedLine(record) {
18421
- return formatDiskLine(record).trimEnd();
18496
+ function formatFeedLine(record2) {
18497
+ return formatDiskLine(record2).trimEnd();
18422
18498
  }
18423
18499
  function attachTerminalMirror(logger2, enabled = process.env.FRIZZ_DEBUG === "1") {
18424
18500
  if (!enabled) return () => {
18425
18501
  };
18426
- return logger2.onRecord((record) => process.stderr.write(`${formatFeedLine(record)}
18502
+ return logger2.onRecord((record2) => process.stderr.write(`${formatFeedLine(record2)}
18427
18503
  `));
18428
18504
  }
18429
18505
  function ambientLogger() {
@@ -19120,13 +19196,13 @@ function acquireMutationGuard(stateDir, timeoutMs = GUARD_TIMEOUT_MS, adapter =
19120
19196
  let fd;
19121
19197
  try {
19122
19198
  fd = openSync3(path, "wx", 384);
19123
- const record = {
19199
+ const record2 = {
19124
19200
  version: 1,
19125
19201
  token,
19126
19202
  at: new Date(adapter.now()).toISOString(),
19127
19203
  ...generation
19128
19204
  };
19129
- writeFileSync6(fd, `${JSON.stringify(record)}
19205
+ writeFileSync6(fd, `${JSON.stringify(record2)}
19130
19206
  `, "utf8");
19131
19207
  fsyncSync2(fd);
19132
19208
  closeSync2(fd);
@@ -19170,13 +19246,13 @@ function parseDelegates(value) {
19170
19246
  const seen = /* @__PURE__ */ new Set();
19171
19247
  for (const candidate of value) {
19172
19248
  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;
19249
+ const record2 = candidate;
19250
+ const generation = parseGeneration(record2);
19251
+ if (!generation || !validDelegateRole(record2.role) || !validText(record2.registeredAt, 128)) return null;
19176
19252
  const key = `${generation.pid}:${generation.processStart}`;
19177
19253
  if (seen.has(key)) return null;
19178
19254
  seen.add(key);
19179
- delegates.push({ ...generation, role: record.role, registeredAt: record.registeredAt });
19255
+ delegates.push({ ...generation, role: record2.role, registeredAt: record2.registeredAt });
19180
19256
  }
19181
19257
  return delegates;
19182
19258
  }
@@ -19210,11 +19286,11 @@ function projectLaunchOwnerPath(stateDir) {
19210
19286
  function readProjectLaunchOwner(stateDir) {
19211
19287
  return parseOwner(projectLaunchOwnerPath(stateDir));
19212
19288
  }
19213
- function sameTarget(record, target2) {
19214
- return record.projectId === target2.projectId && record.projectDir === target2.projectDir;
19289
+ function sameTarget(record2, target2) {
19290
+ return record2.projectId === target2.projectId && record2.projectDir === target2.projectDir;
19215
19291
  }
19216
- function sameProjectIdentity(record, target2) {
19217
- return record.projectId === target2.projectId;
19292
+ function sameProjectIdentity(record2, target2) {
19293
+ return record2.projectId === target2.projectId;
19218
19294
  }
19219
19295
  function projectLaunchTokenProof(target2, token) {
19220
19296
  if (!UUID_RE.test(token)) throw new Error("invalid Frizz project launch owner token");
@@ -19230,11 +19306,11 @@ function removeStatusesForToken(stateDir, token) {
19230
19306
  }
19231
19307
  }
19232
19308
  }
19233
- function writeNewOwner(path, record) {
19309
+ function writeNewOwner(path, record2) {
19234
19310
  let fd;
19235
19311
  try {
19236
19312
  fd = openSync3(path, "wx", 384);
19237
- writeFileSync6(fd, `${JSON.stringify(record)}
19313
+ writeFileSync6(fd, `${JSON.stringify(record2)}
19238
19314
  `, "utf8");
19239
19315
  fsyncSync2(fd);
19240
19316
  closeSync2(fd);
@@ -19250,21 +19326,21 @@ function writeNewOwner(path, record) {
19250
19326
  throw error;
19251
19327
  }
19252
19328
  }
19253
- function makeLease(target2, record, adapter) {
19329
+ function makeLease(target2, record2, adapter) {
19254
19330
  let released = false;
19255
19331
  return {
19256
19332
  target: target2,
19257
- token: record.token,
19258
- pid: record.pid,
19259
- processStart: record.processStart,
19260
- role: record.role,
19333
+ token: record2.token,
19334
+ pid: record2.pid,
19335
+ processStart: record2.processStart,
19336
+ role: record2.role,
19261
19337
  release: () => {
19262
19338
  if (released) return false;
19263
19339
  const unlock = acquireMutationGuard(target2.stateDir, GUARD_TIMEOUT_MS, adapter);
19264
19340
  try {
19265
19341
  const path = projectLaunchOwnerPath(target2.stateDir);
19266
19342
  const current = parseOwner(path);
19267
- if (!current || !sameTarget(current, target2) || current.token !== record.token || current.pid !== record.pid || current.processStart !== record.processStart) {
19343
+ if (!current || !sameTarget(current, target2) || current.token !== record2.token || current.pid !== record2.pid || current.processStart !== record2.processStart) {
19268
19344
  released = true;
19269
19345
  return false;
19270
19346
  }
@@ -19299,15 +19375,15 @@ function newOwnerRecord(target2, role, adapter) {
19299
19375
  }
19300
19376
  function installNewOwner(target2, role, adapter) {
19301
19377
  const path = projectLaunchOwnerPath(target2.stateDir);
19302
- const record = newOwnerRecord(target2, role, adapter);
19378
+ const record2 = newOwnerRecord(target2, role, adapter);
19303
19379
  try {
19304
- writeNewOwner(path, record);
19380
+ writeNewOwner(path, record2);
19305
19381
  } catch (error) {
19306
19382
  if (errorCode3(error) === "EEXIST") return { kind: "contended", owner: parseOwner(path) };
19307
19383
  throw error;
19308
19384
  }
19309
19385
  const committed = parseOwner(path);
19310
- if (!committed || committed.token !== record.token) return { kind: "contended", owner: committed };
19386
+ if (!committed || committed.token !== record2.token) return { kind: "contended", owner: committed };
19311
19387
  return { kind: "acquired", lease: makeLease(target2, committed, adapter) };
19312
19388
  }
19313
19389
  function drainDelegates(delegates, options2) {
@@ -19486,11 +19562,13 @@ function readBootProgress(stateDir) {
19486
19562
  return null;
19487
19563
  }
19488
19564
  }
19489
- var BOOT_PROGRESS_NAME;
19565
+ var BOOT_PROGRESS_NAME, BOOT_STALL_TIMEOUT_MS, BOOT_HARD_TIMEOUT_MS;
19490
19566
  var init_boot_progress = __esm({
19491
19567
  "packages/server/src/boot-progress.ts"() {
19492
19568
  "use strict";
19493
19569
  BOOT_PROGRESS_NAME = "boot.progress";
19570
+ BOOT_STALL_TIMEOUT_MS = 3e4;
19571
+ BOOT_HARD_TIMEOUT_MS = 10 * 6e4;
19494
19572
  }
19495
19573
  });
19496
19574
 
@@ -19828,12 +19906,19 @@ var init_project_registry = __esm({
19828
19906
  }
19829
19907
  });
19830
19908
 
19909
+ // packages/server/src/native-helper.ts
19910
+ var init_native_helper = __esm({
19911
+ "packages/server/src/native-helper.ts"() {
19912
+ "use strict";
19913
+ }
19914
+ });
19915
+
19831
19916
  // src/production.ts
19832
19917
  init_local_origin();
19833
- import { readFileSync as readFileSync13 } from "node:fs";
19918
+ import { readFileSync as readFileSync15 } from "node:fs";
19834
19919
  import { homedir as homedir11 } from "node:os";
19835
19920
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19836
- import { join as join16 } from "node:path";
19921
+ import { join as join17 } from "node:path";
19837
19922
 
19838
19923
  // src/browser.ts
19839
19924
  import { spawn, execFile } from "node:child_process";
@@ -19995,12 +20080,12 @@ function manifestIdFor(url) {
19995
20080
  return url.endsWith("/") ? url : `${url}/`;
19996
20081
  }
19997
20082
  function execFileP(cmd, args2) {
19998
- return new Promise((resolve6, reject) => {
20083
+ return new Promise((resolve7, reject) => {
19999
20084
  execFile(
20000
20085
  cmd,
20001
20086
  args2,
20002
20087
  { encoding: "utf8", timeout: 1e4 },
20003
- (err, stdout) => err ? reject(err) : resolve6(stdout)
20088
+ (err, stdout) => err ? reject(err) : resolve7(stdout)
20004
20089
  );
20005
20090
  });
20006
20091
  }
@@ -20079,9 +20164,9 @@ function openCdpSession(browserPath, dataPath) {
20079
20164
  cmdPipe.on("error", failAll);
20080
20165
  resPipe.on("error", failAll);
20081
20166
  child.on("exit", () => failAll(new Error("chrome exited before the CDP handshake completed")));
20082
- const call2 = (method, params = {}, timeoutMs = 2e4) => new Promise((resolve6, reject) => {
20167
+ const call2 = (method, params = {}, timeoutMs = 2e4) => new Promise((resolve7, reject) => {
20083
20168
  const id = ++seq;
20084
- pending.set(id, { resolve: resolve6, reject });
20169
+ pending.set(id, { resolve: resolve7, reject });
20085
20170
  cmdPipe.write(JSON.stringify({ id, method, params }) + "\0");
20086
20171
  setTimeout(() => {
20087
20172
  if (pending.has(id)) {
@@ -20094,15 +20179,15 @@ function openCdpSession(browserPath, dataPath) {
20094
20179
  await call2("Browser.close").catch(() => {
20095
20180
  });
20096
20181
  if (child.exitCode !== null || child.signalCode !== null) return;
20097
- await new Promise((resolve6) => {
20182
+ await new Promise((resolve7) => {
20098
20183
  const t = setTimeout(() => {
20099
20184
  child.kill();
20100
- resolve6();
20185
+ resolve7();
20101
20186
  }, 1e4);
20102
20187
  t.unref();
20103
20188
  child.once("exit", () => {
20104
20189
  clearTimeout(t);
20105
- resolve6();
20190
+ resolve7();
20106
20191
  });
20107
20192
  });
20108
20193
  };
@@ -22619,6 +22704,7 @@ var RestartSupervisorProxy = class {
22619
22704
  status() {
22620
22705
  const delegated = this.options.status?.();
22621
22706
  const updateVersion = this.options.updateVersion?.();
22707
+ const version = typeof this.options.version === "function" ? this.options.version() : this.options.version;
22622
22708
  const owner = { state: this.state, ...this.message ? { message: this.message } : {} };
22623
22709
  const reported = this.transition === "update" && delegated ? { ...delegated, ...delegated.state === "ready" ? { preparing: true } : {} } : this.restartInFlight !== null || this.state === "failed" ? owner : delegated ?? owner;
22624
22710
  return {
@@ -22630,9 +22716,10 @@ var RestartSupervisorProxy = class {
22630
22716
  // Sent ONLY when the launcher can actually answer it, so an older client — and frizz-dev, which
22631
22717
  // has no notion of "already current" — keeps today's behaviour on its absence.
22632
22718
  ...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 } : {},
22719
+ // The active SERVER version changes on a child-only update. Its absence keeps frizz-dev and
22720
+ // old monolithic supervisors' status payload and versionless UI exactly as they were.
22721
+ ...version ? { version } : {},
22722
+ ...this.options.launcherVersion ? { launcherVersion: this.options.launcherVersion } : {},
22636
22723
  ...updateVersion ? { updateVersion } : {},
22637
22724
  // Sent only when TRUE, so a published Frizz's payload is byte-identical to what it sends today
22638
22725
  // and an older client is unaffected. Absent therefore means "not a development build".
@@ -23288,7 +23375,7 @@ function serveRelayRequest(frame, options2) {
23288
23375
  const publicUrl = new URL(options2.publicOrigin);
23289
23376
  let cancelled = false;
23290
23377
  let abort;
23291
- const done = new Promise((resolve6) => {
23378
+ const done = new Promise((resolve7) => {
23292
23379
  const send = (upFrame) => {
23293
23380
  if (!cancelled) options2.send(upFrame);
23294
23381
  };
@@ -23322,11 +23409,11 @@ function serveRelayRequest(frame, options2) {
23322
23409
  });
23323
23410
  res.on("end", () => {
23324
23411
  send({ t: "res-end", id: frame.id });
23325
- resolve6();
23412
+ resolve7();
23326
23413
  });
23327
23414
  res.on("error", () => {
23328
23415
  send({ t: "res-end", id: frame.id });
23329
- resolve6();
23416
+ resolve7();
23330
23417
  });
23331
23418
  return;
23332
23419
  }
@@ -23343,7 +23430,7 @@ function serveRelayRequest(frame, options2) {
23343
23430
  ...body.byteLength > 0 ? { body: encodeBody(new Uint8Array(body)) } : {},
23344
23431
  end: true
23345
23432
  });
23346
- resolve6();
23433
+ resolve7();
23347
23434
  return;
23348
23435
  }
23349
23436
  send({ t: "res", id: frame.id, status: res.statusCode ?? 502, headers: outHeaders, end: false });
@@ -23355,11 +23442,11 @@ function serveRelayRequest(frame, options2) {
23355
23442
  });
23356
23443
  }
23357
23444
  send({ t: "res-end", id: frame.id });
23358
- resolve6();
23445
+ resolve7();
23359
23446
  });
23360
23447
  res.on("error", () => {
23361
23448
  send({ t: "res", id: frame.id, status: 502, headers: [], end: true });
23362
- resolve6();
23449
+ resolve7();
23363
23450
  });
23364
23451
  }
23365
23452
  );
@@ -23372,13 +23459,13 @@ function serveRelayRequest(frame, options2) {
23372
23459
  body: encodeBody(new TextEncoder().encode(`Frizz is not answering on this machine: ${error.message}`)),
23373
23460
  end: true
23374
23461
  });
23375
- resolve6();
23462
+ resolve7();
23376
23463
  });
23377
23464
  if (frame.body) req.write(Buffer.from(decodeBody(frame.body)));
23378
23465
  req.end();
23379
23466
  abort = () => {
23380
23467
  req.destroy();
23381
- resolve6();
23468
+ resolve7();
23382
23469
  };
23383
23470
  });
23384
23471
  return {
@@ -23512,9 +23599,9 @@ function connectRelay(options2) {
23512
23599
  };
23513
23600
  const scheduleRetry = (reason) => {
23514
23601
  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);
23602
+ const delay3 = backoff(attempt++);
23603
+ options2.onStatus?.("retrying", `${reason}; retrying in ${Math.round(delay3 / 1e3)}s`);
23604
+ retryHandle = setTimer(() => void open(), delay3);
23518
23605
  };
23519
23606
  const open = async () => {
23520
23607
  if (stopped) return;
@@ -23937,9 +24024,9 @@ function createRemoteController(options2) {
23937
24024
  import { execFile as execFile3 } from "node:child_process";
23938
24025
  var PROBE_TIMEOUT_MS = 4e3;
23939
24026
  function run2(command, args2) {
23940
- return new Promise((resolve6) => {
24027
+ return new Promise((resolve7) => {
23941
24028
  execFile3(command, args2, { encoding: "utf8", timeout: PROBE_TIMEOUT_MS }, (error, stdout) => {
23942
- resolve6(error ? null : stdout);
24029
+ resolve7(error ? null : stdout);
23943
24030
  });
23944
24031
  });
23945
24032
  }
@@ -24643,8 +24730,8 @@ init_logging();
24643
24730
  init_src();
24644
24731
  init_project_registry();
24645
24732
  var PORT_SCAN_COUNT = 100;
24646
- var LAUNCH_TIMEOUT_MS = 3e4;
24647
- var LAUNCH_HARD_TIMEOUT_MS = 10 * 6e4;
24733
+ var LAUNCH_TIMEOUT_MS = BOOT_STALL_TIMEOUT_MS;
24734
+ var LAUNCH_HARD_TIMEOUT_MS = BOOT_HARD_TIMEOUT_MS;
24648
24735
  var RETIRED_NETWORK_FLAGS = ["--host", "--allowed-host", "--public-origin", "--cloud"];
24649
24736
  function retiredFlagMessage(flag) {
24650
24737
  return `${flag} was retired \u2014 how a board is reached is set up from the running board now: press R in its terminal to pick a private or custom frizz.sh name, a Cloudflare Tunnel, Tailscale or a proxy of your own. The choice is remembered, so a plain launch serves it.`;
@@ -25178,11 +25265,14 @@ import { basename as basename6, dirname as dirname11, extname as extname2, isAbs
25178
25265
  import { fileURLToPath } from "node:url";
25179
25266
  import watcher from "@parcel/watcher";
25180
25267
  init_logging();
25268
+ init_boot_progress();
25181
25269
  var DEV_RESTART_DEBOUNCE_MS = 180;
25182
25270
  var DEV_CRASH_STABLE_MS = 5e3;
25183
25271
  var DEV_CRASH_RETRY_BASE_MS = 500;
25184
25272
  var DEV_CRASH_RETRY_MAX_MS = 1e4;
25185
25273
  var CHILD_STOP_TIMEOUT_MS = 15e3;
25274
+ var STABLE_UPDATE_READY_TIMEOUT_MS = BOOT_STALL_TIMEOUT_MS;
25275
+ var STABLE_UPDATE_STABILIZE_MS = 1e3;
25186
25276
  var CHILD_KILL_GRACE_MS = 5e3;
25187
25277
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".mjs", ".cjs", ".json"]);
25188
25278
  var CONFIG_NAMES = /* @__PURE__ */ new Set(["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", ".npmrc"]);
@@ -25389,6 +25479,12 @@ var Supervisor = class {
25389
25479
  debounce = null;
25390
25480
  restartRunning = false;
25391
25481
  restartAgain = false;
25482
+ /** Resolves when a pre-existing watcher restart has released the one child slot. */
25483
+ restartCompletion = null;
25484
+ /** Serializes an update candidate with watcher/browser restart requests. */
25485
+ updateChildRunning = false;
25486
+ /** A candidate never joins crash-retry until its selection has committed. */
25487
+ updateCandidate = null;
25392
25488
  reloadLauncher = false;
25393
25489
  crashAttempts = 0;
25394
25490
  crashStableTimer = null;
@@ -25424,6 +25520,11 @@ var Supervisor = class {
25424
25520
  lastActivity;
25425
25521
  publicProxy;
25426
25522
  updateRestart;
25523
+ updateMode;
25524
+ commitUpdate;
25525
+ updateReadyTimeoutMs;
25526
+ updateHardTimeoutMs;
25527
+ updateStabilizeMs;
25427
25528
  rollbackUpdate;
25428
25529
  durableReexec;
25429
25530
  /** Environment of the generation that actually reached ready, never merely the latest pointer. */
@@ -25464,6 +25565,11 @@ var Supervisor = class {
25464
25565
  this.errorLine = opts.error ?? ((line) => log.error("supervisor", stripPrefix(line)));
25465
25566
  this.onActivity = opts.onActivity;
25466
25567
  this.updateRestart = opts.updateRestart;
25568
+ this.updateMode = opts.updateMode ?? "durableReexec";
25569
+ this.commitUpdate = opts.commitUpdate;
25570
+ this.updateReadyTimeoutMs = Math.max(1, opts.updateReadyTimeoutMs ?? STABLE_UPDATE_READY_TIMEOUT_MS);
25571
+ this.updateHardTimeoutMs = Math.max(1, opts.updateHardTimeoutMs ?? BOOT_HARD_TIMEOUT_MS);
25572
+ this.updateStabilizeMs = Math.max(0, opts.updateStabilizeMs ?? STABLE_UPDATE_STABILIZE_MS);
25467
25573
  this.rollbackUpdate = opts.rollbackUpdate;
25468
25574
  this.durableReexec = opts.durableReexec;
25469
25575
  this.publicProxy = new RestartSupervisorProxy({
@@ -25479,6 +25585,7 @@ var Supervisor = class {
25479
25585
  updateRestart: this.updateRestart ? () => this.updateFromBrowser() : void 0,
25480
25586
  updateAvailable: opts.updateAvailable,
25481
25587
  version: opts.version,
25588
+ launcherVersion: opts.launcherVersion,
25482
25589
  updateVersion: opts.updateVersion,
25483
25590
  dev: opts.dev,
25484
25591
  status: () => {
@@ -25570,6 +25677,10 @@ var Supervisor = class {
25570
25677
  }
25571
25678
  requestRestart(reason, immediate = false, reloadLauncher = false, delayMs = this.debounceMs) {
25572
25679
  if (this.closed) return;
25680
+ if (this.updateChildRunning) {
25681
+ this.restartAgain = true;
25682
+ return;
25683
+ }
25573
25684
  this.reloadLauncher ||= reloadLauncher;
25574
25685
  if (this.debounce) clearTimeout(this.debounce);
25575
25686
  const run3 = () => {
@@ -25585,11 +25696,20 @@ var Supervisor = class {
25585
25696
  else this.debounce = setTimeout(run3, delayMs);
25586
25697
  }
25587
25698
  async restart() {
25699
+ if (this.updateChildRunning) {
25700
+ this.restartAgain = true;
25701
+ return;
25702
+ }
25588
25703
  if (this.restartRunning) {
25589
25704
  this.restartAgain = true;
25590
25705
  return;
25591
25706
  }
25592
25707
  this.restartRunning = true;
25708
+ let completeRestart;
25709
+ const completion = new Promise((resolveCompletion) => {
25710
+ completeRestart = resolveCompletion;
25711
+ });
25712
+ this.restartCompletion = completion;
25593
25713
  try {
25594
25714
  let shouldReloadLauncher = false;
25595
25715
  do {
@@ -25597,7 +25717,7 @@ var Supervisor = class {
25597
25717
  shouldReloadLauncher ||= this.reloadLauncher;
25598
25718
  this.reloadLauncher = false;
25599
25719
  await this.stopChild();
25600
- if (!this.closed) {
25720
+ if (!this.closed && !this.updateChildRunning) {
25601
25721
  const ready = await this.spawnChild();
25602
25722
  if (!ready) return;
25603
25723
  }
@@ -25605,6 +25725,8 @@ var Supervisor = class {
25605
25725
  if (shouldReloadLauncher && !this.closed) await this.reexecLauncher();
25606
25726
  } finally {
25607
25727
  this.restartRunning = false;
25728
+ completeRestart();
25729
+ if (this.restartCompletion === completion) this.restartCompletion = null;
25608
25730
  }
25609
25731
  }
25610
25732
  /** Called only by the public supervisor control endpoint, never by ordinary document reloads. */
@@ -25635,6 +25757,7 @@ var Supervisor = class {
25635
25757
  }
25636
25758
  async updateFromBrowser() {
25637
25759
  if (!this.updateRestart) return { state: "failed", message: "Update & Restart is unavailable in this launcher mode" };
25760
+ if (this.updateMode === "child") return this.updateChildFromBrowser();
25638
25761
  this.emitActivity("updating", "preparing the update \u2014 the running Frizz is untouched until it is ready");
25639
25762
  const candidate = await this.updateRestart();
25640
25763
  if (candidate.state !== "ready") {
@@ -25661,6 +25784,115 @@ var Supervisor = class {
25661
25784
  this.handoffDraining = false;
25662
25785
  }
25663
25786
  }
25787
+ /**
25788
+ * Update only the disposable application server while retaining the public listener and launcher.
25789
+ * There is deliberately no blue/green overlap: a Frizz child opens SQLite, tailers and schedulers
25790
+ * before it can report ready, so a candidate is started only after the old child is fully gone.
25791
+ */
25792
+ async updateChildFromBrowser() {
25793
+ if (!this.commitUpdate || !this.rollbackUpdate) {
25794
+ return { state: "failed", message: "child-only Update & Restart requires commit and rollback callbacks" };
25795
+ }
25796
+ this.updateChildRunning = true;
25797
+ try {
25798
+ await this.restartCompletion;
25799
+ this.emitActivity("updating", "preparing the new build \u2014 the running one is untouched until it is ready");
25800
+ let prepared;
25801
+ try {
25802
+ prepared = await this.updateRestart();
25803
+ } catch (error) {
25804
+ return this.failChildUpdate(`the update could not be prepared: ${error instanceof Error ? error.message : error}`, false);
25805
+ }
25806
+ if (prepared.state !== "ready") {
25807
+ return this.failChildUpdate(prepared.message ?? "the update could not be prepared", false);
25808
+ }
25809
+ if (this.closed) return this.failChildUpdate("Frizz supervisor stopped while preparing the update", false);
25810
+ if (prepared.message) this.emitActivity("updating", prepared.message);
25811
+ this.handoffDraining = true;
25812
+ await this.stopChild();
25813
+ if (this.closed) return this.failChildUpdate("Frizz supervisor stopped while starting the update", true);
25814
+ const ready = await this.spawnChild({
25815
+ updateCandidate: true,
25816
+ readinessTimeoutMs: this.updateReadyTimeoutMs,
25817
+ hardTimeoutMs: this.updateHardTimeoutMs
25818
+ });
25819
+ const candidate = this.child;
25820
+ if (!ready || !candidate) {
25821
+ return this.failChildUpdate(this.lastRestartFailure ?? "the update candidate did not become ready", true);
25822
+ }
25823
+ if (!await this.stabilizeUpdateCandidate(candidate)) {
25824
+ return this.failChildUpdate("the update candidate stopped before it became stable", true);
25825
+ }
25826
+ try {
25827
+ await this.commitUpdate();
25828
+ } catch (error) {
25829
+ return this.failChildUpdate(`the update could not be committed: ${error instanceof Error ? error.message : error}`, true);
25830
+ }
25831
+ if (this.closed || this.child !== candidate || candidate.exitCode !== null || candidate.signalCode !== null) {
25832
+ return this.failChildUpdate("the update candidate stopped while committing the update", true);
25833
+ }
25834
+ this.updateCandidate = null;
25835
+ this.lastRestartFailure = void 0;
25836
+ return { state: "ready" };
25837
+ } finally {
25838
+ this.updateChildRunning = false;
25839
+ this.handoffDraining = false;
25840
+ if (this.restartAgain && !this.closed) {
25841
+ this.restartAgain = false;
25842
+ this.requestRestart("changes received while updating", true);
25843
+ }
25844
+ }
25845
+ }
25846
+ /** Wait past a ready event so an immediate post-boot crash cannot be made durable. */
25847
+ async stabilizeUpdateCandidate(candidate) {
25848
+ if (this.child !== candidate || candidate.exitCode !== null || candidate.signalCode !== null) return false;
25849
+ if (this.updateStabilizeMs === 0) return true;
25850
+ return new Promise((resolveStable) => {
25851
+ let settled = false;
25852
+ const finish = (stable) => {
25853
+ if (settled) return;
25854
+ settled = true;
25855
+ clearTimeout(timer);
25856
+ candidate.removeListener("exit", exited);
25857
+ resolveStable(stable);
25858
+ };
25859
+ const exited = () => finish(false);
25860
+ const timer = setTimeout(() => finish(this.child === candidate && !this.closed), this.updateStabilizeMs);
25861
+ timer.unref();
25862
+ candidate.once("exit", exited);
25863
+ });
25864
+ }
25865
+ /** Restore the prepared selection and, if it was drained, put the known-good child back. */
25866
+ async failChildUpdate(message, restoreChild) {
25867
+ const failures = [];
25868
+ const candidate = this.updateCandidate;
25869
+ if (candidate && this.child === candidate) await this.stopChild();
25870
+ this.updateCandidate = null;
25871
+ let rolledBack = false;
25872
+ if (!this.rollbackUpdate) failures.push("rollback callback is unavailable");
25873
+ else {
25874
+ try {
25875
+ await this.rollbackUpdate();
25876
+ rolledBack = true;
25877
+ } catch (error) {
25878
+ failures.push(`rollback failed: ${error instanceof Error ? error.message : error}`);
25879
+ }
25880
+ }
25881
+ if (restoreChild && rolledBack && !this.closed) {
25882
+ try {
25883
+ const restored = await this.spawnChild();
25884
+ if (!restored) failures.push(`control plane recovery failed: ${this.lastRestartFailure ?? "the previous child did not become ready"}`);
25885
+ } catch (error) {
25886
+ failures.push(`control plane recovery failed: ${error instanceof Error ? error.message : error}`);
25887
+ }
25888
+ } else if (restoreChild && !rolledBack) {
25889
+ failures.push("control plane was not restarted because rollback did not restore a known-good selection");
25890
+ }
25891
+ const detail = failures.length > 0 ? `${message}; ${failures.join("; ")}` : message;
25892
+ this.errorLine(`[frizz] ${detail}`);
25893
+ if (!this.closed) this.writeStatus("failed", detail, this.boot);
25894
+ return { state: "failed", message: detail };
25895
+ }
25664
25896
  /**
25665
25897
  * A promoted pointer is not committed until its durable owner has actually been replaced. If
25666
25898
  * that handoff cannot happen, put the previous pointer back before reporting the failure. When
@@ -25709,7 +25941,7 @@ var Supervisor = class {
25709
25941
  await this.publicProxy.close();
25710
25942
  this.writeStatus("restarting", "immutable artifact promoted; re-executing durable supervisor", null);
25711
25943
  }
25712
- async spawnChild() {
25944
+ async spawnChild(options2 = {}) {
25713
25945
  const privatePort = await allocatePrivateDevPort(this.port);
25714
25946
  let launch;
25715
25947
  try {
@@ -25748,16 +25980,53 @@ var Supervisor = class {
25748
25980
  return;
25749
25981
  }
25750
25982
  this.child = child;
25983
+ if (options2.updateCandidate) this.updateCandidate = child;
25751
25984
  this.childPort = privatePort;
25752
25985
  this.boot = null;
25753
25986
  let started = false;
25754
25987
  let ownershipRejected = false;
25755
25988
  let spawnSettled = false;
25989
+ let readinessTimer;
25756
25990
  const settleSpawn = (ready) => {
25757
25991
  if (spawnSettled) return;
25758
25992
  spawnSettled = true;
25993
+ if (readinessTimer) clearTimeout(readinessTimer);
25759
25994
  settled(ready);
25760
25995
  };
25996
+ if (options2.readinessTimeoutMs !== void 0) {
25997
+ const stallMs = options2.readinessTimeoutMs;
25998
+ const hardMs = options2.hardTimeoutMs ?? BOOT_HARD_TIMEOUT_MS;
25999
+ const hardDeadline = Date.now() + hardMs;
26000
+ let stallDeadline = Date.now() + stallMs;
26001
+ let lastStep = -1;
26002
+ let lastPhase;
26003
+ const checkProgress = () => {
26004
+ if (spawnSettled) return;
26005
+ const now = Date.now();
26006
+ const progress = readBootProgress(this.launchTarget.stateDir);
26007
+ if (progress && progress.pid === child.pid && progress.step > lastStep) {
26008
+ lastStep = progress.step;
26009
+ lastPhase = progress.phase;
26010
+ stallDeadline = now + stallMs;
26011
+ }
26012
+ if (now < stallDeadline && now < hardDeadline) {
26013
+ readinessTimer = setTimeout(checkProgress, Math.min(250, stallDeadline - now, hardDeadline - now));
26014
+ readinessTimer.unref();
26015
+ return;
26016
+ }
26017
+ const reason = now >= hardDeadline ? `update candidate did not become ready within ${hardMs}ms` : `update candidate made no boot progress for ${stallMs}ms`;
26018
+ const message = lastPhase ? `${reason}; last boot step: ${lastPhase}` : reason;
26019
+ this.lastRestartFailure = message;
26020
+ this.errorLine(`[frizz] ${message}`);
26021
+ this.writeStatus("failed", message, null);
26022
+ try {
26023
+ child.kill("SIGTERM");
26024
+ } catch {
26025
+ }
26026
+ settleSpawn(false);
26027
+ };
26028
+ checkProgress();
26029
+ }
25761
26030
  child.on("message", (message) => {
25762
26031
  if (stopOwnerMessage(message) && message.token === this.ownerToken) {
25763
26032
  this.resolveStopRequested();
@@ -25799,7 +26068,7 @@ var Supervisor = class {
25799
26068
  this.boot = null;
25800
26069
  this.activeChildEnvironment = {};
25801
26070
  }
25802
- const expected2 = this.closed || this.stopping === child || ownershipRejected;
26071
+ const expected2 = this.closed || this.stopping === child || ownershipRejected || this.updateCandidate === child;
25803
26072
  this.clearCrashStability();
25804
26073
  if (!expected2) {
25805
26074
  const why = signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`;
@@ -26012,15 +26281,11 @@ async function startDevSupervisor(opts) {
26012
26281
  }
26013
26282
 
26014
26283
  // src/production-update.ts
26015
- init_src();
26016
26284
  import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
26017
26285
  import { existsSync as existsSync7 } from "node:fs";
26018
26286
  import { dirname as dirname12, isAbsolute as isAbsolute5, join as join14 } from "node:path";
26019
- import { setTimeout as delay3 } from "node:timers/promises";
26287
+ init_src();
26020
26288
  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
26289
  var PRODUCTION_PRINT_LAUNCHER_FLAG = "--_frizz-print-launcher";
26025
26290
  function resolveNpmInvocation(env = process.env, execPath = process.execPath, exists = existsSync7, platform = process.platform) {
26026
26291
  const nodeDir = dirname12(execPath);
@@ -26040,9 +26305,11 @@ function resolveNpmInvocation(env = process.env, execPath = process.execPath, ex
26040
26305
  }
26041
26306
  function compareReleaseVersions(a, b) {
26042
26307
  const parse2 = (value) => {
26043
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value);
26308
+ 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
26309
  if (!match) return null;
26045
- return { numeric: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease: match[4] };
26310
+ const prerelease = match[4]?.split(".");
26311
+ if (prerelease?.some((part) => /^0\d+$/u.test(part))) return null;
26312
+ return { numeric: [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])], prerelease };
26046
26313
  };
26047
26314
  const left = parse2(a);
26048
26315
  const right = parse2(b);
@@ -26051,26 +26318,22 @@ function compareReleaseVersions(a, b) {
26051
26318
  const delta = left.numeric[index] - right.numeric[index];
26052
26319
  if (delta) return delta < 0 ? -1 : 1;
26053
26320
  }
26054
- if (left.prerelease === right.prerelease) return 0;
26321
+ if (!left.prerelease && !right.prerelease) return 0;
26055
26322
  if (!left.prerelease) return 1;
26056
26323
  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;
26324
+ for (let index = 0; index < Math.max(left.prerelease.length, right.prerelease.length); index++) {
26325
+ const a2 = left.prerelease[index];
26326
+ const b2 = right.prerelease[index];
26327
+ if (a2 === b2) continue;
26328
+ if (a2 === void 0) return -1;
26329
+ if (b2 === void 0) return 1;
26330
+ const aNumeric = /^\d+$/u.test(a2);
26331
+ const bNumeric = /^\d+$/u.test(b2);
26332
+ if (aNumeric && bNumeric) return BigInt(a2) < BigInt(b2) ? -1 : 1;
26333
+ if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
26334
+ return a2 < b2 ? -1 : 1;
26335
+ }
26336
+ return 0;
26074
26337
  }
26075
26338
  async function planRegistryUpdate(packageName, currentVersion, adapter) {
26076
26339
  const latestVersion = (await adapter.latestVersion(packageName)).trim();
@@ -26080,9 +26343,6 @@ async function planRegistryUpdate(packageName, currentVersion, adapter) {
26080
26343
  if (comparison >= 0) return null;
26081
26344
  return { packageName, currentVersion, latestVersion, packageSpec: `${packageName}@${latestVersion}` };
26082
26345
  }
26083
- function successorArgs(port) {
26084
- return [PRODUCTION_REEXEC_FLAG, "--port", String(port)];
26085
- }
26086
26346
  function reexecArgv(rawArgs2) {
26087
26347
  const kept = [];
26088
26348
  for (let index = 0; index < rawArgs2.length; index++) {
@@ -26097,110 +26357,6 @@ function reexecArgv(rawArgs2) {
26097
26357
  }
26098
26358
  return kept;
26099
26359
  }
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
26360
  function isGlobalInstall(bundleDir, env = process.env) {
26205
26361
  if (env.npm_execpath) return false;
26206
26362
  const segments = bundleDir.split(/[\\/]/u);
@@ -26294,11 +26450,386 @@ npm exec ${packageSpec} did not finish within ${formatTimeout(execTimeoutMs)} an
26294
26450
  }
26295
26451
  var npmRegistryReleaseAdapter = createNpmRegistryReleaseAdapter();
26296
26452
 
26453
+ // src/server-release.ts
26454
+ init_frizz_paths();
26455
+ import { execFile as execFile5 } from "node:child_process";
26456
+ import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
26457
+ import {
26458
+ closeSync as closeSync5,
26459
+ existsSync as existsSync8,
26460
+ fsyncSync as fsyncSync5,
26461
+ mkdirSync as mkdirSync12,
26462
+ openSync as openSync6,
26463
+ readFileSync as readFileSync13,
26464
+ realpathSync as realpathSync6,
26465
+ renameSync as renameSync6,
26466
+ rmSync as rmSync9,
26467
+ statSync as statSync5,
26468
+ writeFileSync as writeFileSync11
26469
+ } from "node:fs";
26470
+ 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";
26471
+ var SERVER_PROTOCOL = 1;
26472
+ var SERVER_DATA_EPOCH = 1;
26473
+ 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;
26474
+ var PACKAGE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
26475
+ var GENERATION = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
26476
+ var CURRENT_COMPATIBILITY = { protocol: SERVER_PROTOCOL, dataEpoch: SERVER_DATA_EPOCH };
26477
+ function assertSpecShape(spec) {
26478
+ if (typeof spec.package !== "string" || typeof spec.version !== "string" || !PACKAGE.test(spec.package) || !VERSION.test(spec.version))
26479
+ throw new Error("invalid exact Frizz server package/version");
26480
+ if (!Number.isSafeInteger(spec.protocol) || spec.protocol < 1 || !Number.isSafeInteger(spec.dataEpoch) || spec.dataEpoch < 1)
26481
+ throw new Error("invalid Frizz server protocol/data epoch");
26482
+ }
26483
+ function assertSpec(spec, compatibility = CURRENT_COMPATIBILITY) {
26484
+ assertSpecShape(spec);
26485
+ if (spec.protocol !== compatibility.protocol || spec.dataEpoch !== compatibility.dataEpoch)
26486
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26487
+ }
26488
+ function readJson(path) {
26489
+ return JSON.parse(readFileSync13(path, "utf8"));
26490
+ }
26491
+ function record(value) {
26492
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid Frizz server manifest");
26493
+ return value;
26494
+ }
26495
+ function serverReleaseSpec(manifest, packageOverride) {
26496
+ const metadata = record(record(manifest).frizzServer);
26497
+ const spec = {
26498
+ package: packageOverride ?? metadata.package,
26499
+ version: metadata.version,
26500
+ protocol: metadata.protocol,
26501
+ dataEpoch: metadata.dataEpoch
26502
+ };
26503
+ assertSpec(spec);
26504
+ return spec;
26505
+ }
26506
+ function containedFile(root, name, directory = false) {
26507
+ const path = realpathSync6(join15(root, name));
26508
+ const rel = relative3(realpathSync6(root), path);
26509
+ if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute6(rel))
26510
+ throw new Error(`Frizz server artifact escapes its package: ${name}`);
26511
+ const stat = statSync5(path);
26512
+ if (directory ? !stat.isDirectory() : !stat.isFile()) throw new Error(`invalid Frizz server artifact: ${name}`);
26513
+ return path;
26514
+ }
26515
+ function validateServerGeneration(root, spec, id, compatibility = CURRENT_COMPATIBILITY) {
26516
+ assertSpec(spec, compatibility);
26517
+ if (!GENERATION.test(id)) throw new Error("invalid Frizz server generation id");
26518
+ const manifest = record(readJson(join15(root, "package.json")));
26519
+ const metadata = record(manifest.frizzServer);
26520
+ if (manifest.name !== spec.package || manifest.version !== spec.version)
26521
+ throw new Error(`Frizz server install did not contain ${spec.package}@${spec.version}`);
26522
+ if (metadata.protocol !== spec.protocol || metadata.dataEpoch !== spec.dataEpoch)
26523
+ throw new Error("Frizz server protocol/data epoch is incompatible; the current server has not been stopped");
26524
+ const entry = containedFile(root, "dist/dev-child.js");
26525
+ containedFile(root, "dist/codex-app-server-daemon.js");
26526
+ containedFile(root, "dist/claude-agent-broker.js");
26527
+ containedFile(root, "web-dist/index.html");
26528
+ containedFile(root, "runtime/board/index.mjs");
26529
+ containedFile(root, "runtime/cc-worker/.claude-plugin/plugin.json");
26530
+ return { ...spec, id, root: realpathSync6(root), entry };
26531
+ }
26532
+ function serverGenerationLaunch(generation) {
26533
+ return {
26534
+ entry: generation.entry,
26535
+ environment: {
26536
+ FRIZZ_STABLE_ARTIFACT: `npm:${generation.package}@${generation.version}`,
26537
+ FRIZZ_STABLE_WEB_DIST: join15(generation.root, "web-dist"),
26538
+ FRIZZ_SCRIPTS_DIR: join15(generation.root, "runtime/board"),
26539
+ FRIZZ_WORKER_PLUGIN_DIR: join15(generation.root, "runtime/cc-worker")
26540
+ }
26541
+ };
26542
+ }
26543
+ function resolveNpmCli(env = process.env, executable = process.execPath) {
26544
+ const candidates = [];
26545
+ if (env.npm_execpath?.endsWith("npm-cli.js")) candidates.push(env.npm_execpath);
26546
+ for (const directory of [dirname13(executable), ...(env.PATH ?? env.Path ?? "").split(delimiter2).filter(Boolean)]) {
26547
+ candidates.push(join15(directory, "node_modules/npm/bin/npm-cli.js"), join15(directory, "../lib/node_modules/npm/bin/npm-cli.js"));
26548
+ try {
26549
+ const path = realpathSync6(join15(directory, "npm"));
26550
+ if (path.endsWith("npm-cli.js")) candidates.push(path);
26551
+ } catch {
26552
+ }
26553
+ }
26554
+ const found = candidates.find((path) => existsSync8(path) && statSync5(path).isFile());
26555
+ if (!found) throw new Error("npm was not found beside Node or on PATH; install Node with npm to install Frizz server releases");
26556
+ return resolve6(found);
26557
+ }
26558
+ function npmServerPackageInstaller(env = process.env) {
26559
+ const run3 = (args2, cwd) => new Promise((resolveOutput, reject) => {
26560
+ let cli;
26561
+ try {
26562
+ cli = resolveNpmCli(env);
26563
+ } catch (error) {
26564
+ reject(error);
26565
+ return;
26566
+ }
26567
+ execFile5(process.execPath, [cli, ...args2], {
26568
+ env,
26569
+ cwd,
26570
+ encoding: "utf8",
26571
+ windowsHide: true,
26572
+ timeout: 18e4,
26573
+ killSignal: "SIGKILL",
26574
+ maxBuffer: 4 * 1024 * 1024
26575
+ }, (error, stdout, stderr) => {
26576
+ if (error) {
26577
+ const tail = stderr.trim().split("\n").slice(-8).join("\n");
26578
+ reject(new Error(`Frizz server npm operation failed: ${tail || error.message}`));
26579
+ } else resolveOutput(stdout);
26580
+ });
26581
+ });
26582
+ return {
26583
+ async install(prefix, spec) {
26584
+ assertSpec(spec);
26585
+ await run3([
26586
+ "install",
26587
+ "--prefix",
26588
+ prefix,
26589
+ "--global=false",
26590
+ "--ignore-scripts",
26591
+ "--no-audit",
26592
+ "--no-fund",
26593
+ "--engine-strict",
26594
+ "--install-strategy=hoisted",
26595
+ "--include=optional",
26596
+ "--omit=dev",
26597
+ "--save-exact",
26598
+ `${spec.package}@${spec.version}`
26599
+ ], prefix);
26600
+ },
26601
+ async latestVersion(packageName) {
26602
+ if (!PACKAGE.test(packageName)) throw new Error("invalid Frizz server package name");
26603
+ const version = JSON.parse(await run3(["view", `${packageName}@latest`, "version", "--json"]));
26604
+ if (typeof version !== "string" || !VERSION.test(version)) throw new Error("npm returned an invalid Frizz server version");
26605
+ return version;
26606
+ }
26607
+ };
26608
+ }
26609
+ function atomicJson2(path, value) {
26610
+ mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
26611
+ const temp = `${path}.${randomUUID6()}.tmp`;
26612
+ try {
26613
+ const fd = openSync6(temp, "wx", 384);
26614
+ try {
26615
+ writeFileSync11(fd, `${JSON.stringify(value)}
26616
+ `);
26617
+ fsyncSync5(fd);
26618
+ } finally {
26619
+ closeSync5(fd);
26620
+ }
26621
+ renameSync6(temp, path);
26622
+ try {
26623
+ const directory = openSync6(dirname13(path), "r");
26624
+ try {
26625
+ fsyncSync5(directory);
26626
+ } finally {
26627
+ closeSync5(directory);
26628
+ }
26629
+ } catch {
26630
+ }
26631
+ } finally {
26632
+ rmSync9(temp, { force: true });
26633
+ }
26634
+ }
26635
+ var ServerReleaseStore = class {
26636
+ constructor(spec, installer, roots = frizzPaths(), compatibility = CURRENT_COMPATIBILITY) {
26637
+ this.spec = spec;
26638
+ this.installer = installer;
26639
+ this.roots = roots;
26640
+ this.compatibility = compatibility;
26641
+ assertSpec(spec, compatibility);
26642
+ const key = createHash4("sha256").update(spec.package).digest("hex").slice(0, 16);
26643
+ this.generations = join15(roots.cache, "server-releases", key);
26644
+ this.selection = join15(roots.state, "server-releases", key, "active.json");
26645
+ this.compatibilityMarker = join15(roots.state, "server-releases", "data-compatibility.json");
26646
+ }
26647
+ generations;
26648
+ selection;
26649
+ /** One global data marker: every package override opens the same Frizz databases. */
26650
+ compatibilityMarker;
26651
+ packageRoot(id) {
26652
+ return join15(this.generations, id, "node_modules", this.spec.package);
26653
+ }
26654
+ readCompatibilityMarker() {
26655
+ if (!existsSync8(this.compatibilityMarker)) return void 0;
26656
+ try {
26657
+ const marker = record(readJson(this.compatibilityMarker));
26658
+ const compatibility = { protocol: marker.protocol, dataEpoch: marker.dataEpoch };
26659
+ assertSpecShape({ package: this.spec.package, version: this.spec.version, ...compatibility });
26660
+ return compatibility;
26661
+ } catch {
26662
+ throw new Error("invalid saved Frizz server data compatibility marker; refusing to risk a data downgrade");
26663
+ }
26664
+ }
26665
+ assertMarkerIsSupported() {
26666
+ const marker = this.readCompatibilityMarker();
26667
+ if (!marker) return;
26668
+ if (marker.protocol !== this.compatibility.protocol || marker.dataEpoch > this.compatibility.dataEpoch)
26669
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26670
+ }
26671
+ /**
26672
+ * Advance the global epoch only after the exact replacement has been staged and validated, but
26673
+ * before handing it to the caller that can launch it. A crash before first boot must never let an
26674
+ * older shell reopen the database the candidate may already have migrated.
26675
+ */
26676
+ markCompatibilityBeforeLaunch() {
26677
+ const marker = this.readCompatibilityMarker();
26678
+ if (marker?.protocol !== void 0 && marker.protocol !== this.compatibility.protocol)
26679
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26680
+ if ((marker?.dataEpoch ?? 0) > this.compatibility.dataEpoch)
26681
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26682
+ if (marker?.dataEpoch === this.compatibility.dataEpoch) return;
26683
+ atomicJson2(this.compatibilityMarker, this.compatibility);
26684
+ }
26685
+ async prepareInitial(version) {
26686
+ const generation = await this.prepare(version);
26687
+ this.markCompatibilityBeforeLaunch();
26688
+ return generation;
26689
+ }
26690
+ async load() {
26691
+ this.assertMarkerIsSupported();
26692
+ if (!existsSync8(this.selection)) return this.prepareInitial(this.spec.version);
26693
+ const selected = record(readJson(this.selection));
26694
+ const spec = selected;
26695
+ assertSpecShape(spec);
26696
+ if (spec.package !== this.spec.package || typeof selected.id !== "string" || !GENERATION.test(selected.id))
26697
+ throw new Error("invalid saved Frizz server selection; refusing to fall back to an older server");
26698
+ if (spec.protocol !== this.compatibility.protocol)
26699
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26700
+ if (spec.dataEpoch < this.compatibility.dataEpoch) return this.prepareInitial(this.spec.version);
26701
+ if (spec.dataEpoch > this.compatibility.dataEpoch)
26702
+ throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
26703
+ const root = this.packageRoot(selected.id);
26704
+ const generation = !existsSync8(root) ? await this.prepare(spec.version) : validateServerGeneration(root, spec, selected.id, this.compatibility);
26705
+ this.markCompatibilityBeforeLaunch();
26706
+ return generation;
26707
+ }
26708
+ async prepare(version) {
26709
+ const spec = { ...this.spec, version };
26710
+ assertSpec(spec, this.compatibility);
26711
+ mkdirSync12(this.generations, { recursive: true, mode: 448 });
26712
+ const id = randomUUID6();
26713
+ const staging = join15(this.generations, `${id}.staging`);
26714
+ const destination = join15(this.generations, id);
26715
+ mkdirSync12(staging, { mode: 448 });
26716
+ try {
26717
+ writeFileSync11(join15(staging, "package.json"), '{"private":true}\n', { mode: 384 });
26718
+ await this.installer.install(staging, spec);
26719
+ validateServerGeneration(join15(staging, "node_modules", spec.package), spec, id, this.compatibility);
26720
+ renameSync6(staging, destination);
26721
+ return validateServerGeneration(this.packageRoot(id), spec, id, this.compatibility);
26722
+ } finally {
26723
+ rmSync9(staging, { recursive: true, force: true });
26724
+ }
26725
+ }
26726
+ commit(generation) {
26727
+ if (generation.package !== this.spec.package || generation.root !== realpathSync6(this.packageRoot(generation.id)))
26728
+ throw new Error("cannot select a Frizz server outside this release store");
26729
+ validateServerGeneration(generation.root, generation, generation.id, this.compatibility);
26730
+ const { package: packageName, version, protocol, dataEpoch, id } = generation;
26731
+ atomicJson2(this.selection, { package: packageName, version, protocol, dataEpoch, id });
26732
+ }
26733
+ };
26734
+
26735
+ // src/server-owner.ts
26736
+ init_frizz_paths();
26737
+ init_project_launch();
26738
+ import { randomUUID as randomUUID7 } from "node:crypto";
26739
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, realpathSync as realpathSync7 } from "node:fs";
26740
+ import { join as join16 } from "node:path";
26741
+ var STABLE_SERVER_OWNER_NAME = "frizz-server";
26742
+ var STABLE_SERVER_OWNER_PROJECT_ID = "c1fd5810-0f8a-4c1d-91a0-6d7445d28e5a";
26743
+ var ADDRESS_NAME = "address.json";
26744
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
26745
+ function rootsFor(roots) {
26746
+ return roots ?? frizzPaths();
26747
+ }
26748
+ function stableServerOwnerTarget(roots) {
26749
+ const resolved = rootsFor(roots);
26750
+ mkdirSync13(resolved.data, { recursive: true, mode: 448 });
26751
+ return {
26752
+ projectId: STABLE_SERVER_OWNER_PROJECT_ID,
26753
+ projectDir: realpathSync7(resolved.data),
26754
+ stateDir: join16(resolved.state, STABLE_SERVER_OWNER_NAME)
26755
+ };
26756
+ }
26757
+ function matchingTarget(owner, target2) {
26758
+ return owner.projectId === target2.projectId && owner.projectDir === target2.projectDir;
26759
+ }
26760
+ function readAddress(path) {
26761
+ try {
26762
+ const value = JSON.parse(readFileSync14(path, "utf8"));
26763
+ 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;
26764
+ return value;
26765
+ } catch {
26766
+ return null;
26767
+ }
26768
+ }
26769
+ function readStableServerOwner(roots) {
26770
+ const target2 = stableServerOwnerTarget(roots);
26771
+ const owner = readProjectLaunchOwner(target2.stateDir);
26772
+ if (!owner || !matchingTarget(owner, target2) || processGenerationIsStale(owner)) return { kind: "idle" };
26773
+ const address = readAddress(join16(target2.stateDir, ADDRESS_NAME));
26774
+ if (!address || address.ownerToken !== owner.token || address.pid !== owner.pid || address.processStart !== owner.processStart) return { kind: "busy", owner };
26775
+ return { kind: "running", port: address.port, owner };
26776
+ }
26777
+ function wrapLease(lease) {
26778
+ return {
26779
+ target: lease.target,
26780
+ token: lease.token,
26781
+ pid: lease.pid,
26782
+ processStart: lease.processStart,
26783
+ publisherToken: randomUUID7()
26784
+ };
26785
+ }
26786
+ function acquireStableServerOwner(roots) {
26787
+ const target2 = stableServerOwnerTarget(roots);
26788
+ const attempt = tryAcquireProjectLaunchOwner(target2, "server");
26789
+ if (attempt.kind === "acquired") return { kind: "acquired", lease: holdLease(attempt.lease) };
26790
+ const observed = readStableServerOwner(roots);
26791
+ return observed.kind === "running" || observed.kind === "busy" ? observed : { kind: "busy", owner: attempt.owner };
26792
+ }
26793
+ function publishStableServerAddress(lease, port) {
26794
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`invalid stable Frizz server port: ${port}`);
26795
+ const owner = readProjectLaunchOwner(lease.target.stateDir);
26796
+ 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");
26797
+ writeProjectStatus(join16(lease.target.stateDir, ADDRESS_NAME), {
26798
+ version: 1,
26799
+ ownerToken: lease.token,
26800
+ pid: lease.pid,
26801
+ processStart: lease.processStart,
26802
+ publisherToken: lease.publisherToken,
26803
+ port
26804
+ });
26805
+ }
26806
+ function releaseStableServerOwner(lease) {
26807
+ return releaseHeldLease(lease);
26808
+ }
26809
+ var heldLeases = /* @__PURE__ */ new Map();
26810
+ function holdLease(lease) {
26811
+ const wrapped = wrapLease(lease);
26812
+ heldLeases.set(wrapped.publisherToken, lease);
26813
+ return wrapped;
26814
+ }
26815
+ function releaseHeldLease(lease) {
26816
+ const held = heldLeases.get(lease.publisherToken);
26817
+ if (!held || held.token !== lease.token || held.pid !== lease.pid || held.processStart !== lease.processStart) return false;
26818
+ const released = held.release();
26819
+ if (!released) return false;
26820
+ heldLeases.delete(lease.publisherToken);
26821
+ removeProjectStatus(join16(lease.target.stateDir, ADDRESS_NAME), {
26822
+ ownerToken: lease.token,
26823
+ pid: lease.pid,
26824
+ processStart: lease.processStart,
26825
+ publisherToken: lease.publisherToken
26826
+ });
26827
+ return true;
26828
+ }
26829
+
26297
26830
  // src/preflight.ts
26831
+ init_native_helper();
26298
26832
  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
26833
  var SUPPORTED_NODE_LINES = [
26303
26834
  { major: 22, minor: 13 },
26304
26835
  { major: 23, minor: 4 }
@@ -26342,25 +26873,6 @@ function assertLaunchPrerequisites(options2 = {}) {
26342
26873
  );
26343
26874
  assertRequiredExecutables(options2.command ?? commandIsAvailable);
26344
26875
  }
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
26876
 
26365
26877
  // src/production.ts
26366
26878
  init_src();
@@ -26369,7 +26881,7 @@ init_project_identity();
26369
26881
  var PACKAGE_NAME = process.env.FRIZZ_REGISTRY_PACKAGE ?? "frizz";
26370
26882
  function resolvePackageVersion() {
26371
26883
  try {
26372
- const manifest = JSON.parse(readFileSync13(join16(import.meta.dirname, "..", "package.json"), "utf8"));
26884
+ const manifest = JSON.parse(readFileSync15(join17(import.meta.dirname, "..", "package.json"), "utf8"));
26373
26885
  if (typeof manifest.version === "string" && manifest.version.trim()) return manifest.version.trim();
26374
26886
  } catch {
26375
26887
  }
@@ -26433,10 +26945,13 @@ var sandbox = options.sandbox ? prepareSandbox() : null;
26433
26945
  if (sandbox) process.on("exit", () => cleanupSandbox(sandbox.home));
26434
26946
  var remote = null;
26435
26947
  var paneHost = null;
26436
- var rearmPaneHost = null;
26437
26948
  var globalInstall = process.env[GLOBAL_INSTALL_ENV] === "1" || isGlobalInstall(import.meta.dirname);
26438
26949
  var accessPane = null;
26439
26950
  var activeAccessLink = null;
26951
+ var serverOwner;
26952
+ process.on("exit", () => {
26953
+ if (serverOwner) releaseStableServerOwner(serverOwner);
26954
+ });
26440
26955
  var launchIntent;
26441
26956
  var workspace = (() => {
26442
26957
  try {
@@ -26522,15 +27037,15 @@ if (options.link) {
26522
27037
  var logger = setAmbientLogger(
26523
27038
  process.env.FRIZZ_LOG_FILE ? createLogger({ file: process.env.FRIZZ_LOG_FILE, owner: false }) : createLogger({ file: runLogPath(workspace.stateDir) })
26524
27039
  );
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);
27040
+ var readout = reexec || process.env.FRIZZ_PRODUCTION_SUPERVISOR === "1" ? void 0 : new Readout({ debug: options.debug });
27041
+ var activityReadout = readout ?? (reexec && process.stdout.isTTY ? noticeOnlyReadout({}) : void 0);
26527
27042
  attachTerminalMirror(logger, options.debug || process.env.FRIZZ_DEBUG === "1");
26528
27043
  readout?.plan([
26529
27044
  { key: "server", label: "Server" },
26530
27045
  { key: "browser", label: options.noApp ? "Address" : "Browser" }
26531
27046
  ]);
26532
27047
  readout?.begin("server", "starting");
26533
- logger.info("launcher", `frizz ${PACKAGE_VERSION} starting for ${workspace.root}`);
27048
+ logger.info("launcher", `Frizz launcher ${PACKAGE_VERSION} starting for ${workspace.root}`);
26534
27049
  var target = workspaceLaunchTarget(workspace);
26535
27050
  var expected = expectedOwnerHealth(target, readProjectLaunchOwner(workspace.stateDir));
26536
27051
  async function existingPort() {
@@ -26579,7 +27094,7 @@ async function openOrPrint(port, reused, path = "") {
26579
27094
  readout?.begin("browser", options.appMode ? "requesting app window" : "requesting default browser");
26580
27095
  try {
26581
27096
  if (options.appMode) {
26582
- await launchApp(url, { dataPath: join16(workspace.stateDir, "browser-profile") });
27097
+ await launchApp(url, { dataPath: join17(workspace.stateDir, "browser-profile") });
26583
27098
  browser = reused ? "focused the Frizz app window" : "opened the Frizz app window";
26584
27099
  } else {
26585
27100
  await launchBrowserTab(url);
@@ -26598,7 +27113,7 @@ async function openOrPrint(port, reused, path = "") {
26598
27113
  const warnings = [];
26599
27114
  if (sandbox) warnings.push(`Sandbox: everything here is throwaway (${sandbox.home}) and is deleted when this terminal closes.`);
26600
27115
  if (!readout) {
26601
- console.log(`${reused ? "reusing" : "started"} Frizz ${PACKAGE_VERSION} for ${workspace.root}`);
27116
+ console.log(`${reused ? "reusing" : "started"} Frizz for ${workspace.root}`);
26602
27117
  console.log(url);
26603
27118
  if (publicOrigin) console.log(activeAccessLink?.url ?? `${publicOrigin}/`);
26604
27119
  for (const warning of warnings) console.log(warning);
@@ -26623,31 +27138,40 @@ async function openOrPrint(port, reused, path = "") {
26623
27138
  }
26624
27139
  );
26625
27140
  }
26626
- async function runSupervisor(port, token) {
27141
+ async function runSupervisor(port, token, onPrepared = () => {
27142
+ }) {
26627
27143
  assertLaunchPrerequisites();
26628
- ensureNativeHelperPermissions();
27144
+ if (!serverOwner) throw new Error("stable server launch is missing global ownership");
26629
27145
  const owner = adoptProjectLaunchOwner(target, token, "supervisor");
26630
27146
  const env = projectLaunchEnvironment(
26631
27147
  {
26632
27148
  ...process.env,
26633
27149
  FRIZZ_PRODUCTION_SUPERVISOR: "1",
27150
+ FRIZZ_SERVER_OWNERSHIP: JSON.stringify(projectLaunchEnvironment({}, serverOwner.target, serverOwner.token)),
26634
27151
  ...logEnvironment(logger, options.debug ? "debug" : "info"),
26635
27152
  ...options.debug ? { FRIZZ_DEBUG: "1" } : {}
26636
27153
  },
26637
27154
  target,
26638
27155
  owner.token
26639
27156
  );
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;
27157
+ const spec = serverReleaseSpec(
27158
+ JSON.parse(readFileSync15(join17(import.meta.dirname, "..", "package.json"), "utf8")),
27159
+ process.env.FRIZZ_SERVER_PACKAGE
27160
+ );
27161
+ const installer = npmServerPackageInstaller(env);
27162
+ const store = new ServerReleaseStore(spec, installer);
27163
+ activityReadout?.notice("progress", "Starting", "loading the selected Frizz server release");
27164
+ let active = await store.load();
27165
+ let pending;
27166
+ let previous = active;
27167
+ onPrepared();
26646
27168
  let updateAvailable = false;
26647
27169
  let updateVersion;
26648
27170
  const refreshUpdateAvailable = async () => {
26649
27171
  try {
26650
- const plan = await planRegistryUpdate(PACKAGE_NAME, PACKAGE_VERSION, npmRegistryReleaseAdapter);
27172
+ const checkedVersion = active.version;
27173
+ const plan = await planRegistryUpdate(spec.package, checkedVersion, installer);
27174
+ if (active.version !== checkedVersion) return;
26651
27175
  updateAvailable = plan !== null;
26652
27176
  updateVersion = plan?.latestVersion;
26653
27177
  } catch {
@@ -26673,83 +27197,48 @@ async function runSupervisor(port, token) {
26673
27197
  launchOwnerToken: owner.token,
26674
27198
  env,
26675
27199
  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 }),
27200
+ childLaunchProvider: () => serverGenerationLaunch(pending ?? active),
27201
+ updateMode: "child",
26678
27202
  updateAvailable: () => updateAvailable,
26679
- version: PACKAGE_VERSION,
27203
+ version: () => active.version,
27204
+ launcherVersion: PACKAGE_VERSION,
26680
27205
  updateVersion: () => updateVersion,
26681
27206
  // The terminal that owns the board says so when the board goes down and comes back. Everything
26682
27207
  // here is triggered from a browser tab or by a crash, so without this the foreground process is
26683
27208
  // the last place to learn what happened to it.
26684
27209
  onActivity: (event) => renderSupervisorActivity(activityReadout, event),
26685
27210
  updateRestart: async () => {
27211
+ previous = active;
26686
27212
  try {
26687
- const plan = await planRegistryUpdate(PACKAGE_NAME, PACKAGE_VERSION, npmRegistryReleaseAdapter);
27213
+ const plan = await planRegistryUpdate(spec.package, active.version, installer);
26688
27214
  if (!plan) {
26689
27215
  updateAvailable = false;
26690
27216
  updateVersion = void 0;
26691
- return { state: "failed", message: `Frizz ${PACKAGE_VERSION} is already current` };
27217
+ return { state: "failed", message: `Frizz ${active.version} is already current` };
26692
27218
  }
27219
+ updateAvailable = true;
26693
27220
  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` };
27221
+ pending = await store.prepare(plan.latestVersion);
27222
+ return { state: "ready", message: `Frizz ${plan.latestVersion} is installed and compatible` };
26696
27223
  } catch (error) {
26697
27224
  return { state: "failed", message: error instanceof Error ? error.message : String(error) };
26698
27225
  }
26699
27226
  },
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: () => {
27227
+ commitUpdate: () => {
27228
+ if (!pending) throw new Error("no server update was prepared");
27229
+ store.commit(pending);
27230
+ active = pending;
27231
+ pending = void 0;
27232
+ updateAvailable = false;
27233
+ updateVersion = void 0;
26706
27234
  },
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);
27235
+ rollbackUpdate: () => {
27236
+ if (active.id !== previous.id) store.commit(previous);
27237
+ active = previous;
27238
+ pending = void 0;
26751
27239
  }
26752
27240
  });
27241
+ publishStableServerAddress(serverOwner, port);
26753
27242
  remote = createRemoteController({ host: supervisor, port, log: logger, say: (message) => console.error(`frizz: ${message}`) });
26754
27243
  try {
26755
27244
  await remote.serveSaved();
@@ -26770,16 +27259,14 @@ async function runSupervisor(port, token) {
26770
27259
  onChanged: (config) => logger.info("remote", config ? `reached at https://${config.hostname}` : "loopback only"),
26771
27260
  sandbox: sandbox !== null
26772
27261
  });
26773
- const bindings = { l: accessPane, L: accessPane, r: remotePane, R: remotePane };
26774
- const armPaneHost = () => {
26775
- paneHost = installPaneHost({ bindings });
26776
- };
26777
- armPaneHost();
26778
- rearmPaneHost = armPaneHost;
27262
+ paneHost = installPaneHost({ bindings: { l: accessPane, L: accessPane, r: remotePane, R: remotePane } });
26779
27263
  const stop = createSupervisorShutdownHandler({
26780
27264
  close: () => supervisor.close(),
26781
27265
  force: () => supervisor.forceStop(),
26782
- release: () => owner.release(),
27266
+ release: () => {
27267
+ owner.release();
27268
+ if (serverOwner) releaseStableServerOwner(serverOwner);
27269
+ },
26783
27270
  // Acknowledge the first signal on the spot; the drain that follows is bounded but not instant.
26784
27271
  onStop: () => {
26785
27272
  logger.info("launcher", "stop signal received; draining the control plane");
@@ -26809,17 +27296,28 @@ async function runSupervisor(port, token) {
26809
27296
  process.on("SIGHUP", stop);
26810
27297
  void supervisor.stopRequested.then(stop);
26811
27298
  await supervisor.firstBoot;
27299
+ store.commit(active);
27300
+ activityReadout?.notice("done", "Server", `${active.version} \xB7 launcher ${PACKAGE_VERSION}`);
26812
27301
  if (reexec) {
26813
27302
  activityReadout?.notice(
26814
27303
  "done",
26815
27304
  "Updated",
26816
- `Frizz ${PACKAGE_VERSION} is serving on port ${port} \xB7 ctrl-c to stop${globalInstall ? ` \xB7 ${keepUpdateHint(PACKAGE_NAME, PACKAGE_VERSION)}` : ""}`
27305
+ `Frizz ${active.version} is serving on port ${port} \xB7 ctrl-c to stop${globalInstall ? ` \xB7 ${keepUpdateHint(PACKAGE_NAME, PACKAGE_VERSION)}` : ""}`
26817
27306
  );
26818
27307
  }
26819
27308
  return await new Promise(() => {
26820
27309
  });
26821
27310
  }
26822
27311
  try {
27312
+ const globalClaim = acquireStableServerOwner();
27313
+ if (globalClaim.kind === "busy") throw new Error("Frizz is starting or recovering on this machine; retry shortly");
27314
+ if (globalClaim.kind === "running") {
27315
+ if (options.port && options.port !== globalClaim.port)
27316
+ throw new Error(`Frizz is already running on port ${globalClaim.port}; --port cannot change a running server`);
27317
+ await openOrPrint(globalClaim.port, true, slugPath());
27318
+ process.exit(0);
27319
+ }
27320
+ serverOwner = globalClaim.lease;
26823
27321
  if (process.env.FRIZZ_PRODUCTION_SUPERVISOR === "1" || reexec) {
26824
27322
  if (!options.port) throw new Error("internal registry supervisor launch is missing --port");
26825
27323
  const token = projectLaunchOwnerTokenFromEnvironment(process.env);
@@ -26844,9 +27342,14 @@ try {
26844
27342
  const allocation = await allocatePort(options.port, readPreferredPort(workspace.stateDir), { host: LOOPBACK_BIND_HOST });
26845
27343
  const port = allocation.port;
26846
27344
  portReservation = allocation.release;
26847
- const running = runSupervisor(port, claim.lease.token);
27345
+ let didPrepare;
27346
+ const prepared = new Promise((resolve7) => {
27347
+ didPrepare = resolve7;
27348
+ });
27349
+ const running = runSupervisor(port, claim.lease.token, didPrepare);
26848
27350
  release();
26849
27351
  release = void 0;
27352
+ await Promise.race([prepared, running]);
26850
27353
  await Promise.race([
26851
27354
  waitForWorkspace(
26852
27355
  port,