frizz-server 0.15.17 → 0.15.18

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 (98) hide show
  1. package/dist/dev-child.js +1106 -615
  2. package/package.json +1 -1
  3. package/web-dist/assets/{abnfDiagram-VRR7QNED-BCWz80uN.js → abnfDiagram-VRR7QNED-CN39SboF.js} +1 -1
  4. package/web-dist/assets/{arc-DDtkTWyk.js → arc-BTVl3fKG.js} +1 -1
  5. package/web-dist/assets/architecture-TIHT7OUA-BQiZAFL1.js +1 -0
  6. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-BpRbm_s5.js → architectureDiagram-ZJ3FMSHR-CLNnuVr7.js} +1 -1
  7. package/web-dist/assets/{blockDiagram-677ZJIJ3-_kGtJSW2.js → blockDiagram-677ZJIJ3-bTZNRjJP.js} +1 -1
  8. package/web-dist/assets/{c4Diagram-LMCZKHZV-D8XwDoiY.js → c4Diagram-LMCZKHZV-DeekS0uP.js} +1 -1
  9. package/web-dist/assets/channel-B7pe0_FG.js +1 -0
  10. package/web-dist/assets/{chunk-32BRIVSS-BUGbBNz_.js → chunk-32BRIVSS-CAVyehgA.js} +1 -1
  11. package/web-dist/assets/{chunk-52WLFC77-Dc0eyYJe.js → chunk-52WLFC77-C_ljp3lt.js} +1 -1
  12. package/web-dist/assets/{chunk-C7G6YPKG-CNP8apQ_.js → chunk-C7G6YPKG-BPdDxqsZ.js} +1 -1
  13. package/web-dist/assets/{chunk-EX3LRPZG-m5klG9uB.js → chunk-EX3LRPZG-DWH-e06D.js} +1 -1
  14. package/web-dist/assets/{chunk-FWX5IMBZ-CHO58JFl.js → chunk-FWX5IMBZ-yrfMig6k.js} +2 -2
  15. package/web-dist/assets/{chunk-HOUHSVGY-DjH-r82r.js → chunk-HOUHSVGY-DOIBrNRJ.js} +1 -1
  16. package/web-dist/assets/{chunk-ICXQ74PX-DkYjEd46.js → chunk-ICXQ74PX-kstLL_Nj.js} +1 -1
  17. package/web-dist/assets/{chunk-MOJQB5TN-D89rLIxX.js → chunk-MOJQB5TN-DDtU29dE.js} +1 -1
  18. package/web-dist/assets/{chunk-OGEWGWER-Db2MBETv.js → chunk-OGEWGWER-D2c91oEJ.js} +1 -1
  19. package/web-dist/assets/{chunk-PUDLZKDR-Dtdx_z8j.js → chunk-PUDLZKDR-3Z2pVgx9.js} +1 -1
  20. package/web-dist/assets/{chunk-Q4XR5HBZ-XYvD_xYq.js → chunk-Q4XR5HBZ-rmeb4GSY.js} +1 -1
  21. package/web-dist/assets/{chunk-RYQCIY6F-CZ7RTprX.js → chunk-RYQCIY6F-DE9FW0LM.js} +1 -1
  22. package/web-dist/assets/{chunk-V7JOEXUC-CoAVJiNJ.js → chunk-V7JOEXUC-CNvw-r6K.js} +1 -1
  23. package/web-dist/assets/{chunk-VAUOI2AC-DbjEHErZ.js → chunk-VAUOI2AC-DUK5u-DN.js} +1 -1
  24. package/web-dist/assets/{chunk-VR4S4FIN-4BB6eLCK.js → chunk-VR4S4FIN-8Qx2Il0y.js} +1 -1
  25. package/web-dist/assets/{chunk-WYO6CB5R-Cuvm_fLi.js → chunk-WYO6CB5R-Ch0_cnOf.js} +1 -1
  26. package/web-dist/assets/{chunk-XXDRQBXY-CvLzx99l.js → chunk-XXDRQBXY-Bmd-5yN2.js} +1 -1
  27. package/web-dist/assets/{chunk-ZGVPDNZ5-BccJcRbi.js → chunk-ZGVPDNZ5-MvQyLrp9.js} +1 -1
  28. package/web-dist/assets/classDiagram-OUVF2IWQ-CRHph3JZ.js +1 -0
  29. package/web-dist/assets/classDiagram-v2-EOCWNBFH-CRHph3JZ.js +1 -0
  30. package/web-dist/assets/{cose-bilkent-JH36ORCC-BfwT-16i.js → cose-bilkent-JH36ORCC-BmksLZVW.js} +1 -1
  31. package/web-dist/assets/{cynefin-VYW2F7L2-DbZ1V36t.js → cynefin-VYW2F7L2-nkrhmZuY.js} +1 -1
  32. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-mFI_mDt-.js → cynefinDiagram-TSTJHNR4-Cn3BNg8k.js} +1 -1
  33. package/web-dist/assets/{dagre-VKFMJZFB-BldEgHyl.js → dagre-VKFMJZFB-CTLouEOu.js} +1 -1
  34. package/web-dist/assets/{diagram-FQU43EPY-CWSdOHjN.js → diagram-FQU43EPY-u58-2525.js} +1 -1
  35. package/web-dist/assets/{diagram-G47NLZAW-CohHPpmC.js → diagram-G47NLZAW-Cqsb2-PB.js} +1 -1
  36. package/web-dist/assets/{diagram-NH7WQ7WH-CY9SIr04.js → diagram-NH7WQ7WH-8nRCmdGE.js} +1 -1
  37. package/web-dist/assets/{diagram-OA4YK3LP-D1ChaJcq.js → diagram-OA4YK3LP-BNuiXQqV.js} +1 -1
  38. package/web-dist/assets/{diagram-WEI45ONY-Bng85j-O.js → diagram-WEI45ONY-B0hi_GgE.js} +1 -1
  39. package/web-dist/assets/{dist-Dn2m7SMF.js → dist-D3Kuxfbr.js} +1 -1
  40. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-CcHfTwsR.js → ebnfDiagram-CCIWWBDH-C5EWDVC_.js} +1 -1
  41. package/web-dist/assets/{erDiagram-Q63AITRT-D9XptUTi.js → erDiagram-Q63AITRT-BLphelWe.js} +1 -1
  42. package/web-dist/assets/eventmodeling-45OFAUF4-C45ENNHn.js +1 -0
  43. package/web-dist/assets/flowDiagram-23GEKE2U-aHbgmUdC.js +1 -0
  44. package/web-dist/assets/{ganttDiagram-NO4QXBWP-CpDcHWNC.js → ganttDiagram-NO4QXBWP-Dr-DMNOU.js} +1 -1
  45. package/web-dist/assets/{gitGraph-TEB2WS4Q-dskxWshP.js → gitGraph-TEB2WS4Q-eyuwOTFC.js} +1 -1
  46. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-Gm-g6rXY.js → gitGraphDiagram-IHSO6WYX-BGcdlMUE.js} +1 -1
  47. package/web-dist/assets/index-C1G_Iq1d.js +337 -0
  48. package/web-dist/assets/index-DN7SaNCi.css +2 -0
  49. package/web-dist/assets/{info-DKCQHKI2-DfBWPig9.js → info-DKCQHKI2-T83_1t8S.js} +1 -1
  50. package/web-dist/assets/{infoDiagram-FWYZ7A6U-RA1gm_5G.js → infoDiagram-FWYZ7A6U-BZ7ZHiPE.js} +1 -1
  51. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-DMO4tlB7.js → ishikawaDiagram-FXEZZL3T-BbGaZkdX.js} +1 -1
  52. package/web-dist/assets/{journeyDiagram-5HDEW3XC-CGn0Vfft.js → journeyDiagram-5HDEW3XC-74db34k8.js} +1 -1
  53. package/web-dist/assets/{kanban-definition-HUTT4EX6-qRxs3pxC.js → kanban-definition-HUTT4EX6-_LDBnhu3.js} +1 -1
  54. package/web-dist/assets/{line-BihkNjKV.js → line-BSQ-_Ld3.js} +1 -1
  55. package/web-dist/assets/{linear-CbcYjraG.js → linear-TtekQ35r.js} +1 -1
  56. package/web-dist/assets/{mermaid-parser.core-CstJtBOd.js → mermaid-parser.core-C02mLVGD.js} +3 -3
  57. package/web-dist/assets/{mermaid.core-CAoFTj5o.js → mermaid.core-5-Q5DE5L.js} +3 -3
  58. package/web-dist/assets/{mindmap-definition-LN4V7U3C-Dobyf2TY.js → mindmap-definition-LN4V7U3C-D3OsE9M2.js} +1 -1
  59. package/web-dist/assets/{packet-7NZHBO7P-BV-_zRVl.js → packet-7NZHBO7P-ChHw2m9w.js} +1 -1
  60. package/web-dist/assets/{pegDiagram-2B236MQR-CoWfyvfk.js → pegDiagram-2B236MQR-Be0eWdVa.js} +1 -1
  61. package/web-dist/assets/{pie-RZYD4A2V-ptQFpYwJ.js → pie-RZYD4A2V-Y5I7o_6T.js} +1 -1
  62. package/web-dist/assets/{pieDiagram-ENE6RG2P-DCYSn0kJ.js → pieDiagram-ENE6RG2P-BVzIUwYC.js} +1 -1
  63. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-D-IhKJdt.js → quadrantDiagram-ABIIQ3AL--WcNeUJ5.js} +1 -1
  64. package/web-dist/assets/{radar-I7S5WNFK-CqtSeYcU.js → radar-I7S5WNFK-_iLcxlOU.js} +1 -1
  65. package/web-dist/assets/{railroad-3IZDKUUU-CejeX1m0.js → railroad-3IZDKUUU-DUaKyIjl.js} +1 -1
  66. package/web-dist/assets/railroad-abnf-AHOZXSZD-nyOfFXMk.js +1 -0
  67. package/web-dist/assets/railroad-ebnf-EBAXGLYW-D4SI1We5.js +1 -0
  68. package/web-dist/assets/railroad-peg-LSFZ7HO6-CWKTA2jO.js +1 -0
  69. package/web-dist/assets/{railroadDiagram-RFXS5EU6-CLMVsbkb.js → railroadDiagram-RFXS5EU6-JC2ENdbW.js} +1 -1
  70. package/web-dist/assets/{requirementDiagram-TGXJPOKE-J6ovU2nZ.js → requirementDiagram-TGXJPOKE-CZISQjoj.js} +1 -1
  71. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-BN1Wcnxg.js → sankeyDiagram-HTMAVEWB-B88W28-Q.js} +1 -1
  72. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CnDSUjeE.js → sequenceDiagram-DBY2YBRQ-BsoQQ4qC.js} +1 -1
  73. package/web-dist/assets/{src-ie67lQl7.js → src-GZyi5f3X.js} +1 -1
  74. package/web-dist/assets/{stateDiagram-2N3HPSRC-CDvDw5S6.js → stateDiagram-2N3HPSRC-DO6yDa4d.js} +1 -1
  75. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BXMrHzjW.js +1 -0
  76. package/web-dist/assets/{swimlanes-5IMT3BWC-BmljTUhI.js → swimlanes-5IMT3BWC-zONynGJq.js} +1 -1
  77. package/web-dist/assets/swimlanesDiagram-G3AALYLV-5NqpA4rx.js +8 -0
  78. package/web-dist/assets/{timeline-definition-FHXFAJF6-CjA-qR6O.js → timeline-definition-FHXFAJF6-ChT_IFLs.js} +1 -1
  79. package/web-dist/assets/{treeView-QDETBFTQ-CHKGsZbS.js → treeView-QDETBFTQ-BzNyoCOf.js} +1 -1
  80. package/web-dist/assets/{treemap-6X3UGDF4-CqU-nL1w.js → treemap-6X3UGDF4-BGbFAuCU.js} +1 -1
  81. package/web-dist/assets/{vennDiagram-L72KCM5P-BwQcxcel.js → vennDiagram-L72KCM5P-CNyyMggu.js} +1 -1
  82. package/web-dist/assets/{wardley-OPB4EBWU-Bo8e8N-h.js → wardley-OPB4EBWU-uHhpiyKg.js} +1 -1
  83. package/web-dist/assets/{wardleyDiagram-EHGQE667-ByioZC4m.js → wardleyDiagram-EHGQE667-gYBm-Ips.js} +1 -1
  84. package/web-dist/assets/{xychartDiagram-FW5EYKEG-B48Vy_EL.js → xychartDiagram-FW5EYKEG-BhuHU_17.js} +1 -1
  85. package/web-dist/index.html +2 -2
  86. package/web-dist/assets/architecture-TIHT7OUA-BSM-tgTt.js +0 -1
  87. package/web-dist/assets/channel-BwwaRyUv.js +0 -1
  88. package/web-dist/assets/classDiagram-OUVF2IWQ-DTgWbehh.js +0 -1
  89. package/web-dist/assets/classDiagram-v2-EOCWNBFH-DTgWbehh.js +0 -1
  90. package/web-dist/assets/eventmodeling-45OFAUF4-vqzohKbi.js +0 -1
  91. package/web-dist/assets/flowDiagram-23GEKE2U-CYPLykok.js +0 -1
  92. package/web-dist/assets/index-Bi4vNYho.css +0 -2
  93. package/web-dist/assets/index-C5sCt0eh.js +0 -334
  94. package/web-dist/assets/railroad-abnf-AHOZXSZD-D858Ks1V.js +0 -1
  95. package/web-dist/assets/railroad-ebnf-EBAXGLYW-CnKN0qs5.js +0 -1
  96. package/web-dist/assets/railroad-peg-LSFZ7HO6-CG_oVFtv.js +0 -1
  97. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-CWmRITC6.js +0 -1
  98. package/web-dist/assets/swimlanesDiagram-G3AALYLV-BTxJqUkH.js +0 -8
package/dist/dev-child.js CHANGED
@@ -654,7 +654,7 @@ var init_project_identity = __esm({
654
654
 
655
655
  // packages/server/src/project-root.ts
656
656
  import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
657
- import { closeSync as closeSync2, existsSync as existsSync2, fsyncSync as fsyncSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync3, realpathSync as realpathSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
657
+ import { closeSync as closeSync2, existsSync as existsSync2, fsyncSync as fsyncSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync3, realpathSync as realpathSync2, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
658
658
  import { homedir as homedir3 } from "node:os";
659
659
  import { dirname as dirname3, join as join4, parse, resolve as resolve3 } from "node:path";
660
660
  function projectIdPath(root) {
@@ -741,6 +741,17 @@ function isHomeDirectory(dir, home = homedir3()) {
741
741
  return false;
742
742
  }
743
743
  }
744
+ function isWorldWritable(dir, platform3 = process.platform) {
745
+ if (platform3 === "win32") return false;
746
+ try {
747
+ return (statSync2(dir).mode & 2) !== 0;
748
+ } catch {
749
+ return false;
750
+ }
751
+ }
752
+ function refuseWorldWritableProject(dir) {
753
+ if (isWorldWritable(dir)) throw new Error(`A folder every account can write to cannot be a project: ${dir}`);
754
+ }
744
755
  function existingProjectId(dir) {
745
756
  const fromFile = readProjectIdFile(dir);
746
757
  if (fromFile) return fromFile;
@@ -10669,7 +10680,7 @@ var init_types = __esm({
10669
10680
  * upgrade if you are experiencing issues.
10670
10681
  */
10671
10682
  merge(merging) {
10672
- const merged = new _ZodObject({
10683
+ const merged2 = new _ZodObject({
10673
10684
  unknownKeys: merging._def.unknownKeys,
10674
10685
  catchall: merging._def.catchall,
10675
10686
  shape: () => ({
@@ -10678,7 +10689,7 @@ var init_types = __esm({
10678
10689
  }),
10679
10690
  typeName: ZodFirstPartyTypeKind.ZodObject
10680
10691
  });
10681
- return merged;
10692
+ return merged2;
10682
10693
  }
10683
10694
  // merge<
10684
10695
  // Incoming extends AnyZodObject,
@@ -11045,8 +11056,8 @@ var init_types = __esm({
11045
11056
  if (isAborted(parsedLeft) || isAborted(parsedRight)) {
11046
11057
  return INVALID;
11047
11058
  }
11048
- const merged = mergeValues(parsedLeft.value, parsedRight.value);
11049
- if (!merged.valid) {
11059
+ const merged2 = mergeValues(parsedLeft.value, parsedRight.value);
11060
+ if (!merged2.valid) {
11050
11061
  addIssueToContext(ctx, {
11051
11062
  code: ZodIssueCode.invalid_intersection_types
11052
11063
  });
@@ -11055,7 +11066,7 @@ var init_types = __esm({
11055
11066
  if (isDirty(parsedLeft) || isDirty(parsedRight)) {
11056
11067
  status.dirty();
11057
11068
  }
11058
- return { status: status.value, value: merged.data };
11069
+ return { status: status.value, value: merged2.data };
11059
11070
  };
11060
11071
  if (ctx.common.async) {
11061
11072
  return Promise.all([
@@ -13874,16 +13885,22 @@ function askedQuestionFaults(q2) {
13874
13885
  return faults;
13875
13886
  }
13876
13887
  function secretAnswerText(path3) {
13877
- return `(secret ready at ${quotePath(path3)} \u2014 it can be read ONCE, within ${SECRET_TTL_MS / 6e4}m; never print it)`;
13888
+ return `${SECRET_ANSWER_PREFIX}${quotePath(path3)} \u2014 it can be read ONCE, within ${SECRET_TTL_MS / 6e4}m; never print it)`;
13878
13889
  }
13879
13890
  function quotePath(value) {
13880
13891
  return `'${value.replace(/'/g, `'\\''`)}'`;
13881
13892
  }
13893
+ function answerPicksText(chosen, text2) {
13894
+ const note = text2?.trim() ?? "";
13895
+ if (chosen.length === 0) return note;
13896
+ if (chosen.length === 1 && !note) return chosen[0];
13897
+ return [chosen.map((label) => `${ANSWER_PICK_PREFIX}${label}`).join("\n"), note].filter(Boolean).join("\n\n");
13898
+ }
13882
13899
  function questionAnswerMessage(answers, dismissed = []) {
13883
13900
  if (answers.length === 0) return questionsCancelledWakeMessage(dismissed.length);
13884
13901
  const rows = [];
13885
13902
  const push = (a, followUp) => {
13886
- const said = [a.chosen.join(", "), a.text].filter(Boolean).join(" \u2014 ");
13903
+ const said = answerPicksText(a.chosen, a.text);
13887
13904
  rows.push(`${followUp ? `${ANSWER_FOLLOW_UP_MARKER} ` : ""}\u201C${a.question}\u201D \u2192 ${indentAnswerContinuation(said || "(no answer)")}`);
13888
13905
  for (const child of a.followUps ?? []) push(child, true);
13889
13906
  };
@@ -14058,6 +14075,25 @@ function parseAgentMessage(text2) {
14058
14075
  if (!body.trim() || !from) return void 0;
14059
14076
  return { from, body };
14060
14077
  }
14078
+ function decodeXmlAttribute(value) {
14079
+ return value.replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
14080
+ }
14081
+ function parseCrossSessionMessage(text2) {
14082
+ let rest = text2.trim();
14083
+ const preamble = CROSS_SESSION_PREAMBLE.exec(rest);
14084
+ if (preamble) rest = rest.slice(preamble[0].length);
14085
+ const m = CROSS_SESSION_WRAPPER.exec(rest);
14086
+ if (!m) return void 0;
14087
+ const after = rest.slice(m[0].length).trim();
14088
+ if (after && !after.startsWith(CROSS_SESSION_GUIDANCE)) return void 0;
14089
+ const attributes = /* @__PURE__ */ new Map();
14090
+ for (const [, key, value] of m[1].matchAll(XML_ATTRIBUTE)) attributes.set(key, decodeXmlAttribute(value).trim());
14091
+ const body = m[2].trim();
14092
+ if (!body) return void 0;
14093
+ const name = attributes.get("from-name") || void 0;
14094
+ const socket = attributes.get("from") || void 0;
14095
+ return { ...name ? { name } : {}, ...socket ? { socket } : {}, body };
14096
+ }
14061
14097
  function wakeItemTail(item) {
14062
14098
  return `${item.at ? ` at ${item.at}` : ""}${item.url ? `: ${item.url}` : ""}`;
14063
14099
  }
@@ -14127,7 +14163,7 @@ function parseGithubWakeSteer(text2) {
14127
14163
  function frizzRoute(path3) {
14128
14164
  return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
14129
14165
  }
14130
- 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, ClaudeModel, CODEX_MODELS_FALLBACK, AcpAgent, ACP_MODEL_PREFIX, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, RETIRED_AWAITING_REPLACEMENT, AWAITING_KEY_RE, AWAITING_YAML_KEYS, AWAITING_SEQUENCE_KEYS, AWAITING_HINT_MAX, AWAITING_HINT_VALUE_MAX, AWAITING_SCALAR_KINDS, AWAITING_STEPS_MAX, AWAITING_STEP_VALUE_MAX, AWAITING_QUESTIONS_MAX, AWAITING_TITLE_MAX, AWAITING_STATUSES, AWAITING_ITEM_KINDS, AWAITING_DURATION_RE, DURATION_UNIT_MS, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, INTERRUPT_MARKERS, ALLDONE_SENTINEL, OPT_OUT_NOTE, OVER_QUESTION_NOTE, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TIMER_MAX_ARMED, TimerPromptText, PR_WATCH_SPENT_TRAILER, PR_WATCH_ARMED_TRAILER, SHELL_DONE_TRAILER, ISSUE_WATCH_SPENT_TRAILER, ISSUE_WATCH_ARMED_TRAILER, INTERRUPT_ENDED_LEAD, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, PARK_CORRECTION_NEEDS_INPUT_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, OWN_WATCH_MAX_ARMED, QUESTION_FENCE_RETIRED_AT, NEEDS_INPUT_REQUIRED_AT, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, SECRET_TTL_MS, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, ANSWER_CONTINUATION_INDENT, indentAnswerContinuation, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, 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, UpgradeThreadModelInput, UpgradeThreadModelResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, WAKE_TIME_HEADER_TAIL, WAKE_TRAILERS, HUMAN_GAP_FLOOR_MS, HUMAN_GAP_NOTE_TAIL, WAKE_DELIVERY_TOKEN_TAIL, WAKE_DELIVERY_TOKEN_LINE, NOISE_PREFIXES, AGENT_MESSAGE_WRAPPER, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REVIEW_LEAD, WAKE_BACKLOG_TAIL, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, WAKE_ITEM, WAKE_MORE, 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, ProjectRailCounts, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
14166
+ 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, ClaudeModel, CODEX_MODELS_FALLBACK, AcpAgent, ACP_MODEL_PREFIX, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, RETIRED_AWAITING_REPLACEMENT, AWAITING_KEY_RE, AWAITING_YAML_KEYS, AWAITING_SEQUENCE_KEYS, AWAITING_HINT_MAX, AWAITING_HINT_VALUE_MAX, AWAITING_SCALAR_KINDS, AWAITING_STEPS_MAX, AWAITING_STEP_VALUE_MAX, AWAITING_QUESTIONS_MAX, AWAITING_TITLE_MAX, AWAITING_STATUSES, AWAITING_ITEM_KINDS, AWAITING_DURATION_RE, DURATION_UNIT_MS, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, INTERRUPT_MARKERS, ALLDONE_SENTINEL, OPT_OUT_NOTE, OVER_QUESTION_NOTE, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TIMER_MAX_ARMED, TimerPromptText, PR_WATCH_SPENT_TRAILER, PR_WATCH_ARMED_TRAILER, SHELL_DONE_TRAILER, ISSUE_WATCH_SPENT_TRAILER, ISSUE_WATCH_ARMED_TRAILER, INTERRUPT_ENDED_LEAD, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, PARK_CORRECTION_NEEDS_INPUT_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, OWN_WATCH_MAX_ARMED, QUESTION_FENCE_RETIRED_AT, NEEDS_INPUT_REQUIRED_AT, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, SECRET_ANSWER_PREFIX, SECRET_TTL_MS, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, ANSWER_CONTINUATION_INDENT, indentAnswerContinuation, ANSWER_PICK_PREFIX, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, 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, UpgradeThreadModelInput, UpgradeThreadModelResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, WAKE_TIME_HEADER_TAIL, WAKE_TRAILERS, HUMAN_GAP_FLOOR_MS, HUMAN_GAP_NOTE_TAIL, WAKE_DELIVERY_TOKEN_TAIL, WAKE_DELIVERY_TOKEN_LINE, NOISE_PREFIXES, AGENT_MESSAGE_WRAPPER, CROSS_SESSION_PREAMBLE, CROSS_SESSION_WRAPPER, CROSS_SESSION_GUIDANCE, XML_ATTRIBUTE, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REVIEW_LEAD, WAKE_BACKLOG_TAIL, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, WAKE_ITEM, WAKE_MORE, 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, ProjectRailCounts, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
14131
14167
  var init_src = __esm({
14132
14168
  "packages/shared/src/index.ts"() {
14133
14169
  "use strict";
@@ -14826,6 +14862,7 @@ var init_src = __esm({
14826
14862
  * worker can prepare the command that reads it before the human has answered. */
14827
14863
  secretPath: external_exports.string().optional()
14828
14864
  }).strict();
14865
+ SECRET_ANSWER_PREFIX = "(secret ready at ";
14829
14866
  SECRET_TTL_MS = 15 * 6e4;
14830
14867
  AskResult = external_exports.object({
14831
14868
  registered: external_exports.array(RegisteredQuestionView),
@@ -14886,6 +14923,7 @@ var init_src = __esm({
14886
14923
  ANSWER_CONTINUATION_INDENT = " ";
14887
14924
  indentAnswerContinuation = (text2) => text2.replace(/\n/g, `
14888
14925
  ${ANSWER_CONTINUATION_INDENT}`);
14926
+ ANSWER_PICK_PREFIX = "- [x] ";
14889
14927
  DISMISSED_ANSWER = "(dismissed \u2014 decide it yourself; do not re-ask)";
14890
14928
  MarkOwnDoneInput = external_exports.object({
14891
14929
  slug: ThreadSlug,
@@ -15745,6 +15783,10 @@ ${DISPATCH_TASK_BANNER}
15745
15783
  WAKE_DELIVERY_TOKEN_LINE = /(?:^|\n)[ \t]*<!-- frizz-wake:[A-Za-z0-9_-]+ -->[ \t]*(?=\n|$)/g;
15746
15784
  NOISE_PREFIXES = ["<task-notification>", "[SYSTEM NOTIFICATION", "<system-reminder>", "<frizz-", "[frizz]"];
15747
15785
  AGENT_MESSAGE_WRAPPER = /^<agent-message from="([^"]*)">\n?([\s\S]*?)\n?<\/agent-message>\s*$/;
15786
+ CROSS_SESSION_PREAMBLE = /^Another Claude session sent a message(?: while you were working)?:\n/;
15787
+ CROSS_SESSION_WRAPPER = /^<cross-session-message\b([^>]*)>\n?([\s\S]*?)\n?<\/cross-session-message>/;
15788
+ CROSS_SESSION_GUIDANCE = "This came from another Claude session";
15789
+ XML_ATTRIBUTE = /([a-z][a-z-]*)="([^"]*)"/g;
15748
15790
  GithubWakeItem = external_exports.object({
15749
15791
  label: external_exports.string(),
15750
15792
  // the activity's noun ("comment", "approval", "change request", …)
@@ -16235,6 +16277,20 @@ ${DISPATCH_TASK_BANNER}
16235
16277
  // pairing, and `projectTranscriptPeerNames` uses this id to ask it. Never a drawer key on its own —
16236
16278
  // that is `peerDispatchId`, which the same pass can also supply once this resolves.
16237
16279
  peerSenderTaskId: external_exports.string().optional(),
16280
+ // ANOTHER TOP-LEVEL SESSION wrote this user turn — a thread messaging this one through Claude Code's
16281
+ // cross-session channel (see parseCrossSessionMessage), most often the coordinator thread that spawned
16282
+ // it. Not the human, and not a sub-agent either: the chat draws it as an expandable hairline naming
16283
+ // the sending thread, with the body one click away. `displayText` carries the unwrapped body.
16284
+ //
16285
+ // `name` is the sender's Claude Code session name, the fallback label. `thread` is the Frizz thread
16286
+ // behind it, filled in by the server from the sender's socket (session-peer.ts) — absent when the
16287
+ // sender is not a Frizz thread or is gone without having been resolved, and then the line names the
16288
+ // session without a link. `project` is the URL slug of the sender's project.
16289
+ sessionPeer: external_exports.object({
16290
+ name: external_exports.string().optional(),
16291
+ socket: external_exports.string().optional(),
16292
+ thread: external_exports.object({ project: external_exports.string().optional(), slug: external_exports.string(), title: external_exports.string() }).optional()
16293
+ }).optional(),
16238
16294
  // FRIZZ REFUSED the ```awaiting fence this message ends in — it named something that is not running,
16239
16295
  // or named nothing at all, or used a retired line kind (see `isParkCorrection`). The fence is not a
16240
16296
  // park, so the chat draws nothing for it: an hourglass card with a park button asserts a wait that
@@ -16831,7 +16887,7 @@ function summarizeLinkedPrs(nodes) {
16831
16887
  if (!Array.isArray(nodes)) return void 0;
16832
16888
  let count = 0;
16833
16889
  let open;
16834
- let merged;
16890
+ let merged2;
16835
16891
  for (const node of nodes) {
16836
16892
  const n = node;
16837
16893
  const number = typeof n?.number === "number" ? n.number : Number(n?.number);
@@ -16842,9 +16898,9 @@ function summarizeLinkedPrs(nodes) {
16842
16898
  const pr2 = { count: 0, number, url: typeof n?.url === "string" ? n.url : "", state };
16843
16899
  if (typeof n?.isDraft === "boolean") pr2.isDraft = n.isDraft;
16844
16900
  if (state === "OPEN") open ??= pr2;
16845
- else merged ??= pr2;
16901
+ else merged2 ??= pr2;
16846
16902
  }
16847
- const primary = open ?? merged;
16903
+ const primary = open ?? merged2;
16848
16904
  return primary ? { ...primary, count } : void 0;
16849
16905
  }
16850
16906
  function linkedPrQuery(numbers) {
@@ -17149,7 +17205,9 @@ function projectFromRegistryEntry(entry, home = homedir5()) {
17149
17205
  };
17150
17206
  }
17151
17207
  function resolveProject(cwd = process.cwd(), home = homedir5(), env = process.env) {
17152
- const identity = resolveProjectIdentity(resolveProjectDir(cwd), home);
17208
+ const root = resolveProjectDir(cwd);
17209
+ refuseWorldWritableProject(root);
17210
+ const identity = resolveProjectIdentity(root, home);
17153
17211
  const dir = identity.root;
17154
17212
  const id2 = registerAndReconcile(dir, identity.id, home);
17155
17213
  const stateDir = projectStateDir(id2, home);
@@ -17229,7 +17287,7 @@ import {
17229
17287
  readFileSync as readFileSync5,
17230
17288
  renameSync as renameSync4,
17231
17289
  rmSync as rmSync4,
17232
- statSync as statSync2,
17290
+ statSync as statSync3,
17233
17291
  writeFileSync as writeFileSync4
17234
17292
  } from "node:fs";
17235
17293
  import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute2, join as join7 } from "node:path";
@@ -17296,7 +17354,7 @@ function quarantine(path3, suffix) {
17296
17354
  }
17297
17355
  function pathAge(path3) {
17298
17356
  try {
17299
- return Date.now() - statSync2(path3).mtimeMs;
17357
+ return Date.now() - statSync3(path3).mtimeMs;
17300
17358
  } catch {
17301
17359
  return void 0;
17302
17360
  }
@@ -17927,7 +17985,7 @@ import {
17927
17985
  openSync as openSync5,
17928
17986
  readdirSync as readdirSync2,
17929
17987
  rmSync as rmSync5,
17930
- statSync as statSync3,
17988
+ statSync as statSync4,
17931
17989
  symlinkSync,
17932
17990
  writeFileSync as writeFileSync5,
17933
17991
  writeSync
@@ -17972,7 +18030,7 @@ function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Dat
17972
18030
  }
17973
18031
  const dated = entries2.map((name) => {
17974
18032
  try {
17975
- return { name, at: statSync3(join8(dir, name)).mtimeMs };
18033
+ return { name, at: statSync4(join8(dir, name)).mtimeMs };
17976
18034
  } catch {
17977
18035
  return { name, at: 0 };
17978
18036
  }
@@ -18101,6 +18159,69 @@ var init_logging = __esm({
18101
18159
  }
18102
18160
  });
18103
18161
 
18162
+ // packages/server/src/access-codes.ts
18163
+ import { randomBytes, timingSafeEqual, createHmac } from "node:crypto";
18164
+ import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
18165
+ import { dirname as dirname6, join as join9 } from "node:path";
18166
+ function describeDevice(userAgent) {
18167
+ const ua2 = userAgent ?? "";
18168
+ if (!ua2.trim()) return "unknown device";
18169
+ const os3 = /iPhone/i.test(ua2) ? "iPhone" : /iPad/i.test(ua2) ? "iPad" : /Android/i.test(ua2) ? "Android" : /Mac OS X|Macintosh/i.test(ua2) ? "macOS" : /Windows/i.test(ua2) ? "Windows" : /Linux/i.test(ua2) ? "Linux" : "unknown device";
18170
+ const browser = /Edg\//i.test(ua2) ? "Edge" : /OPR\/|Opera/i.test(ua2) ? "Opera" : /Firefox\//i.test(ua2) ? "Firefox" : /Chrome\//i.test(ua2) ? "Chrome" : /Safari\//i.test(ua2) ? "Safari" : "";
18171
+ return browser ? `${browser} on ${os3}` : os3;
18172
+ }
18173
+ function writeSessionKey(path3, key) {
18174
+ mkdirSync7(dirname6(path3), { recursive: true });
18175
+ writeFileSync6(path3, key, { mode: 384 });
18176
+ try {
18177
+ chmodSync(path3, 384);
18178
+ } catch {
18179
+ }
18180
+ }
18181
+ function rotateSessionKey(stateDir) {
18182
+ const key = randomBytes(32);
18183
+ writeSessionKey(join9(stateDir, SESSION_KEY_FILE), key);
18184
+ return key;
18185
+ }
18186
+ function readSessionEpoch(stateDir) {
18187
+ try {
18188
+ const parsed = JSON.parse(readFileSync6(join9(stateDir, SESSION_EPOCH_FILE), "utf8"));
18189
+ return Number.isInteger(parsed?.epoch) && parsed.epoch >= 0 ? parsed.epoch : 0;
18190
+ } catch {
18191
+ return 0;
18192
+ }
18193
+ }
18194
+ function advanceSessionEpoch(stateDir, epoch = SESSION_EPOCH) {
18195
+ const from = readSessionEpoch(stateDir);
18196
+ if (from >= epoch) return { advanced: false, epoch: from };
18197
+ const keyPath = join9(stateDir, SESSION_KEY_FILE);
18198
+ const rotatedKey = existsSync4(keyPath);
18199
+ if (rotatedKey) rotateSessionKey(stateDir);
18200
+ mkdirSync7(stateDir, { recursive: true });
18201
+ writeFileSync6(
18202
+ join9(stateDir, SESSION_EPOCH_FILE),
18203
+ JSON.stringify({ epoch, advancedAt: (/* @__PURE__ */ new Date()).toISOString() }),
18204
+ { mode: 384 }
18205
+ );
18206
+ return { advanced: true, from, to: epoch, rotatedKey };
18207
+ }
18208
+ function sessionEpochLogLine(advance, signedOut) {
18209
+ if (!advance.advanced || !advance.rotatedKey) return null;
18210
+ const count = signedOut === null ? "an unknown number of" : String(signedOut);
18211
+ return `session epoch ${advance.from} -> ${advance.to}: rotated the session key and signed out ${count} recorded remote device(s); every older remote session is refused`;
18212
+ }
18213
+ var DEFAULT_CODE_TTL_MS, DEFAULT_SESSION_TTL_MS, SESSION_EPOCH, SESSION_KEY_FILE, SESSION_EPOCH_FILE;
18214
+ var init_access_codes = __esm({
18215
+ "packages/server/src/access-codes.ts"() {
18216
+ "use strict";
18217
+ DEFAULT_CODE_TTL_MS = 5 * 6e4;
18218
+ DEFAULT_SESSION_TTL_MS = 30 * 24 * 60 * 6e4;
18219
+ SESSION_EPOCH = 1;
18220
+ SESSION_KEY_FILE = "session-key";
18221
+ SESSION_EPOCH_FILE = "session-epoch";
18222
+ }
18223
+ });
18224
+
18104
18225
  // packages/server/src/local-origin.ts
18105
18226
  import { isIP } from "node:net";
18106
18227
  function publicAuthority(policy) {
@@ -18248,7 +18369,7 @@ var init_local_path = __esm({
18248
18369
  });
18249
18370
 
18250
18371
  // packages/server/src/local-image.ts
18251
- import { createReadStream, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
18372
+ import { createReadStream, realpathSync as realpathSync5, statSync as statSync5 } from "node:fs";
18252
18373
  import { extname, isAbsolute as isAbsolute3 } from "node:path";
18253
18374
  function resolveLocalImage(rawPath, range) {
18254
18375
  if (rawPath) rawPath = normalizeLocalPath(rawPath);
@@ -18263,7 +18384,7 @@ function resolveLocalImage(rawPath, range) {
18263
18384
  }
18264
18385
  let size;
18265
18386
  try {
18266
- const stat2 = statSync4(real);
18387
+ const stat2 = statSync5(real);
18267
18388
  if (!stat2.isFile()) return { status: 404 };
18268
18389
  size = stat2.size;
18269
18390
  } catch {
@@ -18324,6 +18445,163 @@ var init_local_image = __esm({
18324
18445
  }
18325
18446
  });
18326
18447
 
18448
+ // packages/server/src/audit.ts
18449
+ import { AsyncLocalStorage } from "node:async_hooks";
18450
+ import { createHash as createHash5 } from "node:crypto";
18451
+ import { appendFileSync as appendFileSync2, chmodSync as chmodSync2, mkdirSync as mkdirSync8 } from "node:fs";
18452
+ import { dirname as dirname7, join as join10 } from "node:path";
18453
+ function bounded(value) {
18454
+ return typeof value === "string" && value.length > 0 ? value.slice(0, STAMP_FIELD_MAX) : void 0;
18455
+ }
18456
+ function decodeCallerStamp(value) {
18457
+ if (!value) return void 0;
18458
+ try {
18459
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
18460
+ if (!parsed || typeof parsed !== "object" || !VIAS.includes(parsed.via)) return void 0;
18461
+ const host = bounded(parsed.host);
18462
+ const clientIp = bounded(parsed.clientIp);
18463
+ return {
18464
+ via: parsed.via,
18465
+ remote: bounded(parsed.remote) ?? null,
18466
+ ...host ? { host } : {},
18467
+ ...clientIp ? { clientIp } : {},
18468
+ session: bounded(parsed.session) ?? null
18469
+ };
18470
+ } catch {
18471
+ return void 0;
18472
+ }
18473
+ }
18474
+ function requestOriginFrom(input) {
18475
+ const device = describeDevice(input.userAgent);
18476
+ const stamp = decodeCallerStamp(input.caller);
18477
+ if (stamp) return { ...stamp, device };
18478
+ return { via: "direct", remote: input.socketRemote ?? null, session: null, device };
18479
+ }
18480
+ function withRequestOrigin(origin, fn2) {
18481
+ return requestOrigin.run(origin, fn2);
18482
+ }
18483
+ function currentRequestOrigin() {
18484
+ return requestOrigin.getStore() ?? { via: "internal" };
18485
+ }
18486
+ function textDigest(text2) {
18487
+ return { chars: text2.length, sha256: createHash5("sha256").update(text2).digest("hex") };
18488
+ }
18489
+ function auditLogPathForStateDir(stateDir) {
18490
+ return join10(dirname7(serverAddressPathForStateDir(stateDir)), "logs", "audit.jsonl");
18491
+ }
18492
+ function fileAuditLog(path3) {
18493
+ let secured = false;
18494
+ return {
18495
+ append(record) {
18496
+ mkdirSync8(dirname7(path3), { recursive: true, mode: 448 });
18497
+ appendFileSync2(path3, `${JSON.stringify(record)}
18498
+ `, { mode: 384 });
18499
+ if (!secured) {
18500
+ try {
18501
+ chmodSync2(path3, 384);
18502
+ } catch {
18503
+ }
18504
+ secured = true;
18505
+ }
18506
+ }
18507
+ };
18508
+ }
18509
+ function originSummary(origin) {
18510
+ if (origin.via === "internal") return "internal";
18511
+ const parts = [origin.via];
18512
+ if (origin.host) parts.push(origin.host);
18513
+ if (origin.remote) parts.push(`from ${origin.remote}`);
18514
+ if (origin.clientIp) parts.push(`client ${origin.clientIp}`);
18515
+ if (origin.session) parts.push(`session ${origin.session}`);
18516
+ parts.push(`(${origin.device})`);
18517
+ return parts.join(" ");
18518
+ }
18519
+ function auditSummary(record) {
18520
+ const target = [record.project.dir ?? record.project.id, record.thread].filter(Boolean).join(" ");
18521
+ const outcome = record.outcome === "ok" ? "ok" : `refused: ${record.error ?? "unknown error"}`;
18522
+ return `${record.action} ${outcome}${target ? ` \u2014 ${target}` : ""} \u2014 via ${originSummary(record.origin)}`;
18523
+ }
18524
+ function recordAudit(log3, build) {
18525
+ let record;
18526
+ try {
18527
+ const { action, outcome, error, project, thread, servedBy, detail: detail2 } = build();
18528
+ record = {
18529
+ at: (/* @__PURE__ */ new Date()).toISOString(),
18530
+ action,
18531
+ outcome,
18532
+ ...error !== void 0 ? { error } : {},
18533
+ project,
18534
+ ...thread !== void 0 ? { thread } : {},
18535
+ origin: currentRequestOrigin(),
18536
+ ...servedBy !== void 0 ? { servedBy } : {},
18537
+ ...detail2 !== void 0 ? { detail: detail2 } : {}
18538
+ };
18539
+ log.info("audit", auditSummary(record));
18540
+ log3?.append(record);
18541
+ } catch (error) {
18542
+ try {
18543
+ const what = record ? `${record.action} (${record.outcome})` : "an action";
18544
+ log.error("audit", `could not record ${what}: ${error instanceof Error ? error.message : String(error)}`);
18545
+ } catch {
18546
+ }
18547
+ }
18548
+ }
18549
+ function merged(base, extra) {
18550
+ return {
18551
+ project: extra?.project ?? base.project,
18552
+ thread: extra?.thread ?? base.thread,
18553
+ detail: base.detail || extra?.detail ? { ...base.detail, ...extra?.detail } : void 0
18554
+ };
18555
+ }
18556
+ async function audited(log3, action, base, run3, options = {}) {
18557
+ let fields = {};
18558
+ let unreadable;
18559
+ try {
18560
+ fields = typeof base === "function" ? base() : base;
18561
+ } catch (error) {
18562
+ unreadable = error;
18563
+ }
18564
+ const entry = (from) => ({
18565
+ project: from.project ?? { id: null, dir: null },
18566
+ ...from.thread ? { thread: from.thread } : {},
18567
+ ...options.servedBy ? { servedBy: options.servedBy } : {},
18568
+ ...from.detail || unreadable !== void 0 ? { detail: { ...unreadable !== void 0 ? { auditError: messageOf(unreadable) } : {}, ...from.detail } } : {}
18569
+ });
18570
+ let result;
18571
+ try {
18572
+ result = await run3();
18573
+ } catch (error) {
18574
+ recordAudit(log3, () => ({ action, outcome: "refused", error: messageOf(error), ...entry(fields) }));
18575
+ throw error;
18576
+ }
18577
+ recordAudit(log3, () => {
18578
+ let extra;
18579
+ try {
18580
+ extra = options.onResult?.(result);
18581
+ } catch (error) {
18582
+ extra = { detail: { auditError: messageOf(error) } };
18583
+ }
18584
+ return { action, outcome: "ok", ...entry(merged(fields, extra)) };
18585
+ });
18586
+ return result;
18587
+ }
18588
+ function messageOf(error) {
18589
+ return error instanceof Error ? error.message : String(error);
18590
+ }
18591
+ var CALLER_HEADER, VIAS, STAMP_FIELD_MAX, requestOrigin;
18592
+ var init_audit = __esm({
18593
+ "packages/server/src/audit.ts"() {
18594
+ "use strict";
18595
+ init_access_codes();
18596
+ init_frizz_paths();
18597
+ init_logging();
18598
+ CALLER_HEADER = "x-frizz-caller";
18599
+ VIAS = ["public", "network", "loopback"];
18600
+ STAMP_FIELD_MAX = 200;
18601
+ requestOrigin = new AsyncLocalStorage();
18602
+ }
18603
+ });
18604
+
18327
18605
  // packages/server/src/bus.ts
18328
18606
  var Bus, Emitter;
18329
18607
  var init_bus = __esm({
@@ -21468,13 +21746,13 @@ var init_storage = __esm({
21468
21746
 
21469
21747
  // packages/server/src/secret-files.ts
21470
21748
  import { execFileSync as execFileSync4 } from "node:child_process";
21471
- import { chmodSync as chmodSync2, closeSync as closeSync5, constants, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync8, openSync as openSync6, readdirSync as readdirSync3, rmdirSync, rmSync as rmSync6, writeFileSync as writeFileSync7, writeSync as writeSync2 } from "node:fs";
21472
- import { dirname as dirname7, join as join11 } from "node:path";
21749
+ import { chmodSync as chmodSync3, closeSync as closeSync5, constants, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync9, openSync as openSync6, readdirSync as readdirSync3, rmdirSync, rmSync as rmSync6, writeFileSync as writeFileSync7, writeSync as writeSync2 } from "node:fs";
21750
+ import { dirname as dirname8, join as join12 } from "node:path";
21473
21751
  function secretsRoot(stateDir) {
21474
- return join11(stateDir, "secrets");
21752
+ return join12(stateDir, "secrets");
21475
21753
  }
21476
21754
  function secretFilePath(stateDir, slug, questionId) {
21477
- return join11(secretsRoot(stateDir), slug, questionId);
21755
+ return join12(secretsRoot(stateDir), slug, questionId);
21478
21756
  }
21479
21757
  function stop(path3) {
21480
21758
  const entry = served.get(path3);
@@ -21482,7 +21760,7 @@ function stop(path3) {
21482
21760
  served.delete(path3);
21483
21761
  rmSync6(path3, { force: true });
21484
21762
  try {
21485
- rmdirSync(dirname7(path3));
21763
+ rmdirSync(dirname8(path3));
21486
21764
  } catch {
21487
21765
  }
21488
21766
  }
@@ -21490,12 +21768,12 @@ function serveSecret(stateDir, slug, questionId, value, opts = {}) {
21490
21768
  const now = opts.now ?? Date.now;
21491
21769
  const expiresAt = now() + (opts.ttlMs ?? SECRET_TTL_MS);
21492
21770
  const root = secretsRoot(stateDir);
21493
- const dir = join11(root, slug);
21771
+ const dir = join12(root, slug);
21494
21772
  const path3 = secretFilePath(stateDir, slug, questionId);
21495
21773
  stop(path3);
21496
- mkdirSync8(dir, { recursive: true, mode: 448 });
21497
- chmodSync2(root, 448);
21498
- chmodSync2(dir, 448);
21774
+ mkdirSync9(dir, { recursive: true, mode: 448 });
21775
+ chmodSync3(root, 448);
21776
+ chmodSync3(dir, 448);
21499
21777
  if ((opts.platform ?? process.platform) === "win32") {
21500
21778
  writeFileSync7(path3, value, { mode: 384 });
21501
21779
  const timer2 = setInterval(() => {
@@ -21538,7 +21816,7 @@ function clearSecrets(stateDir) {
21538
21816
  return 0;
21539
21817
  }
21540
21818
  for (const thread of threads) {
21541
- const dir = join11(root, thread);
21819
+ const dir = join12(root, thread);
21542
21820
  let entries2;
21543
21821
  try {
21544
21822
  if (!lstatSync(dir).isDirectory()) {
@@ -21550,7 +21828,7 @@ function clearSecrets(stateDir) {
21550
21828
  continue;
21551
21829
  }
21552
21830
  for (const entry of entries2) {
21553
- const path3 = join11(dir, entry);
21831
+ const path3 = join12(dir, entry);
21554
21832
  if (served.has(path3)) continue;
21555
21833
  rmSync6(path3, { force: true });
21556
21834
  removed++;
@@ -21573,10 +21851,10 @@ var init_secret_files = __esm({
21573
21851
  });
21574
21852
 
21575
21853
  // packages/server/src/machine-config.ts
21576
- import { closeSync as closeSync6, fsyncSync as fsyncSync5, mkdirSync as mkdirSync9, openSync as openSync7, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
21577
- import { dirname as dirname8, join as join12 } from "node:path";
21854
+ import { closeSync as closeSync6, fsyncSync as fsyncSync5, mkdirSync as mkdirSync10, openSync as openSync7, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
21855
+ import { dirname as dirname9, join as join13 } from "node:path";
21578
21856
  function machineConfigPath(home) {
21579
- return join12(frizzPaths({ home }).data, "config.json");
21857
+ return join13(frizzPaths({ home }).data, "config.json");
21580
21858
  }
21581
21859
  function readAll(home) {
21582
21860
  let raw2;
@@ -21589,7 +21867,7 @@ function readAll(home) {
21589
21867
  }
21590
21868
  function writeAll(home, all) {
21591
21869
  const path3 = machineConfigPath(home);
21592
- mkdirSync9(dirname8(path3), { recursive: true });
21870
+ mkdirSync10(dirname9(path3), { recursive: true });
21593
21871
  const temp = `${path3}.${process.pid}.tmp`;
21594
21872
  let fd2;
21595
21873
  try {
@@ -21635,9 +21913,9 @@ var init_machine_config = __esm({
21635
21913
 
21636
21914
  // packages/server/src/settings.ts
21637
21915
  import { readFileSync as readFileSync9, rmSync as rmSync8 } from "node:fs";
21638
- import { join as join13 } from "node:path";
21916
+ import { join as join14 } from "node:path";
21639
21917
  function legacyMachineSettingsPath(home) {
21640
- return join13(frizzPaths({ home }).data, "settings.json");
21918
+ return join14(frizzPaths({ home }).data, "settings.json");
21641
21919
  }
21642
21920
  function pickMachine(settings) {
21643
21921
  return Object.fromEntries(MACHINE_KEYS.map((key) => [key, settings[key]]));
@@ -21820,15 +22098,15 @@ function redactUserInfoToken(raw2, options) {
21820
22098
  return `${parts.username}${parts.delimiter}${replacement(options, parts.password)}`;
21821
22099
  }
21822
22100
  function hasCurlCommandContext(source, offset) {
21823
- const bounded = source.slice(Math.max(0, offset - 1024), offset);
22101
+ const bounded2 = source.slice(Math.max(0, offset - 1024), offset);
21824
22102
  const separator = Math.max(
21825
- bounded.lastIndexOf("\n"),
21826
- bounded.lastIndexOf("\r"),
21827
- bounded.lastIndexOf(";"),
21828
- bounded.lastIndexOf("&"),
21829
- bounded.lastIndexOf("|")
22103
+ bounded2.lastIndexOf("\n"),
22104
+ bounded2.lastIndexOf("\r"),
22105
+ bounded2.lastIndexOf(";"),
22106
+ bounded2.lastIndexOf("&"),
22107
+ bounded2.lastIndexOf("|")
21830
22108
  );
21831
- const segment = bounded.slice(separator + 1);
22109
+ const segment = bounded2.slice(separator + 1);
21832
22110
  return segment.split(/[ \t]+/u).some((token) => CURL_EXECUTABLE_RE.test(unquote(token)));
21833
22111
  }
21834
22112
  function redactCredentialSyntax(raw2, options = {}) {
@@ -21978,8 +22256,8 @@ var init_codex_error = __esm({
21978
22256
  });
21979
22257
 
21980
22258
  // packages/server/src/discover.ts
21981
- import { readdirSync as readdirSync4, statSync as statSync5, openSync as openSync8, readSync, closeSync as closeSync7 } from "node:fs";
21982
- import { dirname as dirname9, join as join14 } from "node:path";
22259
+ import { readdirSync as readdirSync4, statSync as statSync6, openSync as openSync8, readSync, closeSync as closeSync7 } from "node:fs";
22260
+ import { dirname as dirname10, join as join15 } from "node:path";
21983
22261
  function sentinelFor(sessionId) {
21984
22262
  return `threads/${sessionId}/`;
21985
22263
  }
@@ -22016,10 +22294,10 @@ function discoverTranscriptId(logDir, sessionId, opts = {}) {
22016
22294
  if (name.startsWith(".") || !name.endsWith(".jsonl")) continue;
22017
22295
  const id2 = name.slice(0, -".jsonl".length);
22018
22296
  if (!id2 || id2 === sessionId || exclude?.has(id2)) continue;
22019
- const path3 = join14(logDir, name);
22297
+ const path3 = join15(logDir, name);
22020
22298
  let mtime;
22021
22299
  try {
22022
- mtime = statSync5(path3).mtimeMs;
22300
+ mtime = statSync6(path3).mtimeMs;
22023
22301
  } catch {
22024
22302
  continue;
22025
22303
  }
@@ -22034,7 +22312,7 @@ function discoverTranscriptId(logDir, sessionId, opts = {}) {
22034
22312
  }
22035
22313
  function mtimeOfNonEmpty(path3) {
22036
22314
  try {
22037
- const st2 = statSync5(path3, { throwIfNoEntry: false });
22315
+ const st2 = statSync6(path3, { throwIfNoEntry: false });
22038
22316
  return st2 && st2.size > 0 ? st2.mtimeMs : void 0;
22039
22317
  } catch {
22040
22318
  return void 0;
@@ -22044,7 +22322,7 @@ function discoverTranscriptDir(logDir, sessionId, memo3 = strandedLogDirs) {
22044
22322
  const name = `${sessionId}.jsonl`;
22045
22323
  const better = (best2, dir) => {
22046
22324
  if (dir === logDir) return best2;
22047
- const at2 = mtimeOfNonEmpty(join14(dir, name));
22325
+ const at2 = mtimeOfNonEmpty(join15(dir, name));
22048
22326
  if (at2 === void 0) return best2;
22049
22327
  return !best2 || at2 > best2.mtimeMs ? { dir, mtimeMs: at2 } : best2;
22050
22328
  };
@@ -22053,11 +22331,11 @@ function discoverTranscriptDir(logDir, sessionId, memo3 = strandedLogDirs) {
22053
22331
  if (best) return best.dir;
22054
22332
  let entries2;
22055
22333
  try {
22056
- entries2 = readdirSync4(dirname9(logDir), { withFileTypes: true }).filter((e14) => e14.isDirectory()).map((e14) => e14.name);
22334
+ entries2 = readdirSync4(dirname10(logDir), { withFileTypes: true }).filter((e14) => e14.isDirectory()).map((e14) => e14.name);
22057
22335
  } catch {
22058
22336
  return void 0;
22059
22337
  }
22060
- for (const entry of entries2) best = better(best, join14(dirname9(logDir), entry));
22338
+ for (const entry of entries2) best = better(best, join15(dirname10(logDir), entry));
22061
22339
  if (!best) return void 0;
22062
22340
  memo3.add(best.dir);
22063
22341
  return best.dir;
@@ -22143,22 +22421,22 @@ var init_types2 = __esm({
22143
22421
  });
22144
22422
 
22145
22423
  // packages/server/src/session-files.ts
22146
- import { chmodSync as chmodSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync10, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "node:fs";
22147
- import { join as join15 } from "node:path";
22424
+ import { chmodSync as chmodSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync11, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "node:fs";
22425
+ import { join as join16 } from "node:path";
22148
22426
  function systemPromptPath(sessionId) {
22149
22427
  if (!SESSION_ID_RE.test(sessionId)) throw new Error("invalid session id");
22150
- return join15(SYSTEM_PROMPT_DIR, `${sessionId}.md`);
22428
+ return join16(SYSTEM_PROMPT_DIR, `${sessionId}.md`);
22151
22429
  }
22152
22430
  function mcpConfigPath(sessionId) {
22153
22431
  if (!SESSION_ID_RE.test(sessionId)) throw new Error("invalid session id");
22154
- return join15(MCP_CONFIG_DIR, `${sessionId}.json`);
22432
+ return join16(MCP_CONFIG_DIR, `${sessionId}.json`);
22155
22433
  }
22156
22434
  function writeMcpConfigFile(sessionId, mcpServers) {
22157
22435
  const path3 = mcpConfigPath(sessionId);
22158
- mkdirSync10(MCP_CONFIG_DIR, { recursive: true, mode: 448 });
22159
- chmodSync3(MCP_CONFIG_DIR, 448);
22436
+ mkdirSync11(MCP_CONFIG_DIR, { recursive: true, mode: 448 });
22437
+ chmodSync4(MCP_CONFIG_DIR, 448);
22160
22438
  writeFileSync9(path3, JSON.stringify({ mcpServers }), { mode: 384 });
22161
- chmodSync3(path3, 384);
22439
+ chmodSync4(path3, 384);
22162
22440
  return path3;
22163
22441
  }
22164
22442
  function isDirectDirectory(path3) {
@@ -22179,7 +22457,7 @@ function pathAbsent(path3) {
22179
22457
  }
22180
22458
  function unlinkDirectChild(parent, filename) {
22181
22459
  if (!isDirectDirectory(parent)) return pathAbsent(parent);
22182
- const child = join15(parent, filename);
22460
+ const child = join16(parent, filename);
22183
22461
  try {
22184
22462
  rmSync9(child, { force: true });
22185
22463
  } catch {
@@ -22189,12 +22467,12 @@ function unlinkDirectChild(parent, filename) {
22189
22467
  }
22190
22468
  function cleanupAdoptionSessionFiles(projectDir, sessionId) {
22191
22469
  if (!SESSION_ID_RE.test(sessionId)) return false;
22192
- const frizzDir = join15(projectDir, ".frizz");
22470
+ const frizzDir = join16(projectDir, ".frizz");
22193
22471
  let clean = true;
22194
22472
  if (isDirectDirectory(frizzDir)) {
22195
- const threads = join15(frizzDir, "threads");
22473
+ const threads = join16(frizzDir, "threads");
22196
22474
  if (isDirectDirectory(threads)) {
22197
- const child = join15(threads, sessionId);
22475
+ const child = join16(threads, sessionId);
22198
22476
  try {
22199
22477
  rmSync9(child, { recursive: true, force: true });
22200
22478
  } catch {
@@ -22448,10 +22726,10 @@ var init_adoption_recovery = __esm({
22448
22726
  });
22449
22727
 
22450
22728
  // packages/server/src/backend/codex-models.ts
22451
- import { join as join16 } from "node:path";
22729
+ import { join as join17 } from "node:path";
22452
22730
  import { readFileSync as readFileSync10 } from "node:fs";
22453
22731
  function cachePath(codexHome) {
22454
- return join16(codexHome, "models_cache.json");
22732
+ return join17(codexHome, "models_cache.json");
22455
22733
  }
22456
22734
  function toCodexModel(raw2) {
22457
22735
  if (!raw2 || typeof raw2 !== "object") return void 0;
@@ -23182,10 +23460,10 @@ var init_claude_agent_sdk_protocol = __esm({
23182
23460
  });
23183
23461
 
23184
23462
  // packages/server/src/backend/claude-broker-diagnostics.ts
23185
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync6, statSync as statSync6 } from "node:fs";
23186
- import { join as join17 } from "node:path";
23463
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync12, readFileSync as readFileSync11, renameSync as renameSync6, statSync as statSync7 } from "node:fs";
23464
+ import { join as join18 } from "node:path";
23187
23465
  function claudeBrokerDiagnosticLogPath(stateDir, sessionId) {
23188
- return join17(stateDir, "claude-broker", `${sessionId}.diagnostics.log`);
23466
+ return join18(stateDir, "claude-broker", `${sessionId}.diagnostics.log`);
23189
23467
  }
23190
23468
  function readClaudeBrokerExit(stateDir, sessionId, generation) {
23191
23469
  if (!generation) return null;
@@ -23290,10 +23568,10 @@ var init_daemon_tree = __esm({
23290
23568
  });
23291
23569
 
23292
23570
  // packages/server/src/backend/ipc-path.ts
23293
- import { join as join18 } from "node:path";
23571
+ import { join as join19 } from "node:path";
23294
23572
  function frizzIpcPath(name) {
23295
23573
  if (process.platform === "win32") return `\\\\.\\pipe\\${name}`;
23296
- return join18(process.env.TMPDIR ?? "/tmp", `${name}.sock`);
23574
+ return join19(process.env.TMPDIR ?? "/tmp", `${name}.sock`);
23297
23575
  }
23298
23576
  var init_ipc_path = __esm({
23299
23577
  "packages/server/src/backend/ipc-path.ts"() {
@@ -23303,9 +23581,9 @@ var init_ipc_path = __esm({
23303
23581
 
23304
23582
  // packages/server/src/backend/claude-broker-host.ts
23305
23583
  import { spawn } from "node:child_process";
23306
- import { createHash as createHash5, randomUUID as randomUUID8 } from "node:crypto";
23307
- import { accessSync, constants as fsConstants, mkdirSync as mkdirSync12, readFileSync as readFileSync12, readdirSync as readdirSync5, unlinkSync, writeFileSync as writeFileSync10 } from "node:fs";
23308
- import { delimiter, dirname as dirname10, isAbsolute as isAbsolute4, join as join19 } from "node:path";
23584
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
23585
+ import { accessSync, constants as fsConstants, mkdirSync as mkdirSync13, readFileSync as readFileSync12, readdirSync as readdirSync5, unlinkSync, writeFileSync as writeFileSync10 } from "node:fs";
23586
+ import { delimiter, dirname as dirname11, isAbsolute as isAbsolute4, join as join20 } from "node:path";
23309
23587
  function windowsShimTarget(shimPath) {
23310
23588
  let body;
23311
23589
  try {
@@ -23315,7 +23593,7 @@ function windowsShimTarget(shimPath) {
23315
23593
  }
23316
23594
  const target = WINDOWS_SHIM_TARGET.exec(body)?.[1];
23317
23595
  if (!target) return void 0;
23318
- const full = join19(dirname10(shimPath), target);
23596
+ const full = join20(dirname11(shimPath), target);
23319
23597
  try {
23320
23598
  accessSync(full, fsConstants.F_OK);
23321
23599
  return full;
@@ -23336,17 +23614,17 @@ function resolveClaudeExecutableAbsolute(bin, env = process.env) {
23336
23614
  for (const dir of searchPath(env).split(delimiter)) {
23337
23615
  if (!dir) continue;
23338
23616
  if (windows) {
23339
- const exe2 = join19(dir, `${candidate}.exe`);
23617
+ const exe2 = join20(dir, `${candidate}.exe`);
23340
23618
  try {
23341
23619
  accessSync(exe2, fsConstants.F_OK);
23342
23620
  return exe2;
23343
23621
  } catch {
23344
23622
  }
23345
- const viaShim = windowsShimTarget(join19(dir, `${candidate}.cmd`));
23623
+ const viaShim = windowsShimTarget(join20(dir, `${candidate}.cmd`));
23346
23624
  if (viaShim) return viaShim;
23347
23625
  continue;
23348
23626
  }
23349
- const full = join19(dir, candidate);
23627
+ const full = join20(dir, candidate);
23350
23628
  try {
23351
23629
  accessSync(full, fsConstants.X_OK);
23352
23630
  return full;
@@ -23356,22 +23634,22 @@ function resolveClaudeExecutableAbsolute(bin, env = process.env) {
23356
23634
  throw new Error(`Claude session broker: could not resolve '${candidate}' to an absolute executable path on PATH (the SDK requires one)`);
23357
23635
  }
23358
23636
  function claudeBrokerSocketPath(stateDir, sessionId) {
23359
- const key = createHash5("sha256").update(stateDir).update("\0").update(sessionId).digest("hex").slice(0, 16);
23637
+ const key = createHash6("sha256").update(stateDir).update("\0").update(sessionId).digest("hex").slice(0, 16);
23360
23638
  return frizzIpcPath(`frizz-claude-${key}`);
23361
23639
  }
23362
23640
  function claudeBrokerRecordPath(stateDir, sessionId) {
23363
- const key = createHash5("sha256").update(sessionId).digest("hex").slice(0, 16);
23364
- return join19(stateDir, "claude-broker", `${key}.json`);
23641
+ const key = createHash6("sha256").update(sessionId).digest("hex").slice(0, 16);
23642
+ return join20(stateDir, "claude-broker", `${key}.json`);
23365
23643
  }
23366
23644
  function claudeBrokerRetirementPath(stateDir, sessionId) {
23367
- const key = createHash5("sha256").update(sessionId).digest("hex").slice(0, 16);
23368
- return join19(stateDir, "claude-broker", `${key}.retired`);
23645
+ const key = createHash6("sha256").update(sessionId).digest("hex").slice(0, 16);
23646
+ return join20(stateDir, "claude-broker", `${key}.retired`);
23369
23647
  }
23370
23648
  function markBrokerRetired(stateDir, sessionId, reason, generation) {
23371
23649
  const mark = { at: (/* @__PURE__ */ new Date()).toISOString(), generation, reason };
23372
23650
  try {
23373
23651
  const path3 = claudeBrokerRetirementPath(stateDir, sessionId);
23374
- mkdirSync12(dirname10(path3), { recursive: true });
23652
+ mkdirSync13(dirname11(path3), { recursive: true });
23375
23653
  writeFileSync10(path3, JSON.stringify(mark));
23376
23654
  } catch {
23377
23655
  }
@@ -23432,7 +23710,7 @@ function liveBrokerRecord(recordPath2) {
23432
23710
  return null;
23433
23711
  }
23434
23712
  function liveBrokerRecords(stateDir) {
23435
- const dir = join19(stateDir, "claude-broker");
23713
+ const dir = join20(stateDir, "claude-broker");
23436
23714
  let names;
23437
23715
  try {
23438
23716
  names = readdirSync5(dir);
@@ -23442,7 +23720,7 @@ function liveBrokerRecords(stateDir) {
23442
23720
  const out = [];
23443
23721
  for (const name of names) {
23444
23722
  if (!name.endsWith(".json")) continue;
23445
- const record = liveBrokerRecord(join19(dir, name));
23723
+ const record = liveBrokerRecord(join20(dir, name));
23446
23724
  if (record) out.push(record);
23447
23725
  }
23448
23726
  return out;
@@ -23450,7 +23728,7 @@ function liveBrokerRecords(stateDir) {
23450
23728
  function forkBroker(options) {
23451
23729
  const socketPath = claudeBrokerSocketPath(options.stateDir, options.sessionId);
23452
23730
  const recordPath2 = claudeBrokerRecordPath(options.stateDir, options.sessionId);
23453
- mkdirSync12(dirname10(recordPath2), { recursive: true });
23731
+ mkdirSync13(dirname11(recordPath2), { recursive: true });
23454
23732
  const generation = randomUUID8();
23455
23733
  const config = {
23456
23734
  socketPath,
@@ -24060,7 +24338,7 @@ var init_delivery_ledger = __esm({
24060
24338
  });
24061
24339
 
24062
24340
  // packages/server/src/codex-subagents.ts
24063
- import { closeSync as closeSync8, openSync as openSync9, readSync as readSync2, statSync as statSync7 } from "node:fs";
24341
+ import { closeSync as closeSync8, openSync as openSync9, readSync as readSync2, statSync as statSync8 } from "node:fs";
24064
24342
  function createCodexSubAgentTracker(deps) {
24065
24343
  const findRollouts = deps.findRollouts ?? ((ids) => findRolloutsByIds(ids, deps.codexHome));
24066
24344
  const readAppended = deps.readAppended ?? defaultReadAppended;
@@ -24252,7 +24530,7 @@ function agentLabel(path3) {
24252
24530
  function defaultReadAppended(path3, offset) {
24253
24531
  let size;
24254
24532
  try {
24255
- size = statSync7(path3).size;
24533
+ size = statSync8(path3).size;
24256
24534
  } catch {
24257
24535
  return void 0;
24258
24536
  }
@@ -24393,16 +24671,16 @@ var init_completion_relay = __esm({
24393
24671
  });
24394
24672
 
24395
24673
  // packages/server/src/tail-cache.ts
24396
- import { createHash as createHash6 } from "node:crypto";
24397
- import { closeSync as closeSync9, openSync as openSync10, readFileSync as readFileSync13, readSync as readSync3, statSync as statSync8 } from "node:fs";
24398
- import { join as join20 } from "node:path";
24674
+ import { createHash as createHash7 } from "node:crypto";
24675
+ import { closeSync as closeSync9, openSync as openSync10, readFileSync as readFileSync13, readSync as readSync3, statSync as statSync9 } from "node:fs";
24676
+ import { join as join21 } from "node:path";
24399
24677
  function foldSchemaDigest(dir = import.meta.dirname) {
24400
24678
  if (foldSchemaMemo) return foldSchemaMemo;
24401
- const hash = createHash6("sha256").update("frizz-tail-state-v1\0");
24679
+ const hash = createHash7("sha256").update("frizz-tail-state-v1\0");
24402
24680
  let read = 0;
24403
24681
  for (const name of FOLD_SOURCES) {
24404
24682
  try {
24405
- hash.update(name).update("\0").update(readFileSync13(join20(dir, name)));
24683
+ hash.update(name).update("\0").update(readFileSync13(join21(dir, name)));
24406
24684
  read++;
24407
24685
  } catch {
24408
24686
  hash.update(name).update("\0missing\0");
@@ -24432,7 +24710,7 @@ function measureFence(path3, offset) {
24432
24710
  if (!Number.isSafeInteger(offset) || offset <= 0) return null;
24433
24711
  let fd2;
24434
24712
  try {
24435
- const stat2 = statSync8(path3);
24713
+ const stat2 = statSync9(path3);
24436
24714
  if (stat2.size < offset) return null;
24437
24715
  fd2 = openSync10(path3, "r");
24438
24716
  const window = Math.min(SAMPLE_BYTES, offset);
@@ -24441,7 +24719,7 @@ function measureFence(path3, offset) {
24441
24719
  const tail = Buffer.allocUnsafe(window);
24442
24720
  const tailRead = readSync3(fd2, tail, 0, window, offset - window);
24443
24721
  if (headRead !== window || tailRead !== window) return null;
24444
- const contentDigest = createHash6("sha256").update(String(offset)).update("\0").update(head).update("\0").update(tail).digest("hex").slice(0, 32);
24722
+ const contentDigest = createHash7("sha256").update(String(offset)).update("\0").update(head).update("\0").update(tail).digest("hex").slice(0, 32);
24445
24723
  return {
24446
24724
  offset,
24447
24725
  size: stat2.size,
@@ -24605,11 +24883,11 @@ var init_tail_cache = __esm({
24605
24883
  });
24606
24884
 
24607
24885
  // packages/server/src/tailer.ts
24608
- import { statSync as statSync9, openSync as openSync11, readSync as readSync4, closeSync as closeSync10, readdirSync as readdirSync6, realpathSync as realpathSync6, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11, readFileSync as readFileSync14, existsSync as existsSync7 } from "node:fs";
24886
+ import { statSync as statSync10, openSync as openSync11, readSync as readSync4, closeSync as closeSync10, readdirSync as readdirSync6, realpathSync as realpathSync6, mkdirSync as mkdirSync14, writeFileSync as writeFileSync11, readFileSync as readFileSync14, existsSync as existsSync7 } from "node:fs";
24609
24887
  import { execFile as execFile2 } from "node:child_process";
24610
- import { createHash as createHash7 } from "node:crypto";
24888
+ import { createHash as createHash8 } from "node:crypto";
24611
24889
  import { promisify as promisify2 } from "node:util";
24612
- import { basename as basename4, join as join21, win32 } from "node:path";
24890
+ import { basename as basename4, join as join22, win32 } from "node:path";
24613
24891
  import { homedir as homedir7 } from "node:os";
24614
24892
  function pendingCallDeadline(tail) {
24615
24893
  const resolved2 = /* @__PURE__ */ new Set();
@@ -25136,7 +25414,7 @@ function launchTaskId(text2) {
25136
25414
  function readDescendantSidecars(sessionDir, mtimeMs) {
25137
25415
  let names;
25138
25416
  try {
25139
- names = readdirSync6(join21(sessionDir, "subagents"));
25417
+ names = readdirSync6(join22(sessionDir, "subagents"));
25140
25418
  } catch {
25141
25419
  return [];
25142
25420
  }
@@ -25147,7 +25425,7 @@ function readDescendantSidecars(sessionDir, mtimeMs) {
25147
25425
  if (!agentId) continue;
25148
25426
  let parsed;
25149
25427
  try {
25150
- parsed = JSON.parse(readFileSync14(join21(sessionDir, "subagents", name), "utf8"));
25428
+ parsed = JSON.parse(readFileSync14(join22(sessionDir, "subagents", name), "utf8"));
25151
25429
  } catch {
25152
25430
  continue;
25153
25431
  }
@@ -25161,7 +25439,7 @@ function readDescendantSidecars(sessionDir, mtimeMs) {
25161
25439
  agentType: text2(meta.agentType),
25162
25440
  parentAgentId: text2(meta.parentAgentId),
25163
25441
  spawnDepth: typeof meta.spawnDepth === "number" && Number.isFinite(meta.spawnDepth) ? meta.spawnDepth : void 0,
25164
- spawnedAtMs: mtimeMs(join21(sessionDir, "subagents", name))
25442
+ spawnedAtMs: mtimeMs(join22(sessionDir, "subagents", name))
25165
25443
  });
25166
25444
  }
25167
25445
  return out;
@@ -25287,7 +25565,7 @@ function trackCompletions(state, rec) {
25287
25565
  const monitorTimedOut = isMonitorTimeoutEvent(block);
25288
25566
  const terminal = status === "completed" || status === "failed" || status === "killed" ? status : status === "stopped" || monitorTimedOut ? "killed" : void 0;
25289
25567
  if (!terminal) continue;
25290
- const fingerprint = createHash7("sha1").update(block).digest("hex").slice(0, 16);
25568
+ const fingerprint = createHash8("sha1").update(block).digest("hex").slice(0, 16);
25291
25569
  const repeatOf = /* @__PURE__ */ new Set();
25292
25570
  for (const id2 of blockTaskIds(block)) {
25293
25571
  if (state.foldedTerminals?.get(id2) === fingerprint) repeatOf.add(id2);
@@ -25650,13 +25928,13 @@ function isPermMarker(v) {
25650
25928
  }
25651
25929
  function defaultMtimeMs(path3) {
25652
25930
  try {
25653
- return statSync9(path3).mtimeMs;
25931
+ return statSync10(path3).mtimeMs;
25654
25932
  } catch {
25655
25933
  return void 0;
25656
25934
  }
25657
25935
  }
25658
25936
  function defaultLogDir(project) {
25659
- return join21(homedir7(), ".claude", "projects", project.cwdSlug);
25937
+ return join22(homedir7(), ".claude", "projects", project.cwdSlug);
25660
25938
  }
25661
25939
  function defaultReadPermMarker(project) {
25662
25940
  if (!project.stateDir) return () => void 0;
@@ -25788,7 +26066,7 @@ function createTailer(deps) {
25788
26066
  return paneDead(row.slug);
25789
26067
  }
25790
26068
  const defaultBackend = {
25791
- transcriptPath: (sessionId) => join21(logDir, `${sessionId}.jsonl`),
26069
+ transcriptPath: (sessionId) => join22(logDir, `${sessionId}.jsonl`),
25792
26070
  foldLine: (state, line) => {
25793
26071
  const rec = parseLine(line);
25794
26072
  if (rec) applyRecord(state, rec);
@@ -25830,7 +26108,7 @@ function createTailer(deps) {
25830
26108
  function blockedUntil(path3, m) {
25831
26109
  let size;
25832
26110
  try {
25833
- size = statSync9(path3).size;
26111
+ size = statSync10(path3).size;
25834
26112
  } catch {
25835
26113
  return void 0;
25836
26114
  }
@@ -25956,7 +26234,7 @@ ${ask}`;
25956
26234
  return state.path.replace(/\.jsonl$/, "");
25957
26235
  }
25958
26236
  function descendantSidecars(state) {
25959
- const at2 = mtimeMs(join21(sessionDirOf(state), "subagents"));
26237
+ const at2 = mtimeMs(join22(sessionDirOf(state), "subagents"));
25960
26238
  const cached2 = descendantIndex.get(state.slug);
25961
26239
  if (cached2 && cached2.at === at2) return cached2.all;
25962
26240
  const all = readDescendantSidecars(sessionDirOf(state), mtimeMs);
@@ -25970,7 +26248,7 @@ ${ask}`;
25970
26248
  return descendantIndex.get(state.slug)?.byToolUse.get(id2);
25971
26249
  }
25972
26250
  function descendantTranscript(state, meta) {
25973
- return join21(sessionDirOf(state), "subagents", `agent-${meta.agentId}.jsonl`);
26251
+ return join22(sessionDirOf(state), "subagents", `agent-${meta.agentId}.jsonl`);
25974
26252
  }
25975
26253
  function subAgentDescendantTasks(slug, id2) {
25976
26254
  const state = states.get(slug);
@@ -26318,10 +26596,10 @@ ${ask}`;
26318
26596
  if (name.startsWith(".") || !name.endsWith(".jsonl")) continue;
26319
26597
  const id2 = name.slice(0, -".jsonl".length);
26320
26598
  if (!id2 || registered.has(id2)) continue;
26321
- const path3 = join21(logDir, name);
26599
+ const path3 = join22(logDir, name);
26322
26600
  let mtime;
26323
26601
  try {
26324
- mtime = statSync9(path3).mtimeMs;
26602
+ mtime = statSync10(path3).mtimeMs;
26325
26603
  } catch {
26326
26604
  continue;
26327
26605
  }
@@ -26369,6 +26647,18 @@ ${ask}`;
26369
26647
  if (state.lastActivityAt !== prevActivity || state.lastAssistant !== prevAssistant || state.model !== prevModel || state.effort !== prevEffort || state.permissionMode !== prevPermissionMode) dirty = true;
26370
26648
  return dirty;
26371
26649
  }
26650
+ const crossSessionOnLine = deps.onCrossSessionMessage ? (line) => {
26651
+ if (!line.includes("cross-session-message")) return;
26652
+ let rec;
26653
+ try {
26654
+ rec = JSON.parse(line);
26655
+ } catch {
26656
+ return;
26657
+ }
26658
+ if (rec.type !== "queue-operation" || rec.operation !== "enqueue" || typeof rec.content !== "string") return;
26659
+ const peer = parseCrossSessionMessage(rec.content);
26660
+ if (peer) deps.onCrossSessionMessage(peer);
26661
+ } : void 0;
26372
26662
  function chainOnLine(a, b) {
26373
26663
  if (!a) return b;
26374
26664
  if (!b) return a;
@@ -26474,7 +26764,7 @@ ${ask}`;
26474
26764
  if (entry.path !== state.path) {
26475
26765
  let derivedSize = 0;
26476
26766
  try {
26477
- derivedSize = statSync9(state.path).size;
26767
+ derivedSize = statSync10(state.path).size;
26478
26768
  } catch {
26479
26769
  derivedSize = 0;
26480
26770
  }
@@ -26531,7 +26821,7 @@ ${ask}`;
26531
26821
  function consume(state, backend, onLine) {
26532
26822
  let size;
26533
26823
  try {
26534
- size = statSync9(state.path).size;
26824
+ size = statSync10(state.path).size;
26535
26825
  } catch {
26536
26826
  return;
26537
26827
  }
@@ -26589,8 +26879,8 @@ ${ask}`;
26589
26879
  `thread ${row.slug} (session ${row.session_id}): no transcript ${DISCOVERY_GRACE_MS / 1e3}s after dispatch \u2014 likely a boot failure. ${detail2.slice(0, 4e3)}`
26590
26880
  );
26591
26881
  try {
26592
- mkdirSync13(stallLogDir, { recursive: true });
26593
- writeFileSync11(join21(stallLogDir, `${row.slug}.stall.log`), `session_id: ${row.session_id}
26882
+ mkdirSync14(stallLogDir, { recursive: true });
26883
+ writeFileSync11(join22(stallLogDir, `${row.slug}.stall.log`), `session_id: ${row.session_id}
26594
26884
  captured_at: ${new Date(now()).toISOString()}
26595
26885
 
26596
26886
  ${detail2}
@@ -26600,10 +26890,10 @@ ${detail2}
26600
26890
  }
26601
26891
  function relocatedTranscript(state) {
26602
26892
  const name = `${state.nativeSessionId}.jsonl`;
26603
- const home = join21(logDir, name);
26893
+ const home = join22(logDir, name);
26604
26894
  const homeAt = home === state.path ? void 0 : mtimeOfNonEmpty(home);
26605
26895
  const dir = discoverTranscriptDir(logDir, state.nativeSessionId);
26606
- const sibling = dir ? join21(dir, name) : void 0;
26896
+ const sibling = dir ? join22(dir, name) : void 0;
26607
26897
  const siblingAt = sibling && sibling !== state.path ? mtimeOfNonEmpty(sibling) : void 0;
26608
26898
  if (homeAt === void 0) return siblingAt === void 0 ? void 0 : sibling;
26609
26899
  if (siblingAt === void 0) return home;
@@ -26638,7 +26928,7 @@ ${detail2}
26638
26928
  }
26639
26929
  let size = 0;
26640
26930
  try {
26641
- size = statSync9(state.path).size;
26931
+ size = statSync10(state.path).size;
26642
26932
  } catch {
26643
26933
  size = 0;
26644
26934
  }
@@ -26655,7 +26945,7 @@ ${detail2}
26655
26945
  state.nextDiscoverMs = nowMs + DISCOVER_RETRY_MS;
26656
26946
  const strandedDir = discoverTranscriptDir(logDir, row.session_id);
26657
26947
  if (strandedDir) {
26658
- state.path = join21(strandedDir, `${row.session_id}.jsonl`);
26948
+ state.path = join22(strandedDir, `${row.session_id}.jsonl`);
26659
26949
  state.offset = 0;
26660
26950
  state.partial = "";
26661
26951
  state.primed = false;
@@ -26679,7 +26969,7 @@ ${detail2}
26679
26969
  committed = false;
26680
26970
  }
26681
26971
  if (!committed) return false;
26682
- state.path = join21(logDir, `${found}.jsonl`);
26972
+ state.path = join22(logDir, `${found}.jsonl`);
26683
26973
  state.offset = 0;
26684
26974
  state.partial = "";
26685
26975
  state.primed = false;
@@ -26739,7 +27029,7 @@ ${detail2}
26739
27029
  continue;
26740
27030
  }
26741
27031
  if (!state) {
26742
- const path3 = backend.transcriptPath(nativeId) ?? join21(logDir, `${nativeId}.jsonl`);
27032
+ const path3 = backend.transcriptPath(nativeId) ?? join22(logDir, `${nativeId}.jsonl`);
26743
27033
  state = newTailState(row.slug, row.session_id, path3, false, nativeId, runtimeGeneration);
26744
27034
  state.dismissedOps = deps.storage.retiredOps(row.slug, row.session_id);
26745
27035
  hydrateFromCache(state, row, nativeId);
@@ -26815,7 +27105,7 @@ ${detail2}
26815
27105
  const rowLedger = row.delivery_ledger ?? null;
26816
27106
  const ledgerDrifted = rowLedger !== (state.deliveryLedgerSeen ?? null);
26817
27107
  const ledger = ledgerFold(row, nowMs, state);
26818
- consume(state, backend, chainOnLine(ledger.onLine, codexSubAgentOnLine(row, state)));
27108
+ consume(state, backend, chainOnLine(chainOnLine(ledger.onLine, codexSubAgentOnLine(row, state)), crossSessionOnLine));
26819
27109
  state.codexSubAgents?.poll(nowMs);
26820
27110
  const ledgerResult = ledger.finish();
26821
27111
  state.deliveryLedgerSeen = ledgerResult.changed ? ledgerResult.value : rowLedger;
@@ -27207,14 +27497,14 @@ var init_tailer = __esm({
27207
27497
  });
27208
27498
 
27209
27499
  // packages/server/src/backend/codex.ts
27210
- import { join as join22 } from "node:path";
27500
+ import { join as join23 } from "node:path";
27211
27501
  import { homedir as homedir8 } from "node:os";
27212
- import { readdirSync as readdirSync7, statSync as statSync10, readFileSync as readFileSync15, openSync as openSync12, readSync as readSync5, closeSync as closeSync11 } from "node:fs";
27502
+ import { readdirSync as readdirSync7, statSync as statSync11, readFileSync as readFileSync15, openSync as openSync12, readSync as readSync5, closeSync as closeSync11 } from "node:fs";
27213
27503
  function defaultCodexHome() {
27214
- return process.env.CODEX_HOME && process.env.CODEX_HOME.trim() ? process.env.CODEX_HOME : join22(homedir8(), ".codex");
27504
+ return process.env.CODEX_HOME && process.env.CODEX_HOME.trim() ? process.env.CODEX_HOME : join23(homedir8(), ".codex");
27215
27505
  }
27216
27506
  function sessionsDir(codexHome) {
27217
- return join22(codexHome, "sessions");
27507
+ return join23(codexHome, "sessions");
27218
27508
  }
27219
27509
  function codexSandbox(mode) {
27220
27510
  switch (mode) {
@@ -27660,17 +27950,17 @@ function collectRollouts(dir, out, budget) {
27660
27950
  const files = entries2.filter((e14) => e14.isFile() && e14.name.startsWith("rollout-") && e14.name.endsWith(".jsonl")).sort(descByName);
27661
27951
  for (const d of dirs) {
27662
27952
  if (budget.n <= 0) return;
27663
- collectRollouts(join22(dir, d.name), out, budget);
27953
+ collectRollouts(join23(dir, d.name), out, budget);
27664
27954
  }
27665
27955
  for (const f of files) {
27666
27956
  if (budget.n <= 0) return;
27667
27957
  let mtimeMs;
27668
27958
  try {
27669
- mtimeMs = statSync10(join22(dir, f.name)).mtimeMs;
27959
+ mtimeMs = statSync11(join23(dir, f.name)).mtimeMs;
27670
27960
  } catch {
27671
27961
  continue;
27672
27962
  }
27673
- out.push({ path: join22(dir, f.name), mtimeMs });
27963
+ out.push({ path: join23(dir, f.name), mtimeMs });
27674
27964
  budget.n--;
27675
27965
  }
27676
27966
  }
@@ -27714,8 +28004,8 @@ function readCodexThreadNames(codexHome = defaultCodexHome()) {
27714
28004
  const out = /* @__PURE__ */ new Map();
27715
28005
  let raw2;
27716
28006
  try {
27717
- const path3 = join22(codexHome, "session_index.jsonl");
27718
- if (statSync10(path3).size > SESSION_INDEX_MAX_BYTES) return out;
28007
+ const path3 = join23(codexHome, "session_index.jsonl");
28008
+ if (statSync11(path3).size > SESSION_INDEX_MAX_BYTES) return out;
27719
28009
  raw2 = readFileSync15(path3, "utf8");
27720
28010
  } catch {
27721
28011
  return out;
@@ -27918,19 +28208,19 @@ function searchPath2(env) {
27918
28208
  function vendoredNativeBeside(launcherJs, arch, access) {
27919
28209
  const triple = WINDOWS_TRIPLE[arch];
27920
28210
  if (!triple) return void 0;
27921
- const { dirname: dirname29, join: join54 } = win322;
27922
- const pkgRoot = dirname29(dirname29(launcherJs));
28211
+ const { dirname: dirname31, join: join56 } = win322;
28212
+ const pkgRoot = dirname31(dirname31(launcherJs));
27923
28213
  const platformPkg = `codex-win32-${arch}`;
27924
28214
  const vendorRoots = [
27925
- join54(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
28215
+ join56(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
27926
28216
  // nested under the package
27927
- join54(dirname29(pkgRoot), platformPkg, "vendor"),
28217
+ join56(dirname31(pkgRoot), platformPkg, "vendor"),
27928
28218
  // hoisted beside it: <prefix>\node_modules\@openai\<pkg>
27929
- join54(pkgRoot, "vendor")
28219
+ join56(pkgRoot, "vendor")
27930
28220
  // the pre-platform-package layout, binaries inside @openai/codex itself
27931
28221
  ];
27932
28222
  for (const root of vendorRoots) {
27933
- const exe2 = join54(root, triple, "bin", "codex.exe");
28223
+ const exe2 = join56(root, triple, "bin", "codex.exe");
27934
28224
  if (access(exe2, "exists")) return exe2;
27935
28225
  }
27936
28226
  return void 0;
@@ -30254,7 +30544,7 @@ var require_websocket = __commonJS({
30254
30544
  var http = __require("http");
30255
30545
  var net2 = __require("net");
30256
30546
  var tls = __require("tls");
30257
- var { randomBytes: randomBytes2, createHash: createHash18 } = __require("crypto");
30547
+ var { randomBytes: randomBytes2, createHash: createHash19 } = __require("crypto");
30258
30548
  var { Duplex, Readable: Readable3 } = __require("stream");
30259
30549
  var { URL: URL2 } = __require("url");
30260
30550
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -30922,7 +31212,7 @@ var require_websocket = __commonJS({
30922
31212
  abortHandshake(websocket, socket, "Invalid Upgrade header");
30923
31213
  return;
30924
31214
  }
30925
- const digest = createHash18("sha1").update(key + GUID).digest("base64");
31215
+ const digest = createHash19("sha1").update(key + GUID).digest("base64");
30926
31216
  if (res.headers["sec-websocket-accept"] !== digest) {
30927
31217
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
30928
31218
  return;
@@ -31291,7 +31581,7 @@ var require_websocket_server = __commonJS({
31291
31581
  var EventEmitter = __require("events");
31292
31582
  var http = __require("http");
31293
31583
  var { Duplex } = __require("stream");
31294
- var { createHash: createHash18 } = __require("crypto");
31584
+ var { createHash: createHash19 } = __require("crypto");
31295
31585
  var extension2 = require_extension();
31296
31586
  var PerMessageDeflate2 = require_permessage_deflate();
31297
31587
  var subprotocol2 = require_subprotocol();
@@ -31598,7 +31888,7 @@ var require_websocket_server = __commonJS({
31598
31888
  );
31599
31889
  }
31600
31890
  if (this._state > RUNNING) return abortHandshake(socket, 503);
31601
- const digest = createHash18("sha1").update(key + GUID).digest("base64");
31891
+ const digest = createHash19("sha1").update(key + GUID).digest("base64");
31602
31892
  const headers = [
31603
31893
  "HTTP/1.1 101 Switching Protocols",
31604
31894
  "Upgrade: websocket",
@@ -31761,19 +32051,19 @@ var init_codex_mcp = __esm({
31761
32051
  import { spawn as spawn2 } from "node:child_process";
31762
32052
  import { connect } from "node:net";
31763
32053
  import { Agent as HttpAgent } from "node:http";
31764
- import { createHash as createHash8, randomUUID as randomUUID9 } from "node:crypto";
31765
- import { existsSync as existsSync8, mkdirSync as mkdirSync14, readFileSync as readFileSync17, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "node:fs";
31766
- import { join as join23 } from "node:path";
32054
+ import { createHash as createHash9, randomUUID as randomUUID9 } from "node:crypto";
32055
+ import { existsSync as existsSync8, mkdirSync as mkdirSync15, readFileSync as readFileSync17, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "node:fs";
32056
+ import { join as join24 } from "node:path";
31767
32057
  import { PassThrough, Writable } from "node:stream";
31768
32058
  import { StringDecoder } from "node:string_decoder";
31769
32059
  function nativeDir(stateDir) {
31770
- return join23(stateDir, "codex-app-server-native");
32060
+ return join24(stateDir, "codex-app-server-native");
31771
32061
  }
31772
32062
  function nativeRecordPath(stateDir, projectId) {
31773
- return join23(nativeDir(stateDir), `${projectId}.json`);
32063
+ return join24(nativeDir(stateDir), `${projectId}.json`);
31774
32064
  }
31775
32065
  function nativeListenSocketPath(stateDir, projectId) {
31776
- const key = createHash8("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
32066
+ const key = createHash9("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
31777
32067
  return frizzIpcPath(`frizz-codex-native-${key}`);
31778
32068
  }
31779
32069
  function pidAlive2(pid) {
@@ -31943,7 +32233,7 @@ function attach(record, timeoutMs) {
31943
32233
  }
31944
32234
  async function startListener(options) {
31945
32235
  const { stateDir, projectId } = options;
31946
- mkdirSync14(nativeDir(stateDir), { recursive: true });
32236
+ mkdirSync15(nativeDir(stateDir), { recursive: true });
31947
32237
  const socketPath = nativeListenSocketPath(stateDir, projectId);
31948
32238
  if (existsSync8(socketPath) && !await socketAccepting(socketPath)) {
31949
32239
  try {
@@ -32041,19 +32331,19 @@ var init_codex_app_server_native = __esm({
32041
32331
  // packages/server/src/backend/codex-app-server-host.ts
32042
32332
  import { spawn as spawn3 } from "node:child_process";
32043
32333
  import { createConnection } from "node:net";
32044
- import { createHash as createHash9, randomUUID as randomUUID10 } from "node:crypto";
32045
- import { existsSync as existsSync9, mkdirSync as mkdirSync15, readFileSync as readFileSync18, unlinkSync as unlinkSync3 } from "node:fs";
32046
- import { join as join24 } from "node:path";
32334
+ import { createHash as createHash10, randomUUID as randomUUID10 } from "node:crypto";
32335
+ import { existsSync as existsSync9, mkdirSync as mkdirSync16, readFileSync as readFileSync18, unlinkSync as unlinkSync3 } from "node:fs";
32336
+ import { join as join25 } from "node:path";
32047
32337
  import { PassThrough as PassThrough2, Writable as Writable2 } from "node:stream";
32048
32338
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
32049
32339
  function daemonDir(stateDir) {
32050
- return join24(stateDir, "codex-app-server");
32340
+ return join25(stateDir, "codex-app-server");
32051
32341
  }
32052
32342
  function recordPath(stateDir, projectId) {
32053
- return join24(daemonDir(stateDir), `${projectId}.json`);
32343
+ return join25(daemonDir(stateDir), `${projectId}.json`);
32054
32344
  }
32055
32345
  function codexAppServerSocketPath(stateDir, projectId) {
32056
- const key = createHash9("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
32346
+ const key = createHash10("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
32057
32347
  return frizzIpcPath(`frizz-codex-${key}`);
32058
32348
  }
32059
32349
  function pidAlive3(pid) {
@@ -32133,7 +32423,7 @@ async function stopCodexAppServerDaemon(stateDir, projectId, timeoutMs = 1e4) {
32133
32423
  }
32134
32424
  function forkDaemon(options) {
32135
32425
  const { stateDir, projectId } = options;
32136
- mkdirSync15(daemonDir(stateDir), { recursive: true });
32426
+ mkdirSync16(daemonDir(stateDir), { recursive: true });
32137
32427
  const record = recordPath(stateDir, projectId);
32138
32428
  try {
32139
32429
  unlinkSync3(record);
@@ -32348,15 +32638,15 @@ var init_codex_app_server_host = __esm({
32348
32638
  });
32349
32639
 
32350
32640
  // packages/server/src/backend/claude-quota.ts
32351
- import { join as join25 } from "node:path";
32641
+ import { join as join26 } from "node:path";
32352
32642
  import { homedir as homedir9, platform } from "node:os";
32353
- import { createHash as createHash10 } from "node:crypto";
32643
+ import { createHash as createHash11 } from "node:crypto";
32354
32644
  import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
32355
32645
  import { execFile as execFile3 } from "node:child_process";
32356
32646
  import { promisify as promisify3 } from "node:util";
32357
32647
  function claudeConfigDir() {
32358
32648
  const override = process.env.CLAUDE_CONFIG_DIR;
32359
- return override && override.trim() ? override : join25(homedir9(), ".claude");
32649
+ return override && override.trim() ? override : join26(homedir9(), ".claude");
32360
32650
  }
32361
32651
  function tokenFromCredentialsJson(raw2) {
32362
32652
  let doc;
@@ -32385,7 +32675,7 @@ async function readKeychainToken() {
32385
32675
  }
32386
32676
  async function readAccessToken(configDir) {
32387
32677
  try {
32388
- const fromFile = tokenFromCredentialsJson(await readFile(join25(configDir, ".credentials.json"), "utf8"));
32678
+ const fromFile = tokenFromCredentialsJson(await readFile(join26(configDir, ".credentials.json"), "utf8"));
32389
32679
  if (fromFile) return fromFile;
32390
32680
  } catch {
32391
32681
  }
@@ -32457,10 +32747,10 @@ function retryAtFrom(res, now) {
32457
32747
  return now + Math.min(backoff, RATE_LIMIT_MAX_BACKOFF_MS);
32458
32748
  }
32459
32749
  function cachePaths(cacheDir, configDir) {
32460
- const profile = createHash10("sha256").update(configDir).digest("hex").slice(0, 12);
32750
+ const profile = createHash11("sha256").update(configDir).digest("hex").slice(0, 12);
32461
32751
  return {
32462
- data: join25(cacheDir, `claude-${profile}.json`),
32463
- lock: join25(cacheDir, `claude-${profile}.lock`)
32752
+ data: join26(cacheDir, `claude-${profile}.json`),
32753
+ lock: join26(cacheDir, `claude-${profile}.lock`)
32464
32754
  };
32465
32755
  }
32466
32756
  async function readShared(path3) {
@@ -32672,7 +32962,7 @@ function refreshSharedInBackground(paths, claudeBin, deps, now) {
32672
32962
  async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
32673
32963
  const now = (deps.now ?? Date.now)();
32674
32964
  const configDir = claudeConfigDir();
32675
- const cacheDir = deps.cacheDir ?? join25(frizzRoots().cache, "quota-cache");
32965
+ const cacheDir = deps.cacheDir ?? join26(frizzRoots().cache, "quota-cache");
32676
32966
  try {
32677
32967
  await mkdir(cacheDir, { recursive: true, mode: 448 });
32678
32968
  } catch {
@@ -32686,7 +32976,7 @@ async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
32686
32976
  async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
32687
32977
  const now = (deps.now ?? Date.now)();
32688
32978
  const configDir = claudeConfigDir();
32689
- const cacheDir = deps.cacheDir ?? join25(frizzRoots().cache, "quota-cache");
32979
+ const cacheDir = deps.cacheDir ?? join26(frizzRoots().cache, "quota-cache");
32690
32980
  const paths = cachePaths(cacheDir, configDir);
32691
32981
  try {
32692
32982
  await mkdir(cacheDir, { recursive: true, mode: 448 });
@@ -32793,19 +33083,19 @@ var init_claude_quota = __esm({
32793
33083
  });
32794
33084
 
32795
33085
  // packages/server/src/backend/auth-status.ts
32796
- import { join as join26 } from "node:path";
33086
+ import { join as join27 } from "node:path";
32797
33087
  import { homedir as homedir10, platform as platform2 } from "node:os";
32798
- import { readFileSync as readFileSync19, statSync as statSync11 } from "node:fs";
33088
+ import { readFileSync as readFileSync19, statSync as statSync12 } from "node:fs";
32799
33089
  import { execFile as execFile4 } from "node:child_process";
32800
33090
  import { promisify as promisify4 } from "node:util";
32801
33091
  function claudeConfigDir2() {
32802
33092
  const override = process.env.CLAUDE_CONFIG_DIR;
32803
- return override && override.trim() ? override : join26(homedir10(), ".claude");
33093
+ return override && override.trim() ? override : join27(homedir10(), ".claude");
32804
33094
  }
32805
33095
  function claudeFileState(configDir) {
32806
33096
  let raw2;
32807
33097
  try {
32808
- raw2 = readFileSync19(join26(configDir, ".credentials.json"), "utf8");
33098
+ raw2 = readFileSync19(join27(configDir, ".credentials.json"), "utf8");
32809
33099
  } catch (err) {
32810
33100
  return err.code === "ENOENT" ? "absent" : "error";
32811
33101
  }
@@ -32838,7 +33128,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
32838
33128
  if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
32839
33129
  let raw2;
32840
33130
  try {
32841
- raw2 = readFileSync19(join26(codexHome, "auth.json"), "utf8");
33131
+ raw2 = readFileSync19(join27(codexHome, "auth.json"), "utf8");
32842
33132
  } catch (err) {
32843
33133
  return err.code === "ENOENT" ? "signed-out" : "unknown";
32844
33134
  }
@@ -32858,7 +33148,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
32858
33148
  function readCodexAccountId(codexHome = defaultCodexHome()) {
32859
33149
  if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return void 0;
32860
33150
  try {
32861
- const doc = JSON.parse(readFileSync19(join26(codexHome, "auth.json"), "utf8"));
33151
+ const doc = JSON.parse(readFileSync19(join27(codexHome, "auth.json"), "utf8"));
32862
33152
  if (!doc || typeof doc !== "object") return void 0;
32863
33153
  const tokens = doc.tokens;
32864
33154
  if (!tokens || typeof tokens !== "object") return void 0;
@@ -32917,12 +33207,12 @@ function asEmail(value) {
32917
33207
  }
32918
33208
  function claudeAccountFile() {
32919
33209
  const override = process.env.CLAUDE_CONFIG_DIR;
32920
- return override && override.trim() ? join26(override, ".claude.json") : join26(homedir10(), ".claude.json");
33210
+ return override && override.trim() ? join27(override, ".claude.json") : join27(homedir10(), ".claude.json");
32921
33211
  }
32922
33212
  function readClaudeAccountEmail(path3 = claudeAccountFile()) {
32923
33213
  let stat2;
32924
33214
  try {
32925
- stat2 = statSync11(path3);
33215
+ stat2 = statSync12(path3);
32926
33216
  } catch {
32927
33217
  return void 0;
32928
33218
  }
@@ -32941,7 +33231,7 @@ function readClaudeAccountEmail(path3 = claudeAccountFile()) {
32941
33231
  function readCodexAccountEmail(codexHome = defaultCodexHome()) {
32942
33232
  let doc;
32943
33233
  try {
32944
- doc = JSON.parse(readFileSync19(join26(codexHome, "auth.json"), "utf8"));
33234
+ doc = JSON.parse(readFileSync19(join27(codexHome, "auth.json"), "utf8"));
32945
33235
  } catch {
32946
33236
  return void 0;
32947
33237
  }
@@ -33066,7 +33356,7 @@ var init_codex_execution_health = __esm({
33066
33356
  });
33067
33357
 
33068
33358
  // packages/server/src/backend/codex-app-server.ts
33069
- import { createHash as createHash11, randomUUID as randomUUID11 } from "node:crypto";
33359
+ import { createHash as createHash12, randomUUID as randomUUID11 } from "node:crypto";
33070
33360
  import { existsSync as existsSync10 } from "node:fs";
33071
33361
  import { StringDecoder as StringDecoder3 } from "node:string_decoder";
33072
33362
  function selectCodexHostKind(flagValue, platform3, hasSpawn) {
@@ -33085,21 +33375,21 @@ function compareCodexVersions(a, b) {
33085
33375
  for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1;
33086
33376
  return 0;
33087
33377
  }
33088
- function codexVersionVerdict(received, audited = CODEX_APP_SERVER_SUPPORTED_VERSION) {
33378
+ function codexVersionVerdict(received, audited2 = CODEX_APP_SERVER_SUPPORTED_VERSION) {
33089
33379
  if (!received) {
33090
- return { kind: "refused", message: `Codex app-server did not report a parseable frizz/<version> user agent; expected ${audited}` };
33380
+ return { kind: "refused", message: `Codex app-server did not report a parseable frizz/<version> user agent; expected ${audited2}` };
33091
33381
  }
33092
- const cmp = compareCodexVersions(received, audited);
33382
+ const cmp = compareCodexVersions(received, audited2);
33093
33383
  if (cmp === 0) return { kind: "audited" };
33094
33384
  if (cmp < 0) {
33095
33385
  return {
33096
33386
  kind: "refused",
33097
- message: `Codex app-server ${received} is older than the audited protocol ${audited}. Upgrade codex (\`npm i -g @openai/codex\`) \u2014 frizz sends parameters this build may not accept.`
33387
+ message: `Codex app-server ${received} is older than the audited protocol ${audited2}. Upgrade codex (\`npm i -g @openai/codex\`) \u2014 frizz sends parameters this build may not accept.`
33098
33388
  };
33099
33389
  }
33100
33390
  return {
33101
33391
  kind: "ahead",
33102
- message: `Codex app-server ${received} is NEWER than frizz's audited protocol ${audited} \u2014 running anyway (the protocol is additive and unknown fields are ignored). If Codex threads misbehave, re-audit and re-pin. Run \`npm test -- codex-protocol-conformance\` to check frizz's params against this binary's own schema.`
33392
+ message: `Codex app-server ${received} is NEWER than frizz's audited protocol ${audited2} \u2014 running anyway (the protocol is additive and unknown fields are ignored). If Codex threads misbehave, re-audit and re-pin. Run \`npm test -- codex-protocol-conformance\` to check frizz's params against this binary's own schema.`
33103
33393
  };
33104
33394
  }
33105
33395
  function isCodexSandboxMode(value) {
@@ -33414,7 +33704,7 @@ function canonicalJson2(value) {
33414
33704
  return JSON.stringify(normalize2(value));
33415
33705
  }
33416
33706
  function logicalRequestId(method, parts) {
33417
- const digest = createHash11("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
33707
+ const digest = createHash12("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
33418
33708
  return `codex-${digest}`;
33419
33709
  }
33420
33710
  function requestFingerprint(value) {
@@ -33424,7 +33714,7 @@ function requestFingerprint(value) {
33424
33714
  delete record.startedAtMs;
33425
33715
  delete record.autoResolutionMs;
33426
33716
  }
33427
- return createHash11("sha256").update(canonicalJson2(normalized)).digest("hex");
33717
+ return createHash12("sha256").update(canonicalJson2(normalized)).digest("hex");
33428
33718
  }
33429
33719
  function decision(id2, semantic, label) {
33430
33720
  return { id: id2, semantic, label };
@@ -36339,11 +36629,11 @@ var init_codex_app_server = __esm({
36339
36629
  });
36340
36630
 
36341
36631
  // packages/server/src/backend/codex-quota.ts
36342
- import { join as join27 } from "node:path";
36343
- import { readdirSync as readdirSync8, statSync as statSync12, openSync as openSync13, readSync as readSync6, fstatSync, closeSync as closeSync12 } from "node:fs";
36632
+ import { join as join28 } from "node:path";
36633
+ import { readdirSync as readdirSync8, statSync as statSync13, openSync as openSync13, readSync as readSync6, fstatSync, closeSync as closeSync12 } from "node:fs";
36344
36634
  import { spawn as spawn4 } from "node:child_process";
36345
36635
  function sessionsDir2(codexHome) {
36346
- return join27(codexHome, "sessions");
36636
+ return join28(codexHome, "sessions");
36347
36637
  }
36348
36638
  function newestRollouts(dir, out, budget) {
36349
36639
  if (budget.n <= 0) return;
@@ -36357,11 +36647,11 @@ function newestRollouts(dir, out, budget) {
36357
36647
  const files = entries2.filter((e14) => e14.isFile() && e14.name.startsWith("rollout-") && e14.name.endsWith(".jsonl")).map((e14) => e14.name).sort(descByName2);
36358
36648
  for (const d of dirs) {
36359
36649
  if (budget.n <= 0) return;
36360
- newestRollouts(join27(dir, d), out, budget);
36650
+ newestRollouts(join28(dir, d), out, budget);
36361
36651
  }
36362
36652
  for (const f of files) {
36363
36653
  if (budget.n <= 0) return;
36364
- out.push(join27(dir, f));
36654
+ out.push(join28(dir, f));
36365
36655
  budget.n--;
36366
36656
  }
36367
36657
  }
@@ -36433,7 +36723,7 @@ function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
36433
36723
  newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
36434
36724
  const byMtime = rollouts.map((path3) => {
36435
36725
  try {
36436
- return { path: path3, mtimeMs: statSync12(path3).mtimeMs };
36726
+ return { path: path3, mtimeMs: statSync13(path3).mtimeMs };
36437
36727
  } catch {
36438
36728
  return void 0;
36439
36729
  }
@@ -36634,9 +36924,9 @@ var init_quota = __esm({
36634
36924
 
36635
36925
  // packages/server/src/local-file.ts
36636
36926
  import { spawn as spawn5 } from "node:child_process";
36637
- import { existsSync as existsSync11, readFileSync as readFileSync20, realpathSync as realpathSync7, statSync as statSync13 } from "node:fs";
36927
+ import { existsSync as existsSync11, readFileSync as readFileSync20, realpathSync as realpathSync7, statSync as statSync14 } from "node:fs";
36638
36928
  import { homedir as homedir11 } from "node:os";
36639
- import { dirname as dirname11, isAbsolute as isAbsolute5, join as join28, resolve as resolve5, sep as sep2 } from "node:path";
36929
+ import { dirname as dirname12, isAbsolute as isAbsolute5, join as join29, resolve as resolve5, sep as sep2 } from "node:path";
36640
36930
  function isUnder(real, root) {
36641
36931
  let rootReal;
36642
36932
  try {
@@ -36648,12 +36938,12 @@ function isUnder(real, root) {
36648
36938
  return real === rootReal || real.startsWith(rootReal.endsWith(sep2) ? rootReal : rootReal + sep2);
36649
36939
  }
36650
36940
  try {
36651
- const trusted2 = statSync13(rootReal, { bigint: true });
36941
+ const trusted2 = statSync14(rootReal, { bigint: true });
36652
36942
  if (!trusted2.isDirectory() || trusted2.ino === 0n) return false;
36653
36943
  for (let ancestor = real; ; ) {
36654
- const actual = statSync13(ancestor, { bigint: true });
36944
+ const actual = statSync14(ancestor, { bigint: true });
36655
36945
  if (actual.dev === trusted2.dev && actual.ino === trusted2.ino) return true;
36656
- const parent = dirname11(ancestor);
36946
+ const parent = dirname12(ancestor);
36657
36947
  if (parent === ancestor) return false;
36658
36948
  ancestor = parent;
36659
36949
  }
@@ -36672,7 +36962,7 @@ function resolveLocalFile(rawPath, roots) {
36672
36962
  }
36673
36963
  if (!roots.some((root) => isUnder(real, root))) throw new Error("Local file is outside Frizz's trusted roots");
36674
36964
  try {
36675
- if (!statSync13(real).isFile()) throw new Error("Local path is not a regular file");
36965
+ if (!statSync14(real).isFile()) throw new Error("Local path is not a regular file");
36676
36966
  } catch (error) {
36677
36967
  if (error instanceof Error && error.message === "Local path is not a regular file") throw error;
36678
36968
  throw new Error("Local file was not found");
@@ -36682,7 +36972,7 @@ function resolveLocalFile(rawPath, roots) {
36682
36972
  function resolveOpenableFile(raw2, projectDir, roots, home = homedir11()) {
36683
36973
  const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
36684
36974
  if (!trimmed) return null;
36685
- const abs = trimmed === "~" ? home : /^~[\/\\]/.test(trimmed) ? join28(home, trimmed.slice(2)) : isAbsolute5(trimmed) ? trimmed : resolve5(projectDir, trimmed);
36975
+ const abs = trimmed === "~" ? home : /^~[\/\\]/.test(trimmed) ? join29(home, trimmed.slice(2)) : isAbsolute5(trimmed) ? trimmed : resolve5(projectDir, trimmed);
36686
36976
  try {
36687
36977
  return resolveLocalFile(abs, roots);
36688
36978
  } catch {
@@ -36740,7 +37030,7 @@ function localFileOpenCommand(path3, selected, options = {}) {
36740
37030
  const exists = options.exists ?? existsSync11;
36741
37031
  const localAppData = env.LOCALAPPDATA;
36742
37032
  if (localAppData) {
36743
- const exe2 = join28(localAppData, ...editor.exe);
37033
+ const exe2 = join29(localAppData, ...editor.exe);
36744
37034
  if (exists(exe2)) return { command: exe2, args: [path3] };
36745
37035
  }
36746
37036
  if (/["%\r\n]/u.test(path3)) throw new Error(`cannot hand ${path3} to the ${editor.shim} shim through cmd.exe`);
@@ -37006,7 +37296,7 @@ var init_claude_model_upgrade = __esm({
37006
37296
  // packages/server/src/frizz.ts
37007
37297
  import { execFile as execFile6 } from "node:child_process";
37008
37298
  import { lstatSync as lstatSync3, realpathSync as realpathSync8 } from "node:fs";
37009
- import { basename as basename5, dirname as dirname12, join as join29, resolve as resolve6 } from "node:path";
37299
+ import { basename as basename5, dirname as dirname13, join as join30, resolve as resolve6 } from "node:path";
37010
37300
  import { promisify as promisify5 } from "node:util";
37011
37301
  function frizzScriptsDir() {
37012
37302
  if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
@@ -37015,11 +37305,11 @@ function frizzScriptsDir() {
37015
37305
  function directFrizzRoot(projectDir) {
37016
37306
  try {
37017
37307
  const projectRoot = realpathSync8(projectDir);
37018
- const path3 = join29(projectRoot, ".frizz");
37308
+ const path3 = join30(projectRoot, ".frizz");
37019
37309
  const stat2 = lstatSync3(path3);
37020
37310
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
37021
37311
  const real = realpathSync8(path3);
37022
- return dirname12(real) === projectRoot && basename5(real) === ".frizz" ? real : null;
37312
+ return dirname13(real) === projectRoot && basename5(real) === ".frizz" ? real : null;
37023
37313
  } catch {
37024
37314
  return null;
37025
37315
  }
@@ -37029,7 +37319,7 @@ function frizzDirExists(projectDir) {
37029
37319
  }
37030
37320
  async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
37031
37321
  if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
37032
- const { stdout } = await execFileP("node", [join29(scriptsDir, "index.mjs"), "--json"], {
37322
+ const { stdout } = await execFileP("node", [join30(scriptsDir, "index.mjs"), "--json"], {
37033
37323
  cwd: projectDir,
37034
37324
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
37035
37325
  maxBuffer: 32 * 1024 * 1024
@@ -37054,7 +37344,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
37054
37344
  };
37055
37345
  }
37056
37346
  async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
37057
- await execFileP("node", [join29(scriptsDir, "thread-update.mjs"), slug, ...args], {
37347
+ await execFileP("node", [join30(scriptsDir, "thread-update.mjs"), slug, ...args], {
37058
37348
  cwd: projectDir,
37059
37349
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
37060
37350
  });
@@ -37190,7 +37480,7 @@ var init_awaiting = __esm({
37190
37480
  // packages/server/src/backend/project-mcp-servers.ts
37191
37481
  import { readFileSync as readFileSync21 } from "node:fs";
37192
37482
  import { homedir as homedir12 } from "node:os";
37193
- import { join as join30 } from "node:path";
37483
+ import { join as join31 } from "node:path";
37194
37484
  function readJsonObject(path3) {
37195
37485
  try {
37196
37486
  const parsed = JSON.parse(readFileSync21(path3, "utf8"));
@@ -37286,15 +37576,15 @@ function projectMcpServers(cwd, options = {}) {
37286
37576
  const env = options.env ?? process.env;
37287
37577
  const home = options.home ?? homedir12();
37288
37578
  const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
37289
- const accountFile = configOverride ? join30(configOverride, ".claude.json") : join30(home, ".claude.json");
37290
- const userSettingsFile = join30(configOverride ?? join30(home, ".claude"), "settings.json");
37579
+ const accountFile = configOverride ? join31(configOverride, ".claude.json") : join31(home, ".claude.json");
37580
+ const userSettingsFile = join31(configOverride ?? join31(home, ".claude"), "settings.json");
37291
37581
  const account = readJsonObject(accountFile);
37292
37582
  const out = userScopeRemoteServers(account, cwd, env);
37293
- const declared = objectField(readJsonObject(join30(cwd, ".mcp.json")), "mcpServers");
37583
+ const declared = objectField(readJsonObject(join31(cwd, ".mcp.json")), "mcpServers");
37294
37584
  if (!declared) return out;
37295
37585
  const approval = collectApproval([
37296
- readJsonObject(join30(cwd, ".claude", "settings.json")),
37297
- readJsonObject(join30(cwd, ".claude", "settings.local.json")),
37586
+ readJsonObject(join31(cwd, ".claude", "settings.json")),
37587
+ readJsonObject(join31(cwd, ".claude", "settings.local.json")),
37298
37588
  readJsonObject(userSettingsFile),
37299
37589
  objectField(objectField(account, "projects"), cwd)
37300
37590
  ]);
@@ -37319,17 +37609,17 @@ var init_project_mcp_servers = __esm({
37319
37609
 
37320
37610
  // packages/server/src/worker-plugin-dir.ts
37321
37611
  import { existsSync as existsSync12 } from "node:fs";
37322
- import { dirname as dirname13, join as join31 } from "node:path";
37612
+ import { dirname as dirname14, join as join32 } from "node:path";
37323
37613
  import { fileURLToPath as fileURLToPath3 } from "node:url";
37324
37614
  function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
37325
37615
  const override = env.FRIZZ_WORKER_PLUGIN_DIR;
37326
- if (override && existsSync12(join31(override, ".claude-plugin", "plugin.json")))
37616
+ if (override && existsSync12(join32(override, ".claude-plugin", "plugin.json")))
37327
37617
  return override;
37328
- let current = dirname13(fileURLToPath3(moduleUrl));
37618
+ let current = dirname14(fileURLToPath3(moduleUrl));
37329
37619
  for (; ; ) {
37330
- const candidate = join31(current, "cc-worker");
37331
- if (existsSync12(join31(candidate, ".claude-plugin", "plugin.json"))) return candidate;
37332
- const parent = dirname13(current);
37620
+ const candidate = join32(current, "cc-worker");
37621
+ if (existsSync12(join32(candidate, ".claude-plugin", "plugin.json"))) return candidate;
37622
+ const parent = dirname14(current);
37333
37623
  if (parent === current) return void 0;
37334
37624
  current = parent;
37335
37625
  }
@@ -38463,11 +38753,11 @@ checks. Depth scales with blast radius and yields to the project's conventions.`
38463
38753
  });
38464
38754
 
38465
38755
  // packages/server/src/backend/acp-agents.ts
38466
- import { accessSync as accessSync3, constants as constants2, statSync as statSync14 } from "node:fs";
38467
- import { delimiter as delimiter2, isAbsolute as isAbsolute7, join as join32 } from "node:path";
38756
+ import { accessSync as accessSync3, constants as constants2, statSync as statSync15 } from "node:fs";
38757
+ import { delimiter as delimiter2, isAbsolute as isAbsolute7, join as join33 } from "node:path";
38468
38758
  function isExecutableFile(path3) {
38469
38759
  try {
38470
- if (!statSync14(path3).isFile()) return false;
38760
+ if (!statSync15(path3).isFile()) return false;
38471
38761
  if (process.platform !== "win32") accessSync3(path3, constants2.X_OK);
38472
38762
  return true;
38473
38763
  } catch {
@@ -38480,9 +38770,9 @@ function findOnPath(command, env = process.env) {
38480
38770
  const exts = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
38481
38771
  for (const dir of dirs) {
38482
38772
  for (const ext of exts) {
38483
- const candidate = join32(dir, command + ext.toLowerCase());
38773
+ const candidate = join33(dir, command + ext.toLowerCase());
38484
38774
  if (isExecutableFile(candidate)) return candidate;
38485
- if (ext && isExecutableFile(join32(dir, command + ext))) return join32(dir, command + ext);
38775
+ if (ext && isExecutableFile(join33(dir, command + ext))) return join33(dir, command + ext);
38486
38776
  }
38487
38777
  }
38488
38778
  return void 0;
@@ -39499,9 +39789,9 @@ var init_claude_agent_broker_bridge = __esm({
39499
39789
  });
39500
39790
 
39501
39791
  // packages/server/src/dispatch.ts
39502
- import { closeSync as closeSync13, constants as constants3, existsSync as existsSync13, fstatSync as fstatSync2, lstatSync as lstatSync4, openSync as openSync14, readFileSync as readFileSync22, realpathSync as realpathSync9, statSync as statSync15, writeFileSync as writeFileSync13, mkdirSync as mkdirSync16, rmSync as rmSync10 } from "node:fs";
39503
- import { basename as basename6, join as join33, dirname as dirname14 } from "node:path";
39504
- import { createHash as createHash12, randomUUID as randomUUID13 } from "node:crypto";
39792
+ import { closeSync as closeSync13, constants as constants3, existsSync as existsSync13, fstatSync as fstatSync2, lstatSync as lstatSync4, openSync as openSync14, readFileSync as readFileSync22, realpathSync as realpathSync9, statSync as statSync16, writeFileSync as writeFileSync13, mkdirSync as mkdirSync17, rmSync as rmSync10 } from "node:fs";
39793
+ import { basename as basename6, join as join34, dirname as dirname15 } from "node:path";
39794
+ import { createHash as createHash13, randomUUID as randomUUID13 } from "node:crypto";
39505
39795
  function fallbackTitle(prompt) {
39506
39796
  const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
39507
39797
  let allWords = firstLine2.split(/\s+/).filter(Boolean);
@@ -39519,7 +39809,7 @@ function fallbackTitle(prompt) {
39519
39809
  }
39520
39810
  function resolveSlug(frizzDir, base, taken) {
39521
39811
  base = ThreadSlug.parse(base);
39522
- const isTaken = (slug) => existsSync13(join33(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
39812
+ const isTaken = (slug) => existsSync13(join34(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
39523
39813
  if (!isTaken(base)) return base;
39524
39814
  for (let n = 2; ; n++) {
39525
39815
  const suffix = `-${n}`;
@@ -39536,16 +39826,16 @@ function resolveLegacyThreadFile(projectDir, value) {
39536
39826
  if (!parsed.success) return null;
39537
39827
  try {
39538
39828
  const projectRoot = realpathSync9(projectDir);
39539
- const frizzPath = join33(projectRoot, ".frizz");
39829
+ const frizzPath = join34(projectRoot, ".frizz");
39540
39830
  const frizzStat = lstatSync4(frizzPath);
39541
39831
  if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
39542
39832
  const realFrizz = realpathSync9(frizzPath);
39543
- if (dirname14(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
39544
- const path3 = join33(realFrizz, `${parsed.data}.md`);
39833
+ if (dirname15(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
39834
+ const path3 = join34(realFrizz, `${parsed.data}.md`);
39545
39835
  const before = lstatSync4(path3);
39546
39836
  if (!before.isFile() || before.isSymbolicLink()) return null;
39547
39837
  const realPath = realpathSync9(path3);
39548
- if (dirname14(realPath) !== realFrizz || basename6(realPath) !== `${parsed.data}.md`) return null;
39838
+ if (dirname15(realPath) !== realFrizz || basename6(realPath) !== `${parsed.data}.md`) return null;
39549
39839
  let contents;
39550
39840
  let openedBefore;
39551
39841
  let openedAfter;
@@ -39570,7 +39860,7 @@ function resolveLegacyThreadFile(projectDir, value) {
39570
39860
  size: after.size,
39571
39861
  mtimeMs: after.mtimeMs,
39572
39862
  ctimeMs: after.ctimeMs,
39573
- digest: createHash12("sha256").update(contents).digest("hex")
39863
+ digest: createHash13("sha256").update(contents).digest("hex")
39574
39864
  };
39575
39865
  } catch {
39576
39866
  return null;
@@ -39585,9 +39875,9 @@ function boardAuthorizesAdoption(board, slug) {
39585
39875
  return !board.errorItems.some((item) => item.file === `${slug}.md`);
39586
39876
  }
39587
39877
  function ensureSafeDirectDirectory(parent, name) {
39588
- const path3 = join33(parent, name);
39878
+ const path3 = join34(parent, name);
39589
39879
  try {
39590
- mkdirSync16(path3);
39880
+ mkdirSync17(path3);
39591
39881
  } catch (error) {
39592
39882
  const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
39593
39883
  if (code !== "EEXIST") throw error;
@@ -39595,7 +39885,7 @@ function ensureSafeDirectDirectory(parent, name) {
39595
39885
  const stat2 = lstatSync4(path3);
39596
39886
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) throw new Error("unsafe project directory");
39597
39887
  const real = realpathSync9(path3);
39598
- if (dirname14(real) !== parent || basename6(real) !== name) throw new Error("unsafe project directory");
39888
+ if (dirname15(real) !== parent || basename6(real) !== name) throw new Error("unsafe project directory");
39599
39889
  return real;
39600
39890
  }
39601
39891
  function scratchDirRelPath(sessionId) {
@@ -39615,8 +39905,8 @@ function loadWorkerPrompt(kind = "claude") {
39615
39905
  function monitorScriptsDir() {
39616
39906
  const plugin = workerPluginDir();
39617
39907
  if (!plugin) return void 0;
39618
- const dir = join33(plugin, "skills", "gh", "scripts");
39619
- return existsSync13(join33(dir, "ci-watch.mjs")) ? dir : void 0;
39908
+ const dir = join34(plugin, "skills", "gh", "scripts");
39909
+ return existsSync13(join34(dir, "ci-watch.mjs")) ? dir : void 0;
39620
39910
  }
39621
39911
  function codexScratchpadHookConfig(hookScript, sessionId) {
39622
39912
  if (!hookScript || !sessionId) return {};
@@ -39628,7 +39918,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
39628
39918
  }
39629
39919
  ]
39630
39920
  });
39631
- const bashBackgroundHook = join33(dirname14(hookScript), "bash-background.mjs");
39921
+ const bashBackgroundHook = join34(dirname15(hookScript), "bash-background.mjs");
39632
39922
  return {
39633
39923
  bypass_hook_trust: true,
39634
39924
  hooks: {
@@ -39654,7 +39944,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
39654
39944
  }
39655
39945
  function scratchpadHookScript() {
39656
39946
  const plugin = workerPluginDir();
39657
- return plugin ? join33(plugin, "hooks", "scratchpad.mjs") : void 0;
39947
+ return plugin ? join34(plugin, "hooks", "scratchpad.mjs") : void 0;
39658
39948
  }
39659
39949
  function composePrompt(sessionId, prompt, kind = "claude") {
39660
39950
  const children2 = kind === "codex" ? "Native sub-agents share it \u2014 have each write its OWN file rather than all editing one." : "Name it in a sub-agent's prompt when you want its notes to land somewhere you can read; give each child its OWN file rather than having them all edit one.";
@@ -39670,10 +39960,10 @@ function scratchpadOrientation(sessionId, kind = "claude") {
39670
39960
  return `SCRATCH DIRECTORY: .frizz/threads/${sessionId}/ \u2014 yours, free-form, as many files as you like, and nothing is expected in it. A single direct task usually needs none; writing notes is never a substitute for doing the work (${children2}). Nothing in this directory is read automatically; if you want a note back after a compaction, mcp__frizz__goal with post_compaction: true re-sends a prompt of your choosing.`;
39671
39961
  }
39672
39962
  function frizzConfigBlock(projectDir) {
39673
- const path3 = join33(projectDir, "FRIZZ.md");
39963
+ const path3 = join34(projectDir, "FRIZZ.md");
39674
39964
  let body;
39675
39965
  try {
39676
- const st2 = statSync15(path3);
39966
+ const st2 = statSync16(path3);
39677
39967
  if (!st2.isFile() || st2.size > FRIZZ_MD_MAX_BYTES) return "";
39678
39968
  body = readFileSync22(path3, "utf8").trim();
39679
39969
  } catch {
@@ -39705,7 +39995,7 @@ function effectivePermissionMode(kind, mode) {
39705
39995
  }
39706
39996
  function systemPromptFlags(sessionId, system) {
39707
39997
  if (!system) return [];
39708
- mkdirSync16(SYSTEM_PROMPT_DIR, { recursive: true });
39998
+ mkdirSync17(SYSTEM_PROMPT_DIR, { recursive: true });
39709
39999
  const path3 = systemPromptPath(sessionId);
39710
40000
  writeFileSync13(path3, system);
39711
40001
  return ["--append-system-prompt-file", path3];
@@ -39714,7 +40004,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
39714
40004
  const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
39715
40005
  const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
39716
40006
  if (!pluginDir) return void 0;
39717
- const scriptPath = join33(pluginDir, "bin", FRIZZ_MCP.script);
40007
+ const scriptPath = join34(pluginDir, "bin", FRIZZ_MCP.script);
39718
40008
  if (!existsSync13(scriptPath)) return void 0;
39719
40009
  return {
39720
40010
  scriptPath,
@@ -39796,7 +40086,7 @@ function buildClaudeResumeCommand(opts) {
39796
40086
  }
39797
40087
  function createDispatcher(deps) {
39798
40088
  const readBoardSource = deps.readBoard ?? readBoard;
39799
- const frizzDir = join33(deps.project.dir, ".frizz");
40089
+ const frizzDir = join34(deps.project.dir, ".frizz");
39800
40090
  const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
39801
40091
  function cleanupPrewrites(built) {
39802
40092
  for (const path3 of new Set(built.prewrite.map((file) => file.path))) {
@@ -39809,7 +40099,7 @@ function createDispatcher(deps) {
39809
40099
  function cleanupDispatchFiles(scratchRel, built, sessionId) {
39810
40100
  cleanupPrewrites(built);
39811
40101
  try {
39812
- rmSync10(join33(deps.project.dir, scratchRel), { force: true, recursive: true });
40102
+ rmSync10(join34(deps.project.dir, scratchRel), { force: true, recursive: true });
39813
40103
  } catch {
39814
40104
  }
39815
40105
  cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
@@ -40302,7 +40592,7 @@ import {
40302
40592
  watch as fsWatch
40303
40593
  } from "node:fs";
40304
40594
  import { homedir as homedir14 } from "node:os";
40305
- import { dirname as dirname15, join as join34 } from "node:path";
40595
+ import { dirname as dirname16, join as join35 } from "node:path";
40306
40596
  import watcher from "@parcel/watcher";
40307
40597
  function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
40308
40598
  if (!liveness) return false;
@@ -40351,7 +40641,7 @@ function registeredLegacyFileIsTerminal(projectDir, slug) {
40351
40641
  return status === "done" || status === "dismissed";
40352
40642
  }
40353
40643
  function isBoardRelevantFrizzPath(roots, path3) {
40354
- return path3.endsWith(".md") && roots.has(dirname15(path3));
40644
+ return path3.endsWith(".md") && roots.has(dirname16(path3));
40355
40645
  }
40356
40646
  function futureSnooze(row, nowMs) {
40357
40647
  const parsed = SnoozeUntil.safeParse(row.snoozed_until);
@@ -41211,7 +41501,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
41211
41501
  if (parcelSub || stopped) return Promise.resolve();
41212
41502
  if (watchSetup) return watchSetup;
41213
41503
  const setup = (async () => {
41214
- const dir = join34(project.dir, ".frizz");
41504
+ const dir = join35(project.dir, ".frizz");
41215
41505
  const roots = /* @__PURE__ */ new Set([dir]);
41216
41506
  try {
41217
41507
  roots.add(realpathSync10(dir));
@@ -41226,7 +41516,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
41226
41516
  }
41227
41517
  if (events.some((event) => isBoardRelevantFrizzPath(roots, event.path))) scheduleRebuild();
41228
41518
  },
41229
- { ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join34(dir, name)) }
41519
+ { ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join35(dir, name)) }
41230
41520
  );
41231
41521
  if (stopped) {
41232
41522
  await next.unsubscribe();
@@ -41332,6 +41622,100 @@ var init_board = __esm({
41332
41622
  }
41333
41623
  });
41334
41624
 
41625
+ // packages/server/src/session-peer.ts
41626
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync23, renameSync as renameSync8, writeFileSync as writeFileSync14 } from "node:fs";
41627
+ import { homedir as homedir15 } from "node:os";
41628
+ import { dirname as dirname17, join as join36 } from "node:path";
41629
+ function claudeSessionDirs(env = process.env, home = homedir15()) {
41630
+ const dirs = [join36(home, ".claude", "sessions")];
41631
+ const override = env.CLAUDE_CONFIG_DIR?.trim();
41632
+ if (override) dirs.unshift(join36(override, "sessions"));
41633
+ return [...new Set(dirs)];
41634
+ }
41635
+ function sessionIdForPeerSocket(peer, dirs) {
41636
+ const socket = peer.socket?.replace(/^uds:/, "");
41637
+ const pid = socket ? SOCKET_PID.exec(socket)?.[1] : void 0;
41638
+ if (!socket || !pid) return void 0;
41639
+ for (const dir of dirs) {
41640
+ let entry;
41641
+ try {
41642
+ entry = JSON.parse(readFileSync23(join36(dir, `${pid}.json`), "utf8"));
41643
+ } catch {
41644
+ continue;
41645
+ }
41646
+ if (entry.messagingSocketPath !== socket) continue;
41647
+ if (peer.name && typeof entry.name === "string" && entry.name !== peer.name) continue;
41648
+ if (typeof entry.sessionId === "string" && entry.sessionId) return entry.sessionId;
41649
+ }
41650
+ return void 0;
41651
+ }
41652
+ function createSessionPeerResolver(deps) {
41653
+ const sessionDirs = deps.sessionDirs ?? (() => claudeSessionDirs());
41654
+ let remembered;
41655
+ const load = () => {
41656
+ if (remembered) return remembered;
41657
+ remembered = {};
41658
+ if (deps.memoryFile) {
41659
+ try {
41660
+ const parsed = JSON.parse(readFileSync23(deps.memoryFile, "utf8"));
41661
+ if (parsed && typeof parsed === "object") {
41662
+ for (const [k10, v] of Object.entries(parsed)) if (typeof v === "string") remembered[k10] = v;
41663
+ }
41664
+ } catch {
41665
+ }
41666
+ }
41667
+ return remembered;
41668
+ };
41669
+ const remember = (key, sessionId) => {
41670
+ const all = load();
41671
+ if (all[key] === sessionId) return;
41672
+ all[key] = sessionId;
41673
+ if (!deps.memoryFile) return;
41674
+ try {
41675
+ mkdirSync18(dirname17(deps.memoryFile), { recursive: true });
41676
+ const tmp = `${deps.memoryFile}.${process.pid}.tmp`;
41677
+ writeFileSync14(tmp, JSON.stringify(all));
41678
+ renameSync8(tmp, deps.memoryFile);
41679
+ } catch {
41680
+ }
41681
+ };
41682
+ return (peer, receiverProjectId) => {
41683
+ if (!peer.socket) return void 0;
41684
+ const key = `${peer.socket}
41685
+ ${peer.name ?? ""}`;
41686
+ const live = sessionIdForPeerSocket(peer, sessionDirs());
41687
+ if (live) remember(key, live);
41688
+ const sessionId = live ?? load()[key];
41689
+ const found = sessionId ? deps.findThread(sessionId) : void 0;
41690
+ if (!found) return void 0;
41691
+ const { projectId, project, ...thread } = found;
41692
+ return projectId === receiverProjectId || !project ? thread : { ...thread, project };
41693
+ };
41694
+ }
41695
+ function projectTranscriptSessionPeers(messages, resolve13) {
41696
+ const cache2 = /* @__PURE__ */ new Map();
41697
+ let changed = false;
41698
+ const out = messages.map((message) => {
41699
+ const peer = message.sessionPeer;
41700
+ if (!peer?.socket) return message;
41701
+ const key = `${peer.socket}
41702
+ ${peer.name ?? ""}`;
41703
+ if (!cache2.has(key)) cache2.set(key, resolve13(peer));
41704
+ const thread = cache2.get(key);
41705
+ if (!thread) return message;
41706
+ changed = true;
41707
+ return { ...message, sessionPeer: { ...peer, thread } };
41708
+ });
41709
+ return changed ? out : messages;
41710
+ }
41711
+ var SOCKET_PID;
41712
+ var init_session_peer = __esm({
41713
+ "packages/server/src/session-peer.ts"() {
41714
+ "use strict";
41715
+ SOCKET_PID = /[\\/](\d+)\.sock$/;
41716
+ }
41717
+ });
41718
+
41335
41719
  // packages/server/src/wake-store.ts
41336
41720
  function isQuietWindowExempt(hintKey) {
41337
41721
  return WAKE_QUIET_EXEMPT_HINT_PREFIXES.some((prefix) => hintKey.startsWith(prefix));
@@ -42209,7 +42593,7 @@ var init_pr_watch_noise = __esm({
42209
42593
  // packages/server/src/scheduler.ts
42210
42594
  import { execFile as execFile8 } from "node:child_process";
42211
42595
  import { promisify as promisify7 } from "node:util";
42212
- import { createHash as createHash13, randomUUID as randomUUID14 } from "node:crypto";
42596
+ import { createHash as createHash14, randomUUID as randomUUID14 } from "node:crypto";
42213
42597
  function failureSignature(rollup) {
42214
42598
  const ids = [];
42215
42599
  for (const c of Array.isArray(rollup) ? rollup : []) {
@@ -42219,7 +42603,7 @@ function failureSignature(rollup) {
42219
42603
  ids.push(`${label}@${c.detailsUrl ?? c.targetUrl ?? c.completedAt ?? ""}`);
42220
42604
  }
42221
42605
  if (ids.length === 0) return "";
42222
- return createHash13("sha1").update(ids.sort().join("\n")).digest("hex").slice(0, 12);
42606
+ return createHash14("sha1").update(ids.sort().join("\n")).digest("hex").slice(0, 12);
42223
42607
  }
42224
42608
  function ghPrViewArgs(ref) {
42225
42609
  return ["pr", "view", String(ref.number), "--repo", `${ref.owner}/${ref.repo}`, "--json", "state,mergedAt,statusCheckRollup,headRefOid,mergeable,reviewDecision"];
@@ -42307,7 +42691,7 @@ function githubWatchStatus(pr2, polledAt) {
42307
42691
  };
42308
42692
  }
42309
42693
  function wakeDeliveryId(slug, sessionId, fenceId) {
42310
- return createHash13("sha256").update(slug).update("\0").update(sessionId).update("\0").update(fenceId).digest("hex");
42694
+ return createHash14("sha256").update(slug).update("\0").update(sessionId).update("\0").update(fenceId).digest("hex");
42311
42695
  }
42312
42696
  function limitFenceId(fault) {
42313
42697
  return `${LIMIT_FENCE_PREFIX}${fault.at}`;
@@ -42322,7 +42706,7 @@ function isReportFenceId(fenceId) {
42322
42706
  return fenceId.startsWith(`${REPORT_FENCE_PREFIX}:`);
42323
42707
  }
42324
42708
  function snoozeFenceId(until, prompt) {
42325
- const digest = createHash13("sha256").update(prompt).digest("hex").slice(0, 16);
42709
+ const digest = createHash14("sha256").update(prompt).digest("hex").slice(0, 16);
42326
42710
  return `${SNOOZE_FENCE_PREFIX}:${until}:${digest}`;
42327
42711
  }
42328
42712
  function isSnoozeFenceId(fenceId) {
@@ -42453,7 +42837,7 @@ function isOwnWatchExpiredFenceId(fenceId) {
42453
42837
  return fenceId.startsWith(`${OWN_WATCH_EXPIRED_FENCE_PREFIX}:`);
42454
42838
  }
42455
42839
  function questionAnswerFenceId(ids) {
42456
- return `${QUESTION_ANSWER_FENCE_PREFIX}:${createHash13("sha256").update([...ids].sort().join(",")).digest("hex").slice(0, 16)}`;
42840
+ return `${QUESTION_ANSWER_FENCE_PREFIX}:${createHash14("sha256").update([...ids].sort().join(",")).digest("hex").slice(0, 16)}`;
42457
42841
  }
42458
42842
  function isQuestionAnswerFenceId(fenceId) {
42459
42843
  return fenceId.startsWith(`${QUESTION_ANSWER_FENCE_PREFIX}:`);
@@ -43440,8 +43824,8 @@ ${clock}`;
43440
43824
  // replaces it re-derives that clause from a cursor the superseded report never got to advance.
43441
43825
  ...(() => {
43442
43826
  const all = [...held?.changes ?? [], ...changes];
43443
- const merged = [...new Set(all)];
43444
- return merged.length ? { changes: merged } : {};
43827
+ const merged2 = [...new Set(all)];
43828
+ return merged2.length ? { changes: merged2 } : {};
43445
43829
  })()
43446
43830
  };
43447
43831
  const review = carried.items.length > 0 ? formatGithubWakeSteer({ ref: key, items: carried.items, omitted: carried.omitted }) : void 0;
@@ -44343,7 +44727,7 @@ var init_resume = __esm({
44343
44727
  });
44344
44728
 
44345
44729
  // packages/server/src/backend/claude.ts
44346
- import { join as join35 } from "node:path";
44730
+ import { join as join37 } from "node:path";
44347
44731
  function toolResultText2(content) {
44348
44732
  if (typeof content === "string") return content;
44349
44733
  if (!Array.isArray(content)) return "";
@@ -44434,7 +44818,7 @@ function createClaudeBackend(opts) {
44434
44818
  return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
44435
44819
  },
44436
44820
  transcriptPath(sessionId) {
44437
- return join35(opts.logDir, `${sessionId}.jsonl`);
44821
+ return join37(opts.logDir, `${sessionId}.jsonl`);
44438
44822
  },
44439
44823
  parseLine(line) {
44440
44824
  return parseClaudeLine(line);
@@ -44459,10 +44843,10 @@ var init_claude = __esm({
44459
44843
  });
44460
44844
 
44461
44845
  // packages/server/src/backend/acp-transcript.ts
44462
- import { appendFileSync as appendFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync17, readFileSync as readFileSync23 } from "node:fs";
44463
- import { dirname as dirname16, join as join36 } from "node:path";
44846
+ import { appendFileSync as appendFileSync4, existsSync as existsSync15, mkdirSync as mkdirSync19, readFileSync as readFileSync24 } from "node:fs";
44847
+ import { dirname as dirname18, join as join38 } from "node:path";
44464
44848
  function acpTranscriptPath(stateDir, sessionId) {
44465
- return join36(stateDir, ACP_TRANSCRIPT_DIR, `${sessionId}.jsonl`);
44849
+ return join38(stateDir, ACP_TRANSCRIPT_DIR, `${sessionId}.jsonl`);
44466
44850
  }
44467
44851
  function parseAcpRecord(line) {
44468
44852
  if (!line.trim()) return void 0;
@@ -44663,11 +45047,11 @@ var init_acp_transcript = __esm({
44663
45047
  AcpTranscriptWriter = class {
44664
45048
  constructor(path3) {
44665
45049
  this.path = path3;
44666
- mkdirSync17(dirname16(path3), { recursive: true });
44667
- if (!existsSync15(path3)) appendFileSync3(path3, "");
45050
+ mkdirSync19(dirname18(path3), { recursive: true });
45051
+ if (!existsSync15(path3)) appendFileSync4(path3, "");
44668
45052
  }
44669
45053
  append(record) {
44670
- appendFileSync3(this.path, JSON.stringify(record) + "\n");
45054
+ appendFileSync4(this.path, JSON.stringify(record) + "\n");
44671
45055
  }
44672
45056
  };
44673
45057
  TOOL_TEXT_MAX = 12e3;
@@ -45050,19 +45434,19 @@ var init_acp_rpc = __esm({
45050
45434
  // packages/server/src/backend/acp-host.ts
45051
45435
  import { spawn as spawn7 } from "node:child_process";
45052
45436
  import { createConnection as createConnection2 } from "node:net";
45053
- import { createHash as createHash14, randomUUID as randomUUID15 } from "node:crypto";
45054
- import { existsSync as existsSync16, mkdirSync as mkdirSync18, readFileSync as readFileSync24, readdirSync as readdirSync9, unlinkSync as unlinkSync4 } from "node:fs";
45055
- import { join as join37 } from "node:path";
45437
+ import { createHash as createHash15, randomUUID as randomUUID15 } from "node:crypto";
45438
+ import { existsSync as existsSync16, mkdirSync as mkdirSync20, readFileSync as readFileSync25, readdirSync as readdirSync9, unlinkSync as unlinkSync4 } from "node:fs";
45439
+ import { join as join39 } from "node:path";
45056
45440
  import { PassThrough as PassThrough3, Writable as Writable3 } from "node:stream";
45057
45441
  import { StringDecoder as StringDecoder5 } from "node:string_decoder";
45058
45442
  function daemonDir2(stateDir) {
45059
- return join37(stateDir, "acp-daemon");
45443
+ return join39(stateDir, "acp-daemon");
45060
45444
  }
45061
45445
  function acpDaemonRecordPath(stateDir, sessionId) {
45062
- return join37(daemonDir2(stateDir), `${sessionId}.json`);
45446
+ return join39(daemonDir2(stateDir), `${sessionId}.json`);
45063
45447
  }
45064
45448
  function acpDaemonSocketPath(stateDir, sessionId) {
45065
- const key = createHash14("sha256").update(stateDir).update("\0").update(sessionId).digest("hex").slice(0, 16);
45449
+ const key = createHash15("sha256").update(stateDir).update("\0").update(sessionId).digest("hex").slice(0, 16);
45066
45450
  return frizzIpcPath(`frizz-acp-${key}`);
45067
45451
  }
45068
45452
  function pidAlive4(pid) {
@@ -45075,7 +45459,7 @@ function pidAlive4(pid) {
45075
45459
  }
45076
45460
  function readAcpDaemonRecord(stateDir, sessionId) {
45077
45461
  try {
45078
- const value = JSON.parse(readFileSync24(acpDaemonRecordPath(stateDir, sessionId), "utf8"));
45462
+ const value = JSON.parse(readFileSync25(acpDaemonRecordPath(stateDir, sessionId), "utf8"));
45079
45463
  if (typeof value.daemonPid !== "number" || typeof value.socketPath !== "string" || typeof value.generation !== "string") return null;
45080
45464
  return {
45081
45465
  threadSlug: typeof value.threadSlug === "string" ? value.threadSlug : "",
@@ -45121,7 +45505,7 @@ async function stopAcpDaemon(stateDir, sessionId, timeoutMs = 1e4) {
45121
45505
  }
45122
45506
  function forkDaemon2(options) {
45123
45507
  const { stateDir, sessionId } = options;
45124
- mkdirSync18(daemonDir2(stateDir), { recursive: true });
45508
+ mkdirSync20(daemonDir2(stateDir), { recursive: true });
45125
45509
  const record = acpDaemonRecordPath(stateDir, sessionId);
45126
45510
  try {
45127
45511
  unlinkSync4(record);
@@ -72151,27 +72535,27 @@ var init_claude_models = __esm({
72151
72535
  });
72152
72536
 
72153
72537
  // packages/server/src/backend/codex-app-server-diagnostics.ts
72154
- import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync20, renameSync as renameSync9, statSync as statSync17 } from "node:fs";
72155
- import { join as join39 } from "node:path";
72538
+ import { appendFileSync as appendFileSync6, mkdirSync as mkdirSync22, renameSync as renameSync10, statSync as statSync18 } from "node:fs";
72539
+ import { join as join41 } from "node:path";
72156
72540
  function codexDiagnosticLogPath(stateDir, projectId) {
72157
- return join39(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
72541
+ return join41(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
72158
72542
  }
72159
72543
  function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
72160
- const dir = join39(stateDir, "codex-app-server");
72544
+ const dir = join41(stateDir, "codex-app-server");
72161
72545
  const path3 = codexDiagnosticLogPath(stateDir, projectId);
72162
72546
  let ensured = false;
72163
72547
  return (event) => {
72164
72548
  if (event.event === "stderr") return;
72165
72549
  try {
72166
72550
  if (!ensured) {
72167
- mkdirSync20(dir, { recursive: true });
72551
+ mkdirSync22(dir, { recursive: true });
72168
72552
  ensured = true;
72169
72553
  }
72170
72554
  try {
72171
- if (statSync17(path3).size > MAX_BYTES2) renameSync9(path3, `${path3}.1`);
72555
+ if (statSync18(path3).size > MAX_BYTES2) renameSync10(path3, `${path3}.1`);
72172
72556
  } catch {
72173
72557
  }
72174
- appendFileSync5(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
72558
+ appendFileSync6(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
72175
72559
  `);
72176
72560
  } catch {
72177
72561
  }
@@ -72189,7 +72573,7 @@ var init_codex_app_server_diagnostics = __esm({
72189
72573
  import { execFile as execFile9 } from "node:child_process";
72190
72574
  import { readdirSync as readdirSync11, unlinkSync as unlinkSync6 } from "node:fs";
72191
72575
  import { connect as connectSocket } from "node:net";
72192
- import { join as join40 } from "node:path";
72576
+ import { join as join42 } from "node:path";
72193
72577
  function parseLsofSocketNames(stdout) {
72194
72578
  const referenced = /* @__PURE__ */ new Set();
72195
72579
  for (const line of stdout.split("\n")) {
@@ -72206,7 +72590,7 @@ function sweepStaleSockets(options, deps = {}) {
72206
72590
  const keep = new Set(options.keep ?? []);
72207
72591
  let candidates;
72208
72592
  try {
72209
- candidates = (deps.readdir ?? readdirSync11)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join40(options.dir, name)).filter((path3) => !keep.has(path3));
72593
+ candidates = (deps.readdir ?? readdirSync11)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join42(options.dir, name)).filter((path3) => !keep.has(path3));
72210
72594
  } catch {
72211
72595
  return;
72212
72596
  }
@@ -72256,8 +72640,8 @@ var init_stale_socket_sweep = __esm({
72256
72640
 
72257
72641
  // packages/server/src/orphan-reaper.ts
72258
72642
  import { execFile as execFile10 } from "node:child_process";
72259
- import { readFileSync as readFileSync26 } from "node:fs";
72260
- import { dirname as dirname18 } from "node:path";
72643
+ import { readFileSync as readFileSync27 } from "node:fs";
72644
+ import { dirname as dirname20 } from "node:path";
72261
72645
  import { promisify as promisify8 } from "node:util";
72262
72646
  function firstTokenBasename(command) {
72263
72647
  const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
@@ -72486,7 +72870,7 @@ async function sweepOrphansOnce(deps = {}) {
72486
72870
  }
72487
72871
  function sweepStaleBrokerSockets() {
72488
72872
  try {
72489
- sweepStaleSockets({ dir: dirname18(claudeBrokerSocketPath("", "")), prefix: CLAUDE_BROKER_SOCKET_PREFIX });
72873
+ sweepStaleSockets({ dir: dirname20(claudeBrokerSocketPath("", "")), prefix: CLAUDE_BROKER_SOCKET_PREFIX });
72490
72874
  } catch {
72491
72875
  }
72492
72876
  }
@@ -72563,7 +72947,7 @@ var init_orphan_reaper = __esm({
72563
72947
  defaultExec = async (file, args) => (await execFileAsync6(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout;
72564
72948
  defaultReadEnv = (pid) => {
72565
72949
  try {
72566
- return readFileSync26(`/proc/${pid}/environ`, "utf8");
72950
+ return readFileSync27(`/proc/${pid}/environ`, "utf8");
72567
72951
  } catch {
72568
72952
  return null;
72569
72953
  }
@@ -72583,9 +72967,9 @@ var init_orphan_reaper = __esm({
72583
72967
  });
72584
72968
 
72585
72969
  // packages/server/src/context.ts
72586
- import { join as join41 } from "node:path";
72970
+ import { join as join43 } from "node:path";
72587
72971
  import { randomUUID as randomUUID16 } from "node:crypto";
72588
- import { homedir as homedir15 } from "node:os";
72972
+ import { homedir as homedir16 } from "node:os";
72589
72973
  function reconcileSessions(storage) {
72590
72974
  for (const row of storage.allSessions()) {
72591
72975
  if (isHeadlessRow(row)) continue;
@@ -72733,9 +73117,9 @@ async function createContext(opts = {}) {
72733
73117
  }
72734
73118
  }
72735
73119
  function createContextUnchecked(opts, resources) {
72736
- const home = opts.home ?? homedir15();
73120
+ const home = opts.home ?? homedir16();
72737
73121
  const project = opts.project ?? resolveProject();
72738
- const storage = createStorage(opts.database ?? join41(project.stateDir, "ui.db"), project.id);
73122
+ const storage = createStorage(opts.database ?? join43(project.stateDir, "ui.db"), project.id);
72739
73123
  resources.storage = storage;
72740
73124
  clearSecrets(project.stateDir);
72741
73125
  const bus = new Bus();
@@ -72950,10 +73334,30 @@ function createContextUnchecked(opts, resources) {
72950
73334
  }
72951
73335
  void claudeBroker?.warmUp();
72952
73336
  opts.startup?.afterPhase?.("Claude broker bridge");
73337
+ const sessionPeerResolver = createSessionPeerResolver({
73338
+ findThread: (sessionId) => {
73339
+ const open = [{ project, storage }, ...(opts.activeTenants?.() ?? []).filter((t) => t.project.id !== project.id)];
73340
+ for (const tenant of open) {
73341
+ const rows = tenant.storage?.allSessions() ?? [];
73342
+ const row = rows.find((r) => r.session_id === sessionId || r.agent_session_id === sessionId || r.transcript_id === sessionId);
73343
+ if (!row) continue;
73344
+ const shown = resolveSessionTitle(row, void 0);
73345
+ return {
73346
+ projectId: tenant.project.id,
73347
+ project: findById(tenant.project.id, home)?.slug,
73348
+ slug: row.slug,
73349
+ title: shown.aiTitle || shown.title || row.slug
73350
+ };
73351
+ }
73352
+ return void 0;
73353
+ },
73354
+ memoryFile: join43(frizzRoots().state, "session-peers.json")
73355
+ });
72953
73356
  const tailer = createTailer({
72954
73357
  project,
72955
73358
  storage,
72956
73359
  bus,
73360
+ onCrossSessionMessage: (peer) => void sessionPeerResolver(peer, project.id),
72957
73361
  backendFor,
72958
73362
  codexModels: () => readCodexModels(void 0, opts.codexVersion),
72959
73363
  onChange: () => board.refresh(),
@@ -73138,8 +73542,12 @@ ${wakeDeliveryToken(deliveryId)}`;
73138
73542
  getDispatchPreferences: (codexModels) => getDispatchPreferences(storage, getSettings(storage, home), home, codexModels),
73139
73543
  setDispatchPreference: (update, codexModels) => setDispatchPreference(storage, getSettings(storage, home), home, update, codexModels),
73140
73544
  activeTenants: opts.activeTenants,
73545
+ sessionPeerThread: (peer) => sessionPeerResolver(peer, project.id),
73141
73546
  teardownProject: opts.teardownProject,
73142
73547
  launchProjectId: opts.launchProjectId,
73548
+ // Derived from the state dir, never from `homedir()`, so a sandboxed server audits into its own
73549
+ // sandbox (the reason serverAddressPathForStateDir exists).
73550
+ audit: fileAuditLog(auditLogPathForStateDir(project.stateDir)),
73143
73551
  claudeBin: opts.claudeBin,
73144
73552
  codexBin: opts.codexBin,
73145
73553
  codexVersion: opts.codexVersion
@@ -73151,6 +73559,8 @@ var init_context = __esm({
73151
73559
  "use strict";
73152
73560
  init_src();
73153
73561
  init_bus();
73562
+ init_frizz_paths();
73563
+ init_project_registry();
73154
73564
  init_project();
73155
73565
  init_storage();
73156
73566
  init_secret_files();
@@ -73159,6 +73569,8 @@ var init_context = __esm({
73159
73569
  init_quota();
73160
73570
  init_claude_quota();
73161
73571
  init_board();
73572
+ init_session_peer();
73573
+ init_audit();
73162
73574
  init_tailer();
73163
73575
  init_dispatch();
73164
73576
  init_scheduler();
@@ -75849,8 +76261,8 @@ var init_server = __esm({
75849
76261
  });
75850
76262
 
75851
76263
  // packages/server/src/repair.ts
75852
- import { existsSync as existsSync18, readFileSync as readFileSync27, writeFileSync as writeFileSync14 } from "node:fs";
75853
- import { basename as basename7, dirname as dirname19, resolve as resolve9 } from "node:path";
76264
+ import { existsSync as existsSync18, readFileSync as readFileSync28, writeFileSync as writeFileSync15 } from "node:fs";
76265
+ import { basename as basename7, dirname as dirname21, resolve as resolve9 } from "node:path";
75854
76266
  function quoteValue(v) {
75855
76267
  if (/^[\w./#:+-]+$/.test(v)) return v;
75856
76268
  return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -75862,10 +76274,10 @@ function deriveTitle(body, slug) {
75862
76274
  function repairThreadFile(frizzDir, file) {
75863
76275
  const root = resolve9(frizzDir);
75864
76276
  const abs = resolve9(root, file);
75865
- if (dirname19(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
76277
+ if (dirname21(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
75866
76278
  if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
75867
76279
  if (!existsSync18(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
75868
- const content = readFileSync27(abs, "utf8");
76280
+ const content = readFileSync28(abs, "utf8");
75869
76281
  const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
75870
76282
  if (firstLine2?.trim() === "---") {
75871
76283
  throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
@@ -75880,7 +76292,7 @@ function repairThreadFile(frizzDir, file) {
75880
76292
  "---",
75881
76293
  ""
75882
76294
  ].join("\n");
75883
- writeFileSync14(abs, frontmatter + content);
76295
+ writeFileSync15(abs, frontmatter + content);
75884
76296
  return { slug };
75885
76297
  }
75886
76298
  var RepairError, REPAIR_STATUS_TEXT;
@@ -76093,7 +76505,7 @@ var init_repo_files = __esm({
76093
76505
  });
76094
76506
 
76095
76507
  // cc-worker/hooks/bash-background.mjs
76096
- import { readFileSync as readFileSync28, realpathSync as realpathSync12 } from "node:fs";
76508
+ import { readFileSync as readFileSync29, realpathSync as realpathSync12 } from "node:fs";
76097
76509
  import { basename as basename8 } from "node:path";
76098
76510
  import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
76099
76511
  function emit(obj) {
@@ -76248,7 +76660,7 @@ var init_bash_background = __esm({
76248
76660
  if (isDirectHookExecution(process.argv[1], import.meta.url)) {
76249
76661
  try {
76250
76662
  const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
76251
- emit(evaluateBashBackgroundHook(JSON.parse(readFileSync28(0, "utf8")), env));
76663
+ emit(evaluateBashBackgroundHook(JSON.parse(readFileSync29(0, "utf8")), env));
76252
76664
  } catch {
76253
76665
  emit({});
76254
76666
  }
@@ -76257,11 +76669,11 @@ var init_bash_background = __esm({
76257
76669
  });
76258
76670
 
76259
76671
  // packages/server/src/transcript.ts
76260
- import { closeSync as closeSync15, existsSync as existsSync19, fstatSync as fstatSync3, mkdirSync as mkdirSync21, openSync as openSync16, readdirSync as readdirSync12, readFileSync as readFileSync29, readSync as readSync8, renameSync as renameSync10, statSync as statSync18, unlinkSync as unlinkSync7, writeFileSync as writeFileSync15 } from "node:fs";
76261
- import { createHash as createHash15 } from "node:crypto";
76672
+ import { closeSync as closeSync15, existsSync as existsSync19, fstatSync as fstatSync3, mkdirSync as mkdirSync23, openSync as openSync16, readdirSync as readdirSync12, readFileSync as readFileSync30, readSync as readSync8, renameSync as renameSync11, statSync as statSync19, unlinkSync as unlinkSync7, writeFileSync as writeFileSync16 } from "node:fs";
76673
+ import { createHash as createHash16 } from "node:crypto";
76262
76674
  import { StringDecoder as StringDecoder6 } from "node:string_decoder";
76263
- import { join as join42 } from "node:path";
76264
- import { homedir as homedir16 } from "node:os";
76675
+ import { join as join44 } from "node:path";
76676
+ import { homedir as homedir17 } from "node:os";
76265
76677
  function formatTokens(n) {
76266
76678
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
76267
76679
  if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
@@ -76317,7 +76729,12 @@ function userDisplayText(text2, first) {
76317
76729
  projected = stripWakeTrailer(projected);
76318
76730
  return projected === text2 ? void 0 : projected;
76319
76731
  }
76732
+ function sessionPeerOf(session) {
76733
+ return { ...session.name ? { name: session.name } : {}, ...session.socket ? { socket: session.socket } : {} };
76734
+ }
76320
76735
  function userProjection(text2, first) {
76736
+ const session = parseCrossSessionMessage(text2);
76737
+ if (session) return { displayText: session.body, sessionPeer: sessionPeerOf(session) };
76321
76738
  const peer = parseAgentMessage(text2);
76322
76739
  if (peer) return { displayText: peer.body, peerFrom: peer.from };
76323
76740
  const displayText2 = userDisplayText(text2, first);
@@ -76478,6 +76895,15 @@ function createTranscriptFold(identityPrefix = "claude") {
76478
76895
  function findQueued(key) {
76479
76896
  return queuedPending.find((e14) => e14.key === key && e14.message.queued) ?? queuedPending.find((e14) => e14.key === key);
76480
76897
  }
76898
+ function findCrossSessionQueued(text2) {
76899
+ const sent = parseCrossSessionMessage(text2);
76900
+ if (!sent) return void 0;
76901
+ const same = (e14) => {
76902
+ const queued = e14.message.sessionPeer ? parseCrossSessionMessage(e14.key) : void 0;
76903
+ return !!queued && queued.body === sent.body && queued.name === sent.name && queued.socket === sent.socket;
76904
+ };
76905
+ return queuedPending.find((e14) => e14.message.queued && same(e14)) ?? queuedPending.find(same);
76906
+ }
76481
76907
  function dropQueued(entry) {
76482
76908
  const i = queuedPending.indexOf(entry);
76483
76909
  if (i !== -1) queuedPending.splice(i, 1);
@@ -76573,9 +76999,9 @@ function createTranscriptFold(identityPrefix = "claude") {
76573
76999
  const str2 = (v) => typeof v === "string" ? v.trim() : "";
76574
77000
  const senderTaskId = str2(peerOrigin.senderTaskId);
76575
77001
  const dispatchId = senderTaskId ? childDispatchIds.get(senderTaskId) : void 0;
76576
- const entry = prompt.trim() ? findQueued(prompt) : void 0;
77002
+ const entry = prompt.trim() ? findQueued(prompt) ?? findCrossSessionQueued(prompt) : void 0;
76577
77003
  if (entry) {
76578
- resolveQueued(prompt);
77004
+ resolveQueued(entry.key);
76579
77005
  if (dispatchId && entry.message.peerFrom) entry.message.peerDispatchId = dispatchId;
76580
77006
  const described = dispatchId ? dispatchLabels.get(dispatchId) : void 0;
76581
77007
  if (entry.message.peerFrom) {
@@ -76584,6 +77010,9 @@ function createTranscriptFold(identityPrefix = "claude") {
76584
77010
  if (senderTaskId) entry.message.peerSenderTaskId = senderTaskId;
76585
77011
  }
76586
77012
  lastAssistantId = null;
77013
+ } else if (prompt.trim() && parseCrossSessionMessage(prompt)) {
77014
+ out.push({ sourceId, role: "user", text: prompt, ...userProjection(prompt, out.length === 0), tools: [], parts: [], at: thisTs });
77015
+ lastAssistantId = null;
76587
77016
  } else {
76588
77017
  const parsed = prompt.trim() ? parseAgentMessage(prompt) : void 0;
76589
77018
  const describedHere = dispatchId ? dispatchLabels.get(dispatchId) : void 0;
@@ -76619,7 +77048,16 @@ function createTranscriptFold(identityPrefix = "claude") {
76619
77048
  const metaText = userText(rec);
76620
77049
  if (metaText) {
76621
77050
  const key = findQueued(metaText) ? metaText : peerSessionQueuedKey(metaText, unresolvedQueuedKeys());
76622
- const pending = key === void 0 ? void 0 : findQueued(key);
77051
+ const pending = (key === void 0 ? void 0 : findQueued(key)) ?? findCrossSessionQueued(metaText);
77052
+ if (rec.isSidechain !== true && parseCrossSessionMessage(metaText)) {
77053
+ if (pending) {
77054
+ resolveQueued(pending.key);
77055
+ } else {
77056
+ out.push({ sourceId, role: "user", text: metaText, ...userProjection(metaText, out.length === 0), tools: [], parts: [], at: rec.timestamp });
77057
+ }
77058
+ lastAssistantId = null;
77059
+ return;
77060
+ }
76623
77061
  if (pending) {
76624
77062
  resolveQueuedThrough(pending);
76625
77063
  dropQueued(pending);
@@ -76868,14 +77306,14 @@ function pruneScreenshotCache() {
76868
77306
  if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
76869
77307
  const byMtime = entries2.map((n) => {
76870
77308
  try {
76871
- return { n, m: statSync18(join42(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
77309
+ return { n, m: statSync19(join44(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
76872
77310
  } catch {
76873
77311
  return { n, m: 0 };
76874
77312
  }
76875
77313
  }).sort((a, b) => b.m - a.m);
76876
77314
  for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
76877
77315
  try {
76878
- unlinkSync7(join42(SCREENSHOT_CACHE_DIR, n));
77316
+ unlinkSync7(join44(SCREENSHOT_CACHE_DIR, n));
76879
77317
  } catch {
76880
77318
  }
76881
77319
  }
@@ -76904,17 +77342,17 @@ function persistDataUrlImage(dataUrl, idKey) {
76904
77342
  function persistBase64Image(mediaType, data, idKey) {
76905
77343
  const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
76906
77344
  if (!ext || !data) return void 0;
76907
- const name = createHash15("sha256").update(idKey).digest("hex").slice(0, 32);
76908
- const path3 = join42(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
77345
+ const name = createHash16("sha256").update(idKey).digest("hex").slice(0, 32);
77346
+ const path3 = join44(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
76909
77347
  try {
76910
77348
  if (existsSync19(path3)) return path3;
76911
77349
  if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
76912
77350
  const buf = Buffer.from(data, "base64");
76913
77351
  if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
76914
- mkdirSync21(SCREENSHOT_CACHE_DIR, { recursive: true });
76915
- const tmp = join42(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
76916
- writeFileSync15(tmp, buf);
76917
- renameSync10(tmp, path3);
77352
+ mkdirSync23(SCREENSHOT_CACHE_DIR, { recursive: true });
77353
+ const tmp = join44(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
77354
+ writeFileSync16(tmp, buf);
77355
+ renameSync11(tmp, path3);
76918
77356
  pruneScreenshotCache();
76919
77357
  return path3;
76920
77358
  } catch {
@@ -76926,17 +77364,17 @@ function persistSentFile(srcPath, idKey) {
76926
77364
  if (!ATTACHMENT_IMAGE_EXTENSIONS.includes(ext)) return void 0;
76927
77365
  const outExt = ext === "jpeg" ? "jpg" : ext;
76928
77366
  try {
76929
- const name = createHash15("sha256").update(idKey).digest("hex").slice(0, 32);
76930
- const dest = join42(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
77367
+ const name = createHash16("sha256").update(idKey).digest("hex").slice(0, 32);
77368
+ const dest = join44(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
76931
77369
  if (existsSync19(dest)) return dest;
76932
- const size = statSync18(srcPath).size;
77370
+ const size = statSync19(srcPath).size;
76933
77371
  if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
76934
- const buf = readFileSync29(srcPath);
77372
+ const buf = readFileSync30(srcPath);
76935
77373
  if (!looksLikeImage(buf, outExt)) return void 0;
76936
- mkdirSync21(SCREENSHOT_CACHE_DIR, { recursive: true });
76937
- const tmp = join42(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
76938
- writeFileSync15(tmp, buf);
76939
- renameSync10(tmp, dest);
77374
+ mkdirSync23(SCREENSHOT_CACHE_DIR, { recursive: true });
77375
+ const tmp = join44(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
77376
+ writeFileSync16(tmp, buf);
77377
+ renameSync11(tmp, dest);
76940
77378
  pruneScreenshotCache();
76941
77379
  return dest;
76942
77380
  } catch {
@@ -77372,7 +77810,7 @@ function readTranscript(project, sessionId) {
77372
77810
  }
77373
77811
  function verifyIncrementalParse(path3, identityPrefix, incremental) {
77374
77812
  try {
77375
- const fresh = parseTranscript(readFileSync29(path3, "utf8"), identityPrefix);
77813
+ const fresh = parseTranscript(readFileSync30(path3, "utf8"), identityPrefix);
77376
77814
  const a = JSON.stringify(incremental);
77377
77815
  const b = JSON.stringify(fresh);
77378
77816
  if (a !== b) {
@@ -77392,16 +77830,16 @@ function verifyIncrementalParse(path3, identityPrefix, incremental) {
77392
77830
  }
77393
77831
  }
77394
77832
  function logDirOf(project) {
77395
- return join42(homedir16(), ".claude", "projects", project.cwdSlug);
77833
+ return join44(homedir17(), ".claude", "projects", project.cwdSlug);
77396
77834
  }
77397
77835
  function resolveTranscriptPath(project, sessionId) {
77398
- const path3 = join42(logDirOf(project), `${sessionId}.jsonl`);
77836
+ const path3 = join44(logDirOf(project), `${sessionId}.jsonl`);
77399
77837
  try {
77400
- if (statSync18(path3).size > 0) return path3;
77838
+ if (statSync19(path3).size > 0) return path3;
77401
77839
  } catch {
77402
77840
  }
77403
77841
  const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
77404
- return stranded ? join42(stranded, `${sessionId}.jsonl`) : path3;
77842
+ return stranded ? join44(stranded, `${sessionId}.jsonl`) : path3;
77405
77843
  }
77406
77844
  function projectCodexTranscript(raw2, identityPrefix = "codex") {
77407
77845
  const out = [];
@@ -77711,7 +78149,7 @@ function countBoundedStart(messages) {
77711
78149
  let boundary = -1;
77712
78150
  for (let i = tail - 1; i >= 0; i--) {
77713
78151
  const m = messages[i];
77714
- if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
78152
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction && !m.sessionPeer) {
77715
78153
  boundary = i;
77716
78154
  break;
77717
78155
  }
@@ -77740,7 +78178,7 @@ function latestTranscriptWindow(messages) {
77740
78178
  );
77741
78179
  if (tools.length === 0) continue;
77742
78180
  pinned.push({
77743
- sourceId: `pinned-bg:${createHash15("sha256").update(message.sourceId).digest("base64url").slice(0, 24)}`,
78181
+ sourceId: `pinned-bg:${createHash16("sha256").update(message.sourceId).digest("base64url").slice(0, 24)}`,
77744
78182
  pinnedFromSourceId: message.sourceId,
77745
78183
  role: "assistant",
77746
78184
  text: "",
@@ -78536,7 +78974,7 @@ function fixedSnapshot(source) {
78536
78974
  };
78537
78975
  let allBytes;
78538
78976
  const fileKey = `${before.dev}:${before.ino}:${Math.trunc(before.birthtimeMs)}`;
78539
- const transcriptKey = createHash15("sha256").update(`${source.slug}\0${source.sessionId}\0${source.nativeId}\0${source.backend}\0${source.runtimeGeneration}\0${fileKey}`).digest("base64url").slice(0, 32);
78977
+ const transcriptKey = createHash16("sha256").update(`${source.slug}\0${source.sessionId}\0${source.nativeId}\0${source.backend}\0${source.runtimeGeneration}\0${fileKey}`).digest("base64url").slice(0, 32);
78540
78978
  let rawText;
78541
78979
  return {
78542
78980
  ...source,
@@ -78569,7 +79007,7 @@ function projectSnapshot(snapshot) {
78569
79007
  return retireStaleQueuedBubbles([...entry.fold.allMessages()]);
78570
79008
  }
78571
79009
  function digestPrefix(bytes, length = bytes.length) {
78572
- return createHash15("sha256").update(bytes.subarray(0, length)).digest("base64url");
79010
+ return createHash16("sha256").update(bytes.subarray(0, length)).digest("base64url");
78573
79011
  }
78574
79012
  function fullDigest(snapshot) {
78575
79013
  const key = `${snapshot.fileKey}\0${snapshot.size}`;
@@ -78579,7 +79017,7 @@ function fullDigest(snapshot) {
78579
79017
  fullDigestCache.set(key, hit);
78580
79018
  return hit;
78581
79019
  }
78582
- const hash = createHash15("sha256");
79020
+ const hash = createHash16("sha256");
78583
79021
  for (let at2 = 0; at2 < snapshot.size; ) {
78584
79022
  const buf = snapshot.readRange(at2, Math.min(TRANSCRIPT_DECODE_WINDOW, snapshot.size - at2));
78585
79023
  if (buf.length === 0) break;
@@ -78633,7 +79071,7 @@ function pageProjectedTranscript(messages, anchor, limits = {}) {
78633
79071
  let boundary = 0;
78634
79072
  for (let i = anchor - 1; i >= 0; i--) {
78635
79073
  const m = messages[i];
78636
- if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
79074
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction && !m.sessionPeer) {
78637
79075
  boundary = i;
78638
79076
  break;
78639
79077
  }
@@ -78662,7 +79100,7 @@ function emptyTranscriptPage(source) {
78662
79100
  beforeCursor: null,
78663
79101
  hasEarlier: false,
78664
79102
  reachedTurnBoundary: true,
78665
- transcriptKey: createHash15("sha256").update(keySeed).digest("base64url").slice(0, 32),
79103
+ transcriptKey: createHash16("sha256").update(keySeed).digest("base64url").slice(0, 32),
78666
79104
  editedFiles: []
78667
79105
  };
78668
79106
  }
@@ -78741,7 +79179,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
78741
79179
  }
78742
79180
  function readCodexTranscriptFile(absPath, nativeId = absPath) {
78743
79181
  try {
78744
- return parseCodexTranscript(readFileSync29(absPath, "utf8"), `codex:${nativeId}`);
79182
+ return parseCodexTranscript(readFileSync30(absPath, "utf8"), `codex:${nativeId}`);
78745
79183
  } catch {
78746
79184
  return [];
78747
79185
  }
@@ -78802,14 +79240,15 @@ function projectTranscriptPeerNames(messages, lookup) {
78802
79240
  });
78803
79241
  return changed ? out : messages;
78804
79242
  }
78805
- function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
79243
+ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup, sessionPeerLookup) {
78806
79244
  const named = peerLookup ? projectTranscriptPeerNames(page.messages, peerLookup) : page.messages;
78807
- return { ...page, messages: projectTranscriptAgentLifecycles(named, lookup) };
79245
+ const sent = sessionPeerLookup ? projectTranscriptSessionPeers(named, sessionPeerLookup) : named;
79246
+ return { ...page, messages: projectTranscriptAgentLifecycles(sent, lookup) };
78808
79247
  }
78809
79248
  function readTranscriptFile(absPath) {
78810
79249
  try {
78811
- const pathKey = createHash15("sha256").update(absPath).digest("base64url").slice(0, 16);
78812
- return parseTranscript(readFileSync29(absPath, "utf8"), `claude-file:${pathKey}`);
79250
+ const pathKey = createHash16("sha256").update(absPath).digest("base64url").slice(0, 16);
79251
+ return parseTranscript(readFileSync30(absPath, "utf8"), `claude-file:${pathKey}`);
78813
79252
  } catch {
78814
79253
  return [];
78815
79254
  }
@@ -78819,6 +79258,7 @@ var init_transcript = __esm({
78819
79258
  "packages/server/src/transcript.ts"() {
78820
79259
  "use strict";
78821
79260
  init_src();
79261
+ init_session_peer();
78822
79262
  init_types2();
78823
79263
  init_delivery_ledger();
78824
79264
  init_edited_files();
@@ -79380,10 +79820,10 @@ var init_background_shell_output = __esm({
79380
79820
 
79381
79821
  // packages/server/src/frizz-md.ts
79382
79822
  import { execFileSync as execFileSync6 } from "node:child_process";
79383
- import { existsSync as existsSync20, writeFileSync as writeFileSync16 } from "node:fs";
79384
- import { join as join43 } from "node:path";
79823
+ import { existsSync as existsSync20, writeFileSync as writeFileSync17 } from "node:fs";
79824
+ import { join as join45 } from "node:path";
79385
79825
  function frizzMdPath(projectDir) {
79386
- return join43(projectDir, "FRIZZ.md");
79826
+ return join45(projectDir, "FRIZZ.md");
79387
79827
  }
79388
79828
  function git2(args, cwd) {
79389
79829
  try {
@@ -79408,7 +79848,7 @@ function frizzMdStatus(projectDir, storage) {
79408
79848
  function createFrizzMd(projectDir, answers) {
79409
79849
  const path3 = frizzMdPath(projectDir);
79410
79850
  try {
79411
- writeFileSync16(path3, composeFrizzMd(answers, defaultBranch(projectDir)), { encoding: "utf8", flag: "wx" });
79851
+ writeFileSync17(path3, composeFrizzMd(answers, defaultBranch(projectDir)), { encoding: "utf8", flag: "wx" });
79412
79852
  } catch (error) {
79413
79853
  if (error.code === "EEXIST") throw new Error("This project already has a FRIZZ.md.");
79414
79854
  throw error;
@@ -79428,12 +79868,12 @@ var init_frizz_md2 = __esm({
79428
79868
  });
79429
79869
 
79430
79870
  // packages/server/src/image-header.ts
79431
- import { closeSync as closeSync17, openSync as openSync18, readSync as readSync10, statSync as statSync19 } from "node:fs";
79871
+ import { closeSync as closeSync17, openSync as openSync18, readSync as readSync10, statSync as statSync20 } from "node:fs";
79432
79872
  import { extname as extname2 } from "node:path";
79433
79873
  function readHead3(path3, budget) {
79434
79874
  let fd2;
79435
79875
  try {
79436
- const size = statSync19(path3).size;
79876
+ const size = statSync20(path3).size;
79437
79877
  if (size === 0) return void 0;
79438
79878
  const buffer = Buffer.alloc(Math.min(size, budget));
79439
79879
  fd2 = openSync18(path3, "r");
@@ -79811,12 +80251,12 @@ function run(argv) {
79811
80251
  });
79812
80252
 
79813
80253
  // packages/server/src/router.ts
79814
- import { readFileSync as readFileSync30, statSync as statSync20 } from "node:fs";
79815
- import { join as join44, resolve as resolve10 } from "node:path";
80254
+ import { readFileSync as readFileSync31, statSync as statSync21 } from "node:fs";
80255
+ import { join as join46, resolve as resolve10 } from "node:path";
79816
80256
  import { randomUUID as randomUUID17 } from "node:crypto";
79817
- import { basename as basename9, dirname as dirname20 } from "node:path";
79818
- import { existsSync as existsSync21, mkdirSync as mkdirSync22, rmSync as rmSync12, writeFileSync as writeFileSync17 } from "node:fs";
79819
- import { homedir as homedir17 } from "node:os";
80257
+ import { basename as basename9, dirname as dirname22 } from "node:path";
80258
+ import { existsSync as existsSync21, mkdirSync as mkdirSync24, rmSync as rmSync12, writeFileSync as writeFileSync18 } from "node:fs";
80259
+ import { homedir as homedir18 } from "node:os";
79820
80260
  function validateGithubDispatchProfile(input, codexModels) {
79821
80261
  if (input.backend === "acp") return;
79822
80262
  if (input.effort === void 0) throw new Error(`Unsupported ${input.backend} model/effort pair: ${input.model} / (no effort)`);
@@ -79986,20 +80426,21 @@ function projectCard(entry, stale) {
79986
80426
  iconIsCustom: entry.iconSource === "custom" ? true : void 0
79987
80427
  };
79988
80428
  }
79989
- function addProjectAtPath(input, home = homedir17()) {
80429
+ function addProjectAtPath(input, home = homedir18()) {
79990
80430
  const typed = input.trim();
79991
80431
  if (!typed) throw new Error("Enter a folder path.");
79992
- const expanded = typed === "~" || typed.startsWith("~/") ? join44(home, typed.slice(1)) : typed;
80432
+ const expanded = typed === "~" || typed.startsWith("~/") ? join46(home, typed.slice(1)) : typed;
79993
80433
  const absolute = resolve10(expanded);
79994
80434
  let stats;
79995
80435
  try {
79996
- stats = statSync20(absolute);
80436
+ stats = statSync21(absolute);
79997
80437
  } catch {
79998
80438
  throw new Error(`No folder at ${absolute}`);
79999
80439
  }
80000
80440
  if (!stats.isDirectory()) throw new Error(`That is a file, not a folder: ${absolute}`);
80001
80441
  const root = chosenProjectRoot(absolute, home);
80002
80442
  if (isHomeDirectory(root, home)) throw new Error("The home folder cannot be a project \u2014 choose a folder inside it.");
80443
+ refuseWorldWritableProject(root);
80003
80444
  const id2 = ensureProjectIdFile(root, home, existingProjectId(root));
80004
80445
  const remoteOwner = resolveProjectLabel(root)?.split("/")[0];
80005
80446
  let registered = registerProject({ dir: root, id: id2, remoteOwner }, home);
@@ -80009,6 +80450,9 @@ function addProjectAtPath(input, home = homedir17()) {
80009
80450
  if (!registered.entry) throw new Error("Could not register that folder.");
80010
80451
  return projectCard(registered.entry, false);
80011
80452
  }
80453
+ function addedProject(card) {
80454
+ return { project: { id: card.id, dir: card.path } };
80455
+ }
80012
80456
  function storeProjectIcon(id2, name, bytes) {
80013
80457
  const extension2 = name.toLowerCase().match(/\.([a-z0-9]+)$/u)?.[1];
80014
80458
  if (!extension2 || !PROJECT_ICON_EXTENSIONS.includes(extension2)) {
@@ -80018,8 +80462,8 @@ function storeProjectIcon(id2, name, bytes) {
80018
80462
  if (!entry) throw new Error("No such project.");
80019
80463
  if (bytes.length === 0) throw new Error("That file is empty.");
80020
80464
  const path3 = customIconPath(id2, `.${extension2}`);
80021
- mkdirSync22(dirname20(path3), { recursive: true });
80022
- writeFileSync17(path3, bytes);
80465
+ mkdirSync24(dirname22(path3), { recursive: true });
80466
+ writeFileSync18(path3, bytes);
80023
80467
  if (!imageDimensions(path3)) {
80024
80468
  rmSync12(path3, { force: true });
80025
80469
  throw new Error("That file does not look like an image Frizz can draw.");
@@ -80040,7 +80484,7 @@ function iconPicker(id2) {
80040
80484
  function setProjectIconFromFile(id2, file) {
80041
80485
  let bytes;
80042
80486
  try {
80043
- bytes = readFileSync30(file);
80487
+ bytes = readFileSync31(file);
80044
80488
  } catch {
80045
80489
  throw new Error("That file could not be read.");
80046
80490
  }
@@ -80049,7 +80493,7 @@ function setProjectIconFromFile(id2, file) {
80049
80493
  }
80050
80494
  function mergeSubAgentSteers(messages, steers) {
80051
80495
  if (!steers.length) return messages;
80052
- const merged = [...messages];
80496
+ const merged2 = [...messages];
80053
80497
  for (const steer of steers) {
80054
80498
  const at2 = new Date(steer.sent_at).toISOString();
80055
80499
  const duplicate = messages.some((message2) => {
@@ -80066,15 +80510,17 @@ function mergeSubAgentSteers(messages, steers) {
80066
80510
  parts: [{ kind: "text", text: steer.message }],
80067
80511
  at: at2
80068
80512
  };
80069
- const next = merged.findIndex((candidate) => candidate.at !== void 0 && Date.parse(candidate.at) > steer.sent_at);
80070
- if (next === -1) merged.push(message);
80071
- else merged.splice(next, 0, message);
80513
+ const next = merged2.findIndex((candidate) => candidate.at !== void 0 && Date.parse(candidate.at) > steer.sent_at);
80514
+ if (next === -1) merged2.push(message);
80515
+ else merged2.splice(next, 0, message);
80072
80516
  }
80073
- return merged;
80517
+ return merged2;
80074
80518
  }
80075
80519
  function createRouter(ctx) {
80076
- const frizzDir = join44(ctx.project.dir, ".frizz");
80520
+ const frizzDir = join46(ctx.project.dir, ".frizz");
80077
80521
  const openRoots = openableFileRoots(ctx.project);
80522
+ const thisProject = { id: ctx.project.id, dir: ctx.project.dir };
80523
+ const audit = (action, base, run3, onResult) => audited(ctx.audit, action, base, run3, { onResult, servedBy: ctx.project.id });
80078
80524
  function isAutoTitledSession(slug) {
80079
80525
  return ctx.storage.getSession(slug)?.title_auto === 1;
80080
80526
  }
@@ -80167,6 +80613,16 @@ function createRouter(ctx) {
80167
80613
  return void 0;
80168
80614
  }
80169
80615
  }
80616
+ function answerDigest(slug, answer) {
80617
+ const q2 = ctx.storage.getThreadQuestion(answer.questionId);
80618
+ if (q2?.thread_slug === slug && parseQuestionSpec(q2.spec)?.secret) return { questionId: answer.questionId, secret: true };
80619
+ return {
80620
+ questionId: answer.questionId,
80621
+ ...answer.chosen.length > 0 ? { chosen: textDigest(answer.chosen.join("\n")) } : {},
80622
+ ...answer.text ? { text: textDigest(answer.text) } : {},
80623
+ ...answer.followUps?.length ? { followUps: answer.followUps.length } : {}
80624
+ };
80625
+ }
80170
80626
  function parseStoredAnswer(raw2) {
80171
80627
  if (!raw2) return void 0;
80172
80628
  try {
@@ -80416,7 +80872,7 @@ function createRouter(ctx) {
80416
80872
  output: TranscriptPage,
80417
80873
  handler: async ({ input }) => {
80418
80874
  const page = readLatestThreadTranscriptPage(ctx.project, ctx.storage, input.slug, ctx.backendFor);
80419
- return retireOpsInPage(input.slug, projectTranscriptPageAgentLifecycles(page, (id2) => ctx.tailer.subAgent(input.slug, id2), (taskId) => ctx.tailer.subAgentByTaskId?.(input.slug, taskId)));
80875
+ return retireOpsInPage(input.slug, projectTranscriptPageAgentLifecycles(page, (id2) => ctx.tailer.subAgent(input.slug, id2), (taskId) => ctx.tailer.subAgentByTaskId?.(input.slug, taskId), ctx.sessionPeerThread));
80420
80876
  }
80421
80877
  }),
80422
80878
  // One bounded backward step through the canonical projected transcript. The cursor excludes the
@@ -80426,7 +80882,7 @@ function createRouter(ctx) {
80426
80882
  output: TranscriptPage,
80427
80883
  handler: async ({ input }) => {
80428
80884
  const page = readEarlierThreadTranscriptPage(ctx.project, ctx.storage, input.slug, input.cursor, ctx.backendFor);
80429
- return retireOpsInPage(input.slug, projectTranscriptPageAgentLifecycles(page, (id2) => ctx.tailer.subAgent(input.slug, id2), (taskId) => ctx.tailer.subAgentByTaskId?.(input.slug, taskId)));
80885
+ return retireOpsInPage(input.slug, projectTranscriptPageAgentLifecycles(page, (id2) => ctx.tailer.subAgent(input.slug, id2), (taskId) => ctx.tailer.subAgentByTaskId?.(input.slug, taskId), ctx.sessionPeerThread));
80430
80886
  }
80431
80887
  }),
80432
80888
  // The thread's ANSWERED registered questions, each with its answer, so the transcript can keep the
@@ -80471,29 +80927,40 @@ function createRouter(ctx) {
80471
80927
  interactionResolve: mutation({
80472
80928
  input: ResolveInteractionInput,
80473
80929
  output: ResolveInteractionResult,
80474
- handler: async ({ input }) => {
80475
- const scope = interactionScope(input.slug, input.sessionId);
80476
- const delivery2 = ctx.interactions.providerDelivery(scope, input.interactionId);
80477
- let result;
80478
- if (delivery2) {
80479
- if (!ctx.codexAppServer || !ctx.codexAppServer.ownsInteraction(scope, input.interactionId)) {
80480
- throw new Error("provider-backed interaction is unavailable until its provider bridge reconnects");
80481
- }
80482
- const providerResult = await ctx.codexAppServer.resolveInteraction(scope, input);
80483
- if (!providerResult) throw new Error("provider-backed interaction lost its durable delivery owner");
80484
- result = {
80485
- effect: providerResult.effect === "already-sent" ? "already-queued" : providerResult.effect,
80486
- interaction: providerResult.interaction
80930
+ handler: ({ input }) => audit(
80931
+ "interactionResolve",
80932
+ {
80933
+ project: thisProject,
80934
+ thread: input.slug,
80935
+ // The decision the operator picked and how many values rode with it — never the values, which
80936
+ // can be a secret the interaction asked for.
80937
+ detail: { interactionId: input.interactionId, decisionId: input.decisionId, values: Object.keys(input.values ?? {}).length }
80938
+ },
80939
+ async () => {
80940
+ const scope = interactionScope(input.slug, input.sessionId);
80941
+ const delivery2 = ctx.interactions.providerDelivery(scope, input.interactionId);
80942
+ let result;
80943
+ if (delivery2) {
80944
+ if (!ctx.codexAppServer || !ctx.codexAppServer.ownsInteraction(scope, input.interactionId)) {
80945
+ throw new Error("provider-backed interaction is unavailable until its provider bridge reconnects");
80946
+ }
80947
+ const providerResult = await ctx.codexAppServer.resolveInteraction(scope, input);
80948
+ if (!providerResult) throw new Error("provider-backed interaction lost its durable delivery owner");
80949
+ result = {
80950
+ effect: providerResult.effect === "already-sent" ? "already-queued" : providerResult.effect,
80951
+ interaction: providerResult.interaction
80952
+ };
80953
+ } else {
80954
+ result = ctx.interactions.resolve(scope, input);
80955
+ }
80956
+ const latest = ctx.interactions.get(scope, result.interaction.id) ?? result.interaction;
80957
+ return {
80958
+ effect: latest.lifecycle === "resolved" && (result.effect === "queued" || result.effect === "already-queued") ? "resolved" : result.effect,
80959
+ interaction: interactionForRead(scope, latest)
80487
80960
  };
80488
- } else {
80489
- result = ctx.interactions.resolve(scope, input);
80490
- }
80491
- const latest = ctx.interactions.get(scope, result.interaction.id) ?? result.interaction;
80492
- return {
80493
- effect: latest.lifecycle === "resolved" && (result.effect === "queued" || result.effect === "already-queued") ? "resolved" : result.effect,
80494
- interaction: interactionForRead(scope, latest)
80495
- };
80496
- }
80961
+ },
80962
+ (result) => ({ detail: { effect: result.effect } })
80963
+ )
80497
80964
  }),
80498
80965
  interactionCancel: mutation({
80499
80966
  input: CancelInteractionInput,
@@ -80562,32 +81029,36 @@ function createRouter(ctx) {
80562
81029
  subAgentSteer: mutation({
80563
81030
  input: external_exports.object({ slug: ThreadSlug, id: external_exports.string(), message: external_exports.string().min(1), deliveryId: external_exports.string().min(1).max(200).optional() }).strict(),
80564
81031
  output: external_exports.object({ delivered: external_exports.boolean() }),
80565
- handler: async ({ input }) => {
80566
- const target = subAgentSteerable(input.slug, input.id);
80567
- if (target.sessionId === null) {
80568
- throw new Error(target.note ?? "This sub-agent is no longer running, so it can't be steered");
81032
+ handler: ({ input }) => audit(
81033
+ "subAgentSteer",
81034
+ { project: thisProject, thread: input.slug, detail: { subAgent: input.id, message: textDigest(input.message) } },
81035
+ async () => {
81036
+ const target = subAgentSteerable(input.slug, input.id);
81037
+ if (target.sessionId === null) {
81038
+ throw new Error(target.note ?? "This sub-agent is no longer running, so it can't be steered");
81039
+ }
81040
+ const bridge = ctx.claudeBroker;
81041
+ if (!bridge) throw new Error("Claude session broker is unavailable; cannot steer this sub-agent");
81042
+ const deliveryId = input.deliveryId ?? randomUUID17();
81043
+ const sentAtMs = Date.now();
81044
+ await bridge.steerSubAgent({
81045
+ threadSlug: input.slug,
81046
+ sessionId: target.sessionId,
81047
+ subAgentId: input.id,
81048
+ text: input.message,
81049
+ deliveryId
81050
+ });
81051
+ ctx.storage.recordSubAgentSteer({
81052
+ slug: input.slug,
81053
+ subAgentId: input.id,
81054
+ deliveryId,
81055
+ message: input.message,
81056
+ sentAtMs
81057
+ });
81058
+ ctx.board.refresh();
81059
+ return { delivered: true };
80569
81060
  }
80570
- const bridge = ctx.claudeBroker;
80571
- if (!bridge) throw new Error("Claude session broker is unavailable; cannot steer this sub-agent");
80572
- const deliveryId = input.deliveryId ?? randomUUID17();
80573
- const sentAtMs = Date.now();
80574
- await bridge.steerSubAgent({
80575
- threadSlug: input.slug,
80576
- sessionId: target.sessionId,
80577
- subAgentId: input.id,
80578
- text: input.message,
80579
- deliveryId
80580
- });
80581
- ctx.storage.recordSubAgentSteer({
80582
- slug: input.slug,
80583
- subAgentId: input.id,
80584
- deliveryId,
80585
- message: input.message,
80586
- sentAtMs
80587
- });
80588
- ctx.board.refresh();
80589
- return { delivered: true };
80590
- }
81061
+ )
80591
81062
  }),
80592
81063
  subAgentStop: mutation({
80593
81064
  input: external_exports.object({ slug: ThreadSlug, id: external_exports.string() }).strict(),
@@ -80736,18 +81207,40 @@ function createRouter(ctx) {
80736
81207
  // Omitted ⇒ the dispatcher defaults to "claude", so an old client (no backend field) is
80737
81208
  // byte-identical. The resume path needs NO analog — resume reads the backend from the row's
80738
81209
  // `backend` column (backendFor(row.backend)), which dispatch already stamped for a codex thread.
80739
- handler: ({ input }) => ctx.dispatcher.dispatch(input, { backend: input.backend })
81210
+ handler: ({ input }) => audit(
81211
+ "dispatch",
81212
+ {
81213
+ project: thisProject,
81214
+ detail: { prompt: textDigest(input.prompt), backend: input.backend ?? "claude", model: input.model ?? null, effort: input.effort ?? null }
81215
+ },
81216
+ () => ctx.dispatcher.dispatch(input, { backend: input.backend }),
81217
+ (result) => ({ thread: result.slug, detail: { sessionId: result.sessionId } })
81218
+ )
80740
81219
  }),
80741
81220
  // Cold-adopt a pre-existing thread (no session row): spawn a fresh worker on its file.
80742
81221
  adoptThread: mutation({
80743
81222
  input: AdoptThreadInput,
80744
81223
  output: AdoptThreadResult,
80745
- handler: ({ input }) => ctx.dispatcher.adopt(input.slug, input.message)
81224
+ handler: ({ input }) => audit(
81225
+ "adoptThread",
81226
+ { project: thisProject, thread: input.slug, detail: input.message ? { message: textDigest(input.message) } : void 0 },
81227
+ () => ctx.dispatcher.adopt(input.slug, input.message),
81228
+ (result) => ({ detail: { sessionId: result.sessionId } })
81229
+ )
80746
81230
  }),
80747
81231
  followUp: mutation({
80748
81232
  input: FollowUpInput,
80749
81233
  // Wrapped so a delivery that throws keeps the operator's words — see keepFailedFollowUp.
80750
- handler: ({ input }) => keepFailedFollowUp(input, async (openWriteAhead) => {
81234
+ handler: ({ input }) => audit("followUp", {
81235
+ project: thisProject,
81236
+ thread: input.slug,
81237
+ detail: {
81238
+ sessionId: input.sessionId,
81239
+ message: textDigest(input.message),
81240
+ ...input.freshProcess ? { freshProcess: true } : {},
81241
+ ...input.interrupt ? { interrupt: true } : {}
81242
+ }
81243
+ }, () => keepFailedFollowUp(input, async (openWriteAhead) => {
80751
81244
  await promoteExternalSession(input.slug, input.sessionId);
80752
81245
  const row = currentOwnedSession(input.slug, input.sessionId);
80753
81246
  if (hasPendingPermissionChange(row)) {
@@ -80905,7 +81398,7 @@ ${gapNote}` : input.message;
80905
81398
  appendDelivery(ctx.storage, input.slug, { id: input.deliveryId, text: input.message });
80906
81399
  }
80907
81400
  ctx.board.refresh();
80908
- })
81401
+ }))
80909
81402
  }),
80910
81403
  // The × on a FAILED send's bubble, and the second half of its Edit (the client has already put the
80911
81404
  // words back into the prompt box). The only way a failed entry leaves the ledger by hand — nothing
@@ -81696,27 +82189,32 @@ Say which way you went and why in your write-up, so the human can course-correct
81696
82189
  answerQuestions: mutation({
81697
82190
  input: AnswerQuestionsInput,
81698
82191
  output: AnswerQuestionsResult,
81699
- handler: async ({ input }) => {
81700
- const now = Date.now();
81701
- const answered = [];
81702
- for (const answer of input.answers) {
81703
- const q2 = ctx.storage.getThreadQuestion(answer.questionId);
81704
- if (!q2 || q2.thread_slug !== input.slug || q2.state !== "open") continue;
81705
- let stored = answer;
81706
- if (parseQuestionSpec(q2.spec)?.secret) {
81707
- const value = answer.text?.trim() ?? "";
81708
- if (!value) continue;
81709
- const path3 = serveSecret(ctx.project.stateDir, input.slug, q2.id, value);
81710
- stored = { questionId: answer.questionId, question: answer.question, chosen: [], text: secretAnswerText(path3) };
82192
+ handler: ({ input }) => audit(
82193
+ "answerQuestions",
82194
+ () => ({ project: thisProject, thread: input.slug, detail: { answers: input.answers.map((answer) => answerDigest(input.slug, answer)) } }),
82195
+ async () => {
82196
+ const now = Date.now();
82197
+ const answered = [];
82198
+ for (const answer of input.answers) {
82199
+ const q2 = ctx.storage.getThreadQuestion(answer.questionId);
82200
+ if (!q2 || q2.thread_slug !== input.slug || q2.state !== "open") continue;
82201
+ let stored = answer;
82202
+ if (parseQuestionSpec(q2.spec)?.secret) {
82203
+ const value = answer.text?.trim() ?? "";
82204
+ if (!value) continue;
82205
+ const path3 = serveSecret(ctx.project.stateDir, input.slug, q2.id, value);
82206
+ stored = { questionId: answer.questionId, question: answer.question, chosen: [], text: secretAnswerText(path3) };
82207
+ }
82208
+ if (ctx.storage.answerThreadQuestion(answer.questionId, JSON.stringify(stored), now)) answered.push(answer.questionId);
81711
82209
  }
81712
- if (ctx.storage.answerThreadQuestion(answer.questionId, JSON.stringify(stored), now)) answered.push(answer.questionId);
81713
- }
81714
- if (answered.length > 0) {
81715
- ctx.board.refresh();
81716
- ctx.scheduler.kick();
81717
- }
81718
- return { answered, open: openQuestionViews(input.slug) };
81719
- }
82210
+ if (answered.length > 0) {
82211
+ ctx.board.refresh();
82212
+ ctx.scheduler.kick();
82213
+ }
82214
+ return { answered, open: openQuestionViews(input.slug) };
82215
+ },
82216
+ (result) => ({ detail: { answered: result.answered } })
82217
+ )
81720
82218
  }),
81721
82219
  dismissQuestions: mutation({
81722
82220
  input: DismissQuestionsInput,
@@ -82232,16 +82730,22 @@ Say which way you went and why in your write-up, so the human can course-correct
82232
82730
  /** Live worker daemons this actually killed — 0 unless `deleteData`. Reported, not guessed at. */
82233
82731
  stoppedWorkers: external_exports.number().int().nonnegative()
82234
82732
  }),
82235
- handler: async ({ input }) => {
82236
- const entry = findById(input.id);
82237
- if (!entry) return { removed: false, deletedData: false, stoppedWorkers: 0 };
82238
- if (ctx.launchProjectId === entry.id) {
82239
- throw new Error("Frizz is serving from this project, so it cannot be deleted. Restart Frizz from another folder first.");
82240
- }
82241
- const deleteData = input.deleteData === true;
82242
- const { stoppedWorkers } = await ctx.teardownProject?.(entry.id, { stopWorkers: deleteData, deleteState: deleteData }) ?? { closed: false, stoppedWorkers: 0 };
82243
- return { removed: forgetProject(entry.id), deletedData: deleteData, stoppedWorkers };
82244
- }
82733
+ handler: async ({ input }) => audit(
82734
+ "projectRemove",
82735
+ // Read BEFORE the removal, which forgets the very entry that names the folder.
82736
+ () => ({ project: { id: input.id, dir: findById(input.id)?.path ?? null }, detail: { deleteData: input.deleteData === true } }),
82737
+ async () => {
82738
+ const entry = findById(input.id);
82739
+ if (!entry) return { removed: false, deletedData: false, stoppedWorkers: 0 };
82740
+ if (ctx.launchProjectId === entry.id) {
82741
+ throw new Error("Frizz is serving from this project, so it cannot be deleted. Restart Frizz from another folder first.");
82742
+ }
82743
+ const deleteData = input.deleteData === true;
82744
+ const { stoppedWorkers } = await ctx.teardownProject?.(entry.id, { stopWorkers: deleteData, deleteState: deleteData }) ?? { closed: false, stoppedWorkers: 0 };
82745
+ return { removed: forgetProject(entry.id), deletedData: deleteData, stoppedWorkers };
82746
+ },
82747
+ (result) => ({ detail: result })
82748
+ )
82245
82749
  }),
82246
82750
  /**
82247
82751
  * Give a project an icon of the operator's choosing.
@@ -82365,7 +82869,13 @@ Say which way you went and why in your write-up, so the human can course-correct
82365
82869
  const picked = await pickDirectory();
82366
82870
  if (picked.kind !== "unavailable") warmDirectoryPicker();
82367
82871
  if (picked.kind !== "picked") return picked;
82368
- return { kind: "picked", project: addProjectAtPath(picked.path) };
82872
+ const project = await audit(
82873
+ "projectPick",
82874
+ { project: { id: null, dir: picked.path }, detail: { path: picked.path } },
82875
+ () => addProjectAtPath(picked.path),
82876
+ addedProject
82877
+ );
82878
+ return { kind: "picked", project };
82369
82879
  }
82370
82880
  }),
82371
82881
  /**
@@ -82388,7 +82898,7 @@ Say which way you went and why in your write-up, so the human can course-correct
82388
82898
  projectAdd: mutation({
82389
82899
  input: external_exports.object({ path: external_exports.string().min(1) }),
82390
82900
  output: ProjectCard,
82391
- handler: async ({ input }) => addProjectAtPath(input.path)
82901
+ handler: async ({ input }) => audit("projectAdd", { project: { id: null, dir: input.path }, detail: { path: input.path } }, () => addProjectAtPath(input.path), addedProject)
82392
82902
  }),
82393
82903
  /**
82394
82904
  * Find which project owns a thread slug.
@@ -82533,7 +83043,12 @@ Say which way you went and why in your write-up, so the human can course-correct
82533
83043
  const hydrated = it2.kind === "issue" ? await hydrateIssue(repo, it2.number) : await hydratePr(repo, it2.number);
82534
83044
  const prompt = renderGithubPrompt(template, repo, hydrated, slug, it2.kind);
82535
83045
  const request = githubDispatcherRequest(input, { prompt, title, slug });
82536
- const res = await ctx.dispatcher.dispatch(request.payload, request.options);
83046
+ const res = await audit(
83047
+ "githubDispatch",
83048
+ { project: thisProject, detail: { repo, kind: it2.kind, number: it2.number, prompt: textDigest(prompt) } },
83049
+ () => ctx.dispatcher.dispatch(request.payload, request.options),
83050
+ (result) => ({ thread: result.slug, detail: { sessionId: result.sessionId } })
83051
+ );
82537
83052
  dispatched.push({ number: it2.number, kind: it2.kind, slug: res.slug });
82538
83053
  } catch (e14) {
82539
83054
  failed.push({ number: it2.number, kind: it2.kind, error: e14.message.slice(0, 120) });
@@ -82574,6 +83089,7 @@ var init_router5 = __esm({
82574
83089
  init_claude_models();
82575
83090
  init_claude_model_upgrade();
82576
83091
  init_logging();
83092
+ init_audit();
82577
83093
  init_codex();
82578
83094
  init_quota();
82579
83095
  init_auth_status();
@@ -82656,9 +83172,9 @@ var init_compression = __esm({
82656
83172
  });
82657
83173
 
82658
83174
  // packages/server/src/project-icon.ts
82659
- import { readFileSync as readFileSync31, readdirSync as readdirSync13, realpathSync as realpathSync13, statSync as statSync21 } from "node:fs";
82660
- import { homedir as homedir18 } from "node:os";
82661
- import { basename as basename10, dirname as dirname21, extname as extname3, join as join45, relative as relative2, sep as sep4 } from "node:path";
83175
+ import { readFileSync as readFileSync32, readdirSync as readdirSync13, realpathSync as realpathSync13, statSync as statSync22 } from "node:fs";
83176
+ import { homedir as homedir19 } from "node:os";
83177
+ import { basename as basename10, dirname as dirname23, extname as extname3, join as join47, relative as relative2, sep as sep4 } from "node:path";
82662
83178
  function normalizeStem(stem) {
82663
83179
  const sized = stem.toLowerCase().replace(/[@-]\d+(\.\d+)?x(\d+)?$/u, "").replace(/[-_]\d{2,4}$/u, "");
82664
83180
  const normalized = sized.replace(VARIANT_SUFFIX, "");
@@ -82676,7 +83192,7 @@ function sizeScore(dimensions) {
82676
83192
  return Math.max(0, Math.min(40, Math.round(Math.log2(edge / 24) * 8)));
82677
83193
  }
82678
83194
  function under(root, relativeDirectory) {
82679
- return relativeDirectory ? join45(root, relativeDirectory) : root;
83195
+ return relativeDirectory ? join47(root, relativeDirectory) : root;
82680
83196
  }
82681
83197
  function listFiles(directory) {
82682
83198
  try {
@@ -82698,19 +83214,19 @@ function iconDirectories(root) {
82698
83214
  for (const asset of ASSET_DIRECTORIES) directories.push(under(root, asset));
82699
83215
  for (const host of HOST_DIRECTORIES) {
82700
83216
  if (!children2.has(host)) continue;
82701
- for (const asset of ASSET_DIRECTORIES) directories.push(under(join45(root, host), asset));
83217
+ for (const asset of ASSET_DIRECTORIES) directories.push(under(join47(root, host), asset));
82702
83218
  }
82703
83219
  for (const extra of EXTRA_DIRECTORIES) directories.push(under(root, extra));
82704
83220
  for (const container of WORKSPACE_CONTAINERS) {
82705
83221
  if (!children2.has(container)) continue;
82706
83222
  let members;
82707
83223
  try {
82708
- members = readdirSync13(join45(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
83224
+ members = readdirSync13(join47(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
82709
83225
  } catch {
82710
83226
  continue;
82711
83227
  }
82712
83228
  for (const member of members) {
82713
- for (const asset of ASSET_DIRECTORIES) directories.push(under(join45(root, container, member), asset));
83229
+ for (const asset of ASSET_DIRECTORIES) directories.push(under(join47(root, container, member), asset));
82714
83230
  }
82715
83231
  }
82716
83232
  return [...new Set(directories)];
@@ -82718,13 +83234,13 @@ function iconDirectories(root) {
82718
83234
  function manifestIcons(manifestPath) {
82719
83235
  let parsed;
82720
83236
  try {
82721
- parsed = JSON.parse(readFileSync31(manifestPath, "utf8"));
83237
+ parsed = JSON.parse(readFileSync32(manifestPath, "utf8"));
82722
83238
  } catch {
82723
83239
  return [];
82724
83240
  }
82725
83241
  if (!Array.isArray(parsed?.icons)) return [];
82726
- const directory = dirname21(manifestPath);
82727
- return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) => join45(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
83242
+ const directory = dirname23(manifestPath);
83243
+ return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) => join47(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
82728
83244
  }
82729
83245
  function projectIconCandidates(root) {
82730
83246
  const projectStem = normalizeStem(basename10(root)).normalized;
@@ -82743,10 +83259,10 @@ function projectIconCandidates(root) {
82743
83259
  for (const name of listFiles(directory)) {
82744
83260
  const extension2 = extname3(name).toLowerCase();
82745
83261
  if (MANIFEST_NAMES.includes(name.toLowerCase())) {
82746
- for (const icon of manifestIcons(join45(directory, name))) {
83262
+ for (const icon of manifestIcons(join47(directory, name))) {
82747
83263
  if (MEASURABLE_IMAGE_EXTENSIONS.has(extname3(icon).toLowerCase())) {
82748
83264
  fromManifest.add(icon);
82749
- files.push({ path: icon, directory: dirname21(icon) });
83265
+ files.push({ path: icon, directory: dirname23(icon) });
82750
83266
  }
82751
83267
  }
82752
83268
  continue;
@@ -82754,7 +83270,7 @@ function projectIconCandidates(root) {
82754
83270
  if (!MEASURABLE_IMAGE_EXTENSIONS.has(extension2)) continue;
82755
83271
  const stem = name.slice(0, name.length - extension2.length);
82756
83272
  if (normalizeStem(stem).normalized === "favicon") iconHomes.add(directory);
82757
- files.push({ path: join45(directory, name), directory });
83273
+ files.push({ path: join47(directory, name), directory });
82758
83274
  if (files.length >= MAX_CANDIDATES) break;
82759
83275
  }
82760
83276
  }
@@ -82796,7 +83312,7 @@ function iconMediaType(path3) {
82796
83312
  }
82797
83313
  function resolveProjectIconResponse(id2, options = {}) {
82798
83314
  if (!id2) return { status: 400 };
82799
- const home = options.home ?? homedir18();
83315
+ const home = options.home ?? homedir19();
82800
83316
  const entry = readRegistry(home).projects.find((project) => project.id === id2);
82801
83317
  if (!entry) return { status: 404 };
82802
83318
  const path3 = resolveProjectIcon(id2, (root) => detectProjectIcon(root)?.path, { home });
@@ -82806,7 +83322,7 @@ function resolveProjectIconResponse(id2, options = {}) {
82806
83322
  let real;
82807
83323
  try {
82808
83324
  real = realpathSync13(path3);
82809
- if (!statSync21(real).isFile()) return { status: 404 };
83325
+ if (!statSync22(real).isFile()) return { status: 404 };
82810
83326
  } catch {
82811
83327
  return { status: 404 };
82812
83328
  }
@@ -82819,7 +83335,7 @@ function resolveProjectIconResponse(id2, options = {}) {
82819
83335
  });
82820
83336
  if (!roots.some((root) => real === root || real.startsWith(root + sep4))) return { status: 404 };
82821
83337
  try {
82822
- return { status: 200, contentType, body: readFileSync31(real) };
83338
+ return { status: 200, contentType, body: readFileSync32(real) };
82823
83339
  } catch {
82824
83340
  return { status: 404 };
82825
83341
  }
@@ -82931,8 +83447,8 @@ var init_project_icon = __esm({
82931
83447
  });
82932
83448
 
82933
83449
  // packages/server/src/local-visualization.ts
82934
- import { readdirSync as readdirSync14, readFileSync as readFileSync32, realpathSync as realpathSync14, statSync as statSync22 } from "node:fs";
82935
- import { join as join46, sep as sep5 } from "node:path";
83450
+ import { readdirSync as readdirSync14, readFileSync as readFileSync33, realpathSync as realpathSync14, statSync as statSync23 } from "node:fs";
83451
+ import { join as join48, sep as sep5 } from "node:path";
82936
83452
  function children(path3, pattern) {
82937
83453
  try {
82938
83454
  return readdirSync14(path3, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
@@ -82944,7 +83460,7 @@ function isUnder2(path3, root) {
82944
83460
  return path3 === root || path3.startsWith(root.endsWith(sep5) ? root : root + sep5);
82945
83461
  }
82946
83462
  function resolveFragment(projectDir, sessionId, file) {
82947
- const base = join46(projectDir, ".codex", "visualizations");
83463
+ const base = join48(projectDir, ".codex", "visualizations");
82948
83464
  let projectReal;
82949
83465
  let baseReal;
82950
83466
  try {
@@ -82955,15 +83471,15 @@ function resolveFragment(projectDir, sessionId, file) {
82955
83471
  }
82956
83472
  if (!isUnder2(baseReal, projectReal)) return null;
82957
83473
  for (const year of children(base, YEAR_PART)) {
82958
- const yearDir = join46(base, year);
83474
+ const yearDir = join48(base, year);
82959
83475
  for (const month of children(yearDir, DATE_PART)) {
82960
- const monthDir = join46(yearDir, month);
83476
+ const monthDir = join48(yearDir, month);
82961
83477
  for (const day of children(monthDir, DATE_PART)) {
82962
- const sessionRoot = join46(baseReal, year, month, day, sessionId);
82963
- const candidate = join46(monthDir, day, sessionId, file);
83478
+ const sessionRoot = join48(baseReal, year, month, day, sessionId);
83479
+ const candidate = join48(monthDir, day, sessionId, file);
82964
83480
  try {
82965
83481
  const real = realpathSync14(candidate);
82966
- if (isUnder2(real, sessionRoot) && statSync22(real).isFile()) return real;
83482
+ if (isUnder2(real, sessionRoot) && statSync23(real).isFile()) return real;
82967
83483
  } catch {
82968
83484
  }
82969
83485
  }
@@ -83048,9 +83564,9 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
83048
83564
  if (!path3) return { status: 404 };
83049
83565
  let fragment;
83050
83566
  try {
83051
- const size = statSync22(path3).size;
83567
+ const size = statSync23(path3).size;
83052
83568
  if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
83053
- fragment = readFileSync32(path3, "utf8");
83569
+ fragment = readFileSync33(path3, "utf8");
83054
83570
  } catch {
83055
83571
  return { status: 404 };
83056
83572
  }
@@ -83092,9 +83608,9 @@ var init_local_visualization = __esm({
83092
83608
  });
83093
83609
 
83094
83610
  // packages/server/src/app.ts
83095
- import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync18 } from "node:fs";
83611
+ import { mkdirSync as mkdirSync25, writeFileSync as writeFileSync19 } from "node:fs";
83096
83612
  import { Readable } from "node:stream";
83097
- import { join as join47 } from "node:path";
83613
+ import { join as join49 } from "node:path";
83098
83614
  import { randomUUID as randomUUID18 } from "node:crypto";
83099
83615
  function createApp(ctx, options = {}) {
83100
83616
  const app = new Hono2();
@@ -83129,6 +83645,17 @@ function createApp(ctx, options = {}) {
83129
83645
  c.header("x-frizz-boot", ctx.bootId);
83130
83646
  await next();
83131
83647
  });
83648
+ app.use(
83649
+ `${frizzRoute("/rpc")}/*`,
83650
+ (c, next) => withRequestOrigin(
83651
+ requestOriginFrom({
83652
+ caller: c.req.header(CALLER_HEADER),
83653
+ userAgent: c.req.header("user-agent"),
83654
+ socketRemote: c.env?.remoteAddress
83655
+ }),
83656
+ next
83657
+ )
83658
+ );
83132
83659
  app.use(`${frizzRoute("/rpc")}/*`, async (c, next) => {
83133
83660
  await next();
83134
83661
  const encoding = negotiateEncoding(c.req.header("accept-encoding"));
@@ -83201,11 +83728,11 @@ function createApp(ctx, options = {}) {
83201
83728
  if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
83202
83729
  const ext = `.${attachmentExtension(name)}`;
83203
83730
  const buf = Buffer.from(body.data, "base64");
83204
- const dir = join47(ctx.project.stateDir, "attachments");
83205
- mkdirSync23(dir, { recursive: true });
83731
+ const dir = join49(ctx.project.stateDir, "attachments");
83732
+ mkdirSync25(dir, { recursive: true });
83206
83733
  const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
83207
- const path3 = join47(dir, `${Date.now()}-${randomUUID18().slice(0, 8)}-${base}${ext}`);
83208
- writeFileSync18(path3, buf);
83734
+ const path3 = join49(dir, `${Date.now()}-${randomUUID18().slice(0, 8)}-${base}${ext}`);
83735
+ writeFileSync19(path3, buf);
83209
83736
  return c.json({ path: path3 });
83210
83737
  });
83211
83738
  app.get(
@@ -83257,21 +83784,23 @@ var init_app = __esm({
83257
83784
  init_local_image();
83258
83785
  init_project_icon();
83259
83786
  init_local_visualization();
83787
+ init_audit();
83260
83788
  init_local_image();
83261
83789
  init_local_visualization();
83262
83790
  }
83263
83791
  });
83264
83792
 
83265
83793
  // packages/server/src/app-socket.ts
83266
- import { createHash as createHash16 } from "node:crypto";
83794
+ import { createHash as createHash17 } from "node:crypto";
83267
83795
  function isWsPath(url) {
83268
83796
  return (url ?? "").split("?")[0] === WS_PATH;
83269
83797
  }
83270
- function makeTranscriptReader(project, storage, backendFor, lifecycleFor, peerNameFor, ownerGone) {
83798
+ function makeTranscriptReader(project, storage, backendFor, lifecycleFor, peerNameFor, ownerGone, sessionPeerThread) {
83271
83799
  return (slug) => {
83272
83800
  const page = readLatestThreadTranscriptPage(project, storage, slug, backendFor, { editedFiles: false });
83273
83801
  const named = peerNameFor ? projectTranscriptPeerNames(page.messages, (taskId) => peerNameFor(slug, taskId)) : page.messages;
83274
- const projected = lifecycleFor ? projectTranscriptAgentLifecycles(named, (id2) => lifecycleFor(slug, id2)) : named;
83802
+ const sent = sessionPeerThread ? projectTranscriptSessionPeers(named, sessionPeerThread) : named;
83803
+ const projected = lifecycleFor ? projectTranscriptAgentLifecycles(sent, (id2) => lifecycleFor(slug, id2)) : sent;
83275
83804
  const messages = projectRetiredBackgroundOps(projected, retiredOpsFor(storage, slug), ownerGone?.(slug) ?? false);
83276
83805
  return {
83277
83806
  messages,
@@ -83467,7 +83996,7 @@ function createAppSocketServer(deps) {
83467
83996
  sendEncoded(ws, frame, false);
83468
83997
  }
83469
83998
  function frameSignature(frame) {
83470
- return createHash16("sha256").update(frame.text).digest("base64url");
83999
+ return createHash17("sha256").update(frame.text).digest("base64url");
83471
84000
  }
83472
84001
  const globalReadTimes = [];
83473
84002
  const originReadTimes = /* @__PURE__ */ new Map();
@@ -84024,6 +84553,7 @@ var init_app_socket = __esm({
84024
84553
  "packages/server/src/app-socket.ts"() {
84025
84554
  "use strict";
84026
84555
  init_wrapper();
84556
+ init_session_peer();
84027
84557
  init_src();
84028
84558
  init_transcript();
84029
84559
  init_local_origin();
@@ -84124,11 +84654,11 @@ var init_app_socket = __esm({
84124
84654
  });
84125
84655
 
84126
84656
  // packages/server/src/local-file-watch.ts
84127
- import { statSync as statSync23, watch } from "node:fs";
84128
- import { basename as basename11, dirname as dirname22 } from "node:path";
84657
+ import { statSync as statSync24, watch } from "node:fs";
84658
+ import { basename as basename11, dirname as dirname24 } from "node:path";
84129
84659
  function stampOf(path3) {
84130
84660
  try {
84131
- const s = statSync23(path3, { bigint: true });
84661
+ const s = statSync24(path3, { bigint: true });
84132
84662
  return `${s.ino}:${s.size}:${s.mtimeNs}`;
84133
84663
  } catch {
84134
84664
  return "gone";
@@ -84148,7 +84678,7 @@ function watchLocalFile(path3, onChange) {
84148
84678
  let entry = entries.get(path3);
84149
84679
  if (!entry) {
84150
84680
  const name = basename11(path3);
84151
- const watcher2 = watch(dirname22(path3), { persistent: false }, (_event, changed) => {
84681
+ const watcher2 = watch(dirname24(path3), { persistent: false }, (_event, changed) => {
84152
84682
  if (changed !== null && changed !== void 0 && changed.toString() !== name) return;
84153
84683
  const current = entries.get(path3);
84154
84684
  if (!current) return;
@@ -84184,10 +84714,10 @@ var init_local_file_watch = __esm({
84184
84714
  });
84185
84715
 
84186
84716
  // packages/server/src/boot-progress.ts
84187
- import { mkdirSync as mkdirSync24, readFileSync as readFileSync33, renameSync as renameSync11, rmSync as rmSync13, writeFileSync as writeFileSync19 } from "node:fs";
84188
- import { basename as basename12, dirname as dirname23, join as join48 } from "node:path";
84717
+ import { mkdirSync as mkdirSync26, readFileSync as readFileSync34, renameSync as renameSync12, rmSync as rmSync13, writeFileSync as writeFileSync20 } from "node:fs";
84718
+ import { basename as basename12, dirname as dirname25, join as join50 } from "node:path";
84189
84719
  function bootProgressPath(stateDir) {
84190
- return join48(stateDir, BOOT_PROGRESS_NAME);
84720
+ return join50(stateDir, BOOT_PROGRESS_NAME);
84191
84721
  }
84192
84722
  function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
84193
84723
  if (!stateDir) {
@@ -84207,11 +84737,11 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
84207
84737
  lastAt = now;
84208
84738
  step++;
84209
84739
  try {
84210
- mkdirSync24(dirname23(path3), { recursive: true, mode: 448 });
84211
- const temp = join48(dirname23(path3), `.${basename12(path3)}.${process.pid}.tmp`);
84212
- writeFileSync19(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
84740
+ mkdirSync26(dirname25(path3), { recursive: true, mode: 448 });
84741
+ const temp = join50(dirname25(path3), `.${basename12(path3)}.${process.pid}.tmp`);
84742
+ writeFileSync20(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
84213
84743
  `, "utf8");
84214
- renameSync11(temp, path3);
84744
+ renameSync12(temp, path3);
84215
84745
  } catch {
84216
84746
  }
84217
84747
  };
@@ -84234,13 +84764,13 @@ var init_boot_progress = __esm({
84234
84764
  });
84235
84765
 
84236
84766
  // packages/server/src/runtime-lease.ts
84237
- import { existsSync as existsSync22, mkdirSync as mkdirSync25, readdirSync as readdirSync15, readFileSync as readFileSync34, unlinkSync as unlinkSync8, writeFileSync as writeFileSync20 } from "node:fs";
84238
- import { dirname as dirname24, join as join49 } from "node:path";
84767
+ import { existsSync as existsSync22, mkdirSync as mkdirSync27, readdirSync as readdirSync15, readFileSync as readFileSync35, unlinkSync as unlinkSync8, writeFileSync as writeFileSync21 } from "node:fs";
84768
+ import { dirname as dirname26, join as join51 } from "node:path";
84239
84769
  function runtimeVersionDir(bin) {
84240
- let dir = dirname24(bin);
84770
+ let dir = dirname26(bin);
84241
84771
  for (let step = 0; step < MAX_ASCENT; step++) {
84242
- if (existsSync22(join49(dir, PROVISIONED_MARKER))) return dir;
84243
- const parent = dirname24(dir);
84772
+ if (existsSync22(join51(dir, PROVISIONED_MARKER))) return dir;
84773
+ const parent = dirname26(dir);
84244
84774
  if (parent === dir) return void 0;
84245
84775
  dir = parent;
84246
84776
  }
@@ -84258,11 +84788,11 @@ function leaseRuntime(bin, role) {
84258
84788
  const versionDir = runtimeVersionDir(bin);
84259
84789
  if (!versionDir) return () => {
84260
84790
  };
84261
- const path3 = join49(versionDir, LEASES_DIR, `${process.pid}.json`);
84791
+ const path3 = join51(versionDir, LEASES_DIR, `${process.pid}.json`);
84262
84792
  try {
84263
- mkdirSync25(dirname24(path3), { recursive: true });
84793
+ mkdirSync27(dirname26(path3), { recursive: true });
84264
84794
  const lease = { pid: process.pid, role, bin, at: (/* @__PURE__ */ new Date()).toISOString() };
84265
- writeFileSync20(path3, JSON.stringify(lease));
84795
+ writeFileSync21(path3, JSON.stringify(lease));
84266
84796
  } catch {
84267
84797
  return () => {
84268
84798
  };
@@ -84275,7 +84805,7 @@ function leaseRuntime(bin, role) {
84275
84805
  };
84276
84806
  }
84277
84807
  function liveRuntimeLeases(versionDir) {
84278
- const dir = join49(versionDir, LEASES_DIR);
84808
+ const dir = join51(versionDir, LEASES_DIR);
84279
84809
  let names;
84280
84810
  try {
84281
84811
  names = readdirSync15(dir);
@@ -84285,10 +84815,10 @@ function liveRuntimeLeases(versionDir) {
84285
84815
  const live = [];
84286
84816
  for (const name of names) {
84287
84817
  if (!name.endsWith(".json")) continue;
84288
- const path3 = join49(dir, name);
84818
+ const path3 = join51(dir, name);
84289
84819
  let lease;
84290
84820
  try {
84291
- lease = JSON.parse(readFileSync34(path3, "utf8"));
84821
+ lease = JSON.parse(readFileSync35(path3, "utf8"));
84292
84822
  } catch {
84293
84823
  continue;
84294
84824
  }
@@ -84315,29 +84845,29 @@ var init_runtime_lease = __esm({
84315
84845
  });
84316
84846
 
84317
84847
  // packages/server/src/runtimes.ts
84318
- import { createHash as createHash17 } from "node:crypto";
84848
+ import { createHash as createHash18 } from "node:crypto";
84319
84849
  import {
84320
- chmodSync as chmodSync4,
84850
+ chmodSync as chmodSync5,
84321
84851
  closeSync as closeSync18,
84322
84852
  existsSync as existsSync23,
84323
- mkdirSync as mkdirSync26,
84853
+ mkdirSync as mkdirSync28,
84324
84854
  openSync as openSync19,
84325
- readFileSync as readFileSync35,
84855
+ readFileSync as readFileSync36,
84326
84856
  readdirSync as readdirSync16,
84327
- renameSync as renameSync12,
84857
+ renameSync as renameSync13,
84328
84858
  rmSync as rmSync14,
84329
- statSync as statSync24,
84330
- writeFileSync as writeFileSync21,
84859
+ statSync as statSync25,
84860
+ writeFileSync as writeFileSync22,
84331
84861
  writeSync as writeSync3
84332
84862
  } from "node:fs";
84333
- import { dirname as dirname25, isAbsolute as isAbsolute10, join as join50, normalize, relative as relative3, sep as sep6 } from "node:path";
84863
+ import { dirname as dirname27, isAbsolute as isAbsolute10, join as join52, normalize, relative as relative3, sep as sep6 } from "node:path";
84334
84864
  import { Readable as Readable2, Transform, Writable as Writable4 } from "node:stream";
84335
84865
  import { pipeline } from "node:stream/promises";
84336
84866
  import { createGunzip } from "node:zlib";
84337
84867
  function runtimesRoot(env = process.env) {
84338
84868
  const override = env[RUNTIMES_DIR_ENV];
84339
84869
  if (override && isAbsolute10(override)) return override;
84340
- return join50(frizzRoots().cache, "runtimes");
84870
+ return join52(frizzRoots().cache, "runtimes");
84341
84871
  }
84342
84872
  function isMusl() {
84343
84873
  if (process.platform !== "linux") return false;
@@ -84354,15 +84884,15 @@ function runtimeCoordinates(backend, platform3 = process.platform, arch = proces
84354
84884
  return { pkg: "@openai/codex", packageVersion: `${CODEX_VERSION}-${platform3}-${arch}`, label: CODEX_VERSION, binary: `codex${exe2}` };
84355
84885
  }
84356
84886
  function provisionedBinary(backend, root, label) {
84357
- const dir = join50(root, backend, label);
84887
+ const dir = join52(root, backend, label);
84358
84888
  let marker;
84359
84889
  try {
84360
- marker = JSON.parse(readFileSync35(join50(dir, MARKER), "utf8"));
84890
+ marker = JSON.parse(readFileSync36(join52(dir, MARKER), "utf8"));
84361
84891
  } catch {
84362
84892
  return void 0;
84363
84893
  }
84364
84894
  if (typeof marker.binary !== "string" || marker.label !== label) return void 0;
84365
- const bin = join50(dir, marker.binary);
84895
+ const bin = join52(dir, marker.binary);
84366
84896
  return existsSync23(bin) ? bin : void 0;
84367
84897
  }
84368
84898
  function octal(buffer, offset, length) {
@@ -84377,17 +84907,17 @@ function safeEntryPath(dest, name) {
84377
84907
  if (parts.length < 2) return void 0;
84378
84908
  const rel = normalize(parts.slice(1).join(sep6));
84379
84909
  if (isAbsolute10(rel) || rel === ".." || rel.startsWith(`..${sep6}`)) return void 0;
84380
- const full = join50(dest, rel);
84910
+ const full = join52(dest, rel);
84381
84911
  const back = relative3(dest, full);
84382
84912
  if (back.startsWith("..") || isAbsolute10(back)) return void 0;
84383
84913
  return full;
84384
84914
  }
84385
84915
  function locateBinary(dir, backend, binary) {
84386
84916
  if (backend === "claude") {
84387
- const direct = join50(dir, binary);
84917
+ const direct = join52(dir, binary);
84388
84918
  return existsSync23(direct) ? direct : void 0;
84389
84919
  }
84390
- const vendor = join50(dir, "vendor");
84920
+ const vendor = join52(dir, "vendor");
84391
84921
  let triples;
84392
84922
  try {
84393
84923
  triples = readdirSync16(vendor);
@@ -84395,7 +84925,7 @@ function locateBinary(dir, backend, binary) {
84395
84925
  return void 0;
84396
84926
  }
84397
84927
  for (const triple of triples) {
84398
- const candidate = join50(vendor, triple, "bin", binary);
84928
+ const candidate = join52(vendor, triple, "bin", binary);
84399
84929
  if (existsSync23(candidate)) return candidate;
84400
84930
  }
84401
84931
  return void 0;
@@ -84411,12 +84941,12 @@ async function provisionRuntime(backend, options) {
84411
84941
  const registry = options.registry ?? REGISTRY;
84412
84942
  const progress = options.onProgress ?? (() => {
84413
84943
  });
84414
- const backendDir = join50(root, backend);
84415
- const finalDir = join50(backendDir, coordinates.label);
84416
- const partial = join50(backendDir, `.partial-${coordinates.label}-${process.pid}`);
84417
- mkdirSync26(backendDir, { recursive: true });
84944
+ const backendDir = join52(root, backend);
84945
+ const finalDir = join52(backendDir, coordinates.label);
84946
+ const partial = join52(backendDir, `.partial-${coordinates.label}-${process.pid}`);
84947
+ mkdirSync28(backendDir, { recursive: true });
84418
84948
  rmSync14(partial, { recursive: true, force: true });
84419
- mkdirSync26(partial);
84949
+ mkdirSync28(partial);
84420
84950
  try {
84421
84951
  const manifestUrl = `${registry}/${coordinates.pkg.replace("/", "%2F")}/${coordinates.packageVersion}`;
84422
84952
  progress(`resolving ${coordinates.pkg}@${coordinates.packageVersion}`);
@@ -84429,7 +84959,7 @@ async function provisionRuntime(backend, options) {
84429
84959
  const response = await fetchImpl(tarball);
84430
84960
  if (!response.ok || !response.body) throw new Error(`tarball download answered ${response.status}`);
84431
84961
  const total = Number(response.headers.get("content-length") ?? 0);
84432
- const hash = createHash17("sha512");
84962
+ const hash = createHash18("sha512");
84433
84963
  let seen = 0;
84434
84964
  let lastReport = 0;
84435
84965
  const hasher = new Transform({
@@ -84450,7 +84980,7 @@ async function provisionRuntime(backend, options) {
84450
84980
  if (digest !== integrity) throw new Error(`integrity mismatch for ${tarball}: expected ${integrity}, got ${digest}`);
84451
84981
  const bin = locateBinary(partial, backend, coordinates.binary);
84452
84982
  if (!bin) throw new Error(`${coordinates.pkg}@${coordinates.packageVersion} unpacked without a ${coordinates.binary} binary`);
84453
- if (process.platform !== "win32") chmodSync4(bin, 493);
84983
+ if (process.platform !== "win32") chmodSync5(bin, 493);
84454
84984
  const marker = {
84455
84985
  backend,
84456
84986
  label: coordinates.label,
@@ -84460,9 +84990,9 @@ async function provisionRuntime(backend, options) {
84460
84990
  binary: relative3(partial, bin),
84461
84991
  at: (/* @__PURE__ */ new Date()).toISOString()
84462
84992
  };
84463
- writeFileSync21(join50(partial, MARKER), `${JSON.stringify(marker, null, 2)}
84993
+ writeFileSync22(join52(partial, MARKER), `${JSON.stringify(marker, null, 2)}
84464
84994
  `, "utf8");
84465
- const rename2 = options.rename ?? renameSync12;
84995
+ const rename2 = options.rename ?? renameSync13;
84466
84996
  try {
84467
84997
  for (let attempt = 1; ; attempt++) {
84468
84998
  try {
@@ -84480,7 +85010,7 @@ async function provisionRuntime(backend, options) {
84480
85010
  return { bin: theirs, label: coordinates.label, fetched: false };
84481
85011
  }
84482
85012
  progress("ready");
84483
- return { bin: join50(finalDir, marker.binary), label: coordinates.label, fetched: true };
85013
+ return { bin: join52(finalDir, marker.binary), label: coordinates.label, fetched: true };
84484
85014
  } catch (err) {
84485
85015
  rmSync14(partial, { recursive: true, force: true });
84486
85016
  throw err;
@@ -84488,7 +85018,7 @@ async function provisionRuntime(backend, options) {
84488
85018
  }
84489
85019
  function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
84490
85020
  const rm2 = deps.rm ?? defaultSweepRm;
84491
- const backendDir = join50(root, backend);
85021
+ const backendDir = join52(root, backend);
84492
85022
  let entries2;
84493
85023
  try {
84494
85024
  entries2 = readdirSync16(backendDir);
@@ -84498,10 +85028,10 @@ function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
84498
85028
  const sweep = { removed: [], kept: [] };
84499
85029
  for (const entry of entries2) {
84500
85030
  if (entry === keepLabel) continue;
84501
- const full = join50(backendDir, entry);
85031
+ const full = join52(backendDir, entry);
84502
85032
  if (entry.startsWith(".partial-")) {
84503
85033
  try {
84504
- if (now - statSync24(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
85034
+ if (now - statSync25(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
84505
85035
  } catch {
84506
85036
  continue;
84507
85037
  }
@@ -84681,7 +85211,7 @@ var init_runtimes = __esm({
84681
85211
  return;
84682
85212
  case "5": {
84683
85213
  const dir = safeEntryPath(this.dest, name);
84684
- if (dir) mkdirSync26(dir, { recursive: true });
85214
+ if (dir) mkdirSync28(dir, { recursive: true });
84685
85215
  return;
84686
85216
  }
84687
85217
  case "0":
@@ -84689,7 +85219,7 @@ var init_runtimes = __esm({
84689
85219
  case "7": {
84690
85220
  const file = safeEntryPath(this.dest, name);
84691
85221
  if (!file) return;
84692
- mkdirSync26(dirname25(file), { recursive: true });
85222
+ mkdirSync28(dirname27(file), { recursive: true });
84693
85223
  this.fd = openSync19(file, "w", octal(header, 100, 8) & 511 || 420);
84694
85224
  this.written.push(file);
84695
85225
  return;
@@ -84757,6 +85287,7 @@ function createTenantMap(options) {
84757
85287
  log.info("tenants", `project ${project.name} (${project.id}) moved from ${already.project.dir} to ${project.dir}; reopening it there`);
84758
85288
  await deactivate(project.id);
84759
85289
  }
85290
+ refuseWorldWritableProject(project.dir);
84760
85291
  const ctx = await options.createContext({ ...options.contextOptions, project });
84761
85292
  open.set(project.id, { project, ctx, app: options.createApp?.(ctx) });
84762
85293
  await options.startProducers?.(ctx);
@@ -84764,11 +85295,12 @@ function createTenantMap(options) {
84764
85295
  } catch (error) {
84765
85296
  report(project, error);
84766
85297
  return void 0;
84767
- } finally {
84768
- opening.delete(project.id);
84769
85298
  }
84770
85299
  })();
84771
85300
  opening.set(project.id, attempt);
85301
+ void attempt.then(() => {
85302
+ if (opening.get(project.id) === attempt) opening.delete(project.id);
85303
+ });
84772
85304
  return attempt;
84773
85305
  }
84774
85306
  async function deactivate(projectId) {
@@ -84816,6 +85348,7 @@ var init_tenants = __esm({
84816
85348
  "use strict";
84817
85349
  init_context();
84818
85350
  init_logging();
85351
+ init_project_root();
84819
85352
  }
84820
85353
  });
84821
85354
 
@@ -85269,18 +85802,18 @@ var init_legacy_project_db = __esm({
85269
85802
  });
85270
85803
 
85271
85804
  // packages/server/src/frizz-db.ts
85272
- import { existsSync as existsSync24, mkdirSync as mkdirSync27, readdirSync as readdirSync17, renameSync as renameSync13, statSync as statSync25 } from "node:fs";
85273
- import { basename as basename13, dirname as dirname26, join as join51 } from "node:path";
85805
+ import { existsSync as existsSync24, mkdirSync as mkdirSync29, readdirSync as readdirSync17, renameSync as renameSync14, statSync as statSync26 } from "node:fs";
85806
+ import { basename as basename13, dirname as dirname28, join as join53 } from "node:path";
85274
85807
  function frizzDatabaseLocation(options) {
85275
85808
  if (options.stateDir !== void 0) {
85276
- const projectsDir = dirname26(options.stateDir);
85809
+ const projectsDir = dirname28(options.stateDir);
85277
85810
  if (basename13(projectsDir) === "projects") {
85278
- return { path: options.path ?? join51(dirname26(projectsDir), "ui.db"), projectsDir };
85811
+ return { path: options.path ?? join53(dirname28(projectsDir), "ui.db"), projectsDir };
85279
85812
  }
85280
- return { path: options.path ?? join51(options.stateDir, "ui.db"), projectsDir: void 0 };
85813
+ return { path: options.path ?? join53(options.stateDir, "ui.db"), projectsDir: void 0 };
85281
85814
  }
85282
85815
  const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
85283
- return { path: options.path ?? join51(data, "ui.db"), projectsDir: join51(data, "projects") };
85816
+ return { path: options.path ?? join53(data, "ui.db"), projectsDir: join53(data, "projects") };
85284
85817
  }
85285
85818
  function ensureFrizzSchema(db2) {
85286
85819
  db2.exec(`
@@ -85299,7 +85832,7 @@ function ensureFrizzSchema(db2) {
85299
85832
  }
85300
85833
  function openFrizzDatabase(options = {}) {
85301
85834
  const { path: path3, projectsDir } = frizzDatabaseLocation(options);
85302
- mkdirSync27(dirname26(path3), { recursive: true });
85835
+ mkdirSync29(dirname28(path3), { recursive: true });
85303
85836
  const db2 = new sqlite_default(path3);
85304
85837
  db2.pragma("busy_timeout = 5000");
85305
85838
  db2.pragma("journal_mode = WAL");
@@ -85311,10 +85844,10 @@ function openFrizzDatabase(options = {}) {
85311
85844
  );
85312
85845
  for (const projectId of retiredLegacyProjectIds(projectsDir)) {
85313
85846
  if (alreadyImported.has(projectId)) continue;
85314
- const dir = join51(projectsDir, projectId);
85847
+ const dir = join53(projectsDir, projectId);
85315
85848
  try {
85316
- renameSync13(join51(dir, "ui.db.imported"), join51(dir, "ui.db"));
85317
- db2.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join51(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
85849
+ renameSync14(join53(dir, "ui.db.imported"), join53(dir, "ui.db"));
85850
+ db2.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join53(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
85318
85851
  alreadyImported.add(projectId);
85319
85852
  log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
85320
85853
  } catch (error) {
@@ -85324,7 +85857,7 @@ function openFrizzDatabase(options = {}) {
85324
85857
  }
85325
85858
  for (const projectId of legacyProjectIds(projectsDir)) {
85326
85859
  if (alreadyImported.has(projectId)) continue;
85327
- const legacyPath = join51(projectsDir, projectId, "ui.db");
85860
+ const legacyPath = join53(projectsDir, projectId, "ui.db");
85328
85861
  try {
85329
85862
  const rows = importLegacyProject(db2, projectId, legacyPath);
85330
85863
  imported.push({ projectId, rows });
@@ -85357,7 +85890,7 @@ function projectIdsWithFile(projectsDir, file) {
85357
85890
  if (!existsSync24(projectsDir)) return [];
85358
85891
  return readdirSync17(projectsDir).filter((name) => {
85359
85892
  try {
85360
- return statSync25(join51(projectsDir, name, file)).isFile();
85893
+ return statSync26(join53(projectsDir, name, file)).isFile();
85361
85894
  } catch {
85362
85895
  return false;
85363
85896
  }
@@ -85367,7 +85900,7 @@ function legacyProjectIds(projectsDir) {
85367
85900
  return projectIdsWithFile(projectsDir, "ui.db");
85368
85901
  }
85369
85902
  function retiredLegacyProjectIds(projectsDir) {
85370
- return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id2) => !existsSync24(join51(projectsDir, id2, "ui.db")));
85903
+ return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id2) => !existsSync24(join53(projectsDir, id2, "ui.db")));
85371
85904
  }
85372
85905
  function tableColumns(db2, schema, table) {
85373
85906
  return db2.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
@@ -85522,8 +86055,8 @@ var init_tenant_prime = __esm({
85522
86055
 
85523
86056
  // packages/server/src/project-teardown.ts
85524
86057
  import { rmSync as rmSync15 } from "node:fs";
85525
- import { homedir as homedir19 } from "node:os";
85526
- import { dirname as dirname27, resolve as resolve11 } from "node:path";
86058
+ import { homedir as homedir20 } from "node:os";
86059
+ import { dirname as dirname29, resolve as resolve11 } from "node:path";
85527
86060
  async function stopProjectWorkers(ctx) {
85528
86061
  let stopped = 0;
85529
86062
  for (const row of ctx.storage.allSessions()) {
@@ -85538,12 +86071,12 @@ async function stopProjectWorkers(ctx) {
85538
86071
  }
85539
86072
  return stopped;
85540
86073
  }
85541
- function deleteProjectState(projectId, home = homedir19()) {
86074
+ function deleteProjectState(projectId, home = homedir20()) {
85542
86075
  const target = projectStateDir(projectId, home);
85543
86076
  if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
85544
86077
  throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
85545
86078
  }
85546
- if (dirname27(resolve11(target)) !== resolve11(dirname27(projectStateDir("x", home)))) {
86079
+ if (dirname29(resolve11(target)) !== resolve11(dirname29(projectStateDir("x", home)))) {
85547
86080
  throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
85548
86081
  }
85549
86082
  rmSync15(target, { recursive: true, force: true });
@@ -85572,11 +86105,11 @@ __export(index_exports, {
85572
86105
  unknownProjectPage: () => unknownProjectPage
85573
86106
  });
85574
86107
  import { createServer } from "node:http";
85575
- import { readFileSync as readFileSync36, existsSync as existsSync25, statSync as statSync26 } from "node:fs";
85576
- import { dirname as dirname28, join as join52, resolve as resolve12, extname as extname4, posix as posix2, sep as sep7 } from "node:path";
86108
+ import { readFileSync as readFileSync37, existsSync as existsSync25, statSync as statSync27 } from "node:fs";
86109
+ import { dirname as dirname30, join as join54, resolve as resolve12, extname as extname4, posix as posix2, sep as sep7 } from "node:path";
85577
86110
  function readServerAddressHolder(path3) {
85578
86111
  try {
85579
- const value = JSON.parse(readFileSync36(path3, "utf8"));
86112
+ const value = JSON.parse(readFileSync37(path3, "utf8"));
85580
86113
  if (typeof value?.pid !== "number") return void 0;
85581
86114
  return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
85582
86115
  } catch {
@@ -85623,7 +86156,10 @@ async function pipeToApp(app, req, res, port2, controller) {
85623
86156
  // @ts-expect-error duplex is required by node's fetch when streaming a request body
85624
86157
  duplex: "half",
85625
86158
  signal: controller.signal
85626
- })
86159
+ }),
86160
+ // The peer this server actually accepted, for a request the launcher's proxy did not stamp — the
86161
+ // only place it is known once the request becomes a Web Request (app.ts, the audit attribution).
86162
+ { remoteAddress: req.socket?.remoteAddress }
85627
86163
  );
85628
86164
  res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
85629
86165
  if (response.body) {
@@ -85701,12 +86237,12 @@ function staticIsFresh(req, etag, mtimeMs) {
85701
86237
  }
85702
86238
  function serveStatic(distDir, req, res) {
85703
86239
  const rel = posix2.normalize((req.url ?? "/").split("?")[0].replace(/\\/g, "/")).replace(/^(\.\.[/\\])+/, "");
85704
- let file = join52(distDir, rel === "/" ? "index.html" : rel);
85705
- if (!file.startsWith(distDir + sep7)) file = join52(distDir, "index.html");
85706
- if (!existsSync25(file)) file = join52(distDir, "index.html");
86240
+ let file = join54(distDir, rel === "/" ? "index.html" : rel);
86241
+ if (!file.startsWith(distDir + sep7)) file = join54(distDir, "index.html");
86242
+ if (!existsSync25(file)) file = join54(distDir, "index.html");
85707
86243
  try {
85708
- const stats = statSync26(file);
85709
- const cacheControl = file.startsWith(join52(distDir, "assets") + sep7) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
86244
+ const stats = statSync27(file);
86245
+ const cacheControl = file.startsWith(join54(distDir, "assets") + sep7) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
85710
86246
  const etag = `"${stats.size.toString(16)}-${Math.trunc(stats.mtimeMs).toString(16)}"`;
85711
86247
  const headers = {
85712
86248
  "content-type": MIME[extname4(file)] ?? "application/octet-stream",
@@ -85722,7 +86258,7 @@ function serveStatic(distDir, req, res) {
85722
86258
  res.end();
85723
86259
  return;
85724
86260
  }
85725
- let body = readFileSync36(file);
86261
+ let body = readFileSync37(file);
85726
86262
  const accept = req.headers["accept-encoding"];
85727
86263
  const encoding = negotiateEncoding(Array.isArray(accept) ? accept.join(",") : accept);
85728
86264
  if (encoding && shouldCompress(new Headers({ "content-type": headers["content-type"] }), body.byteLength)) {
@@ -85761,7 +86297,7 @@ async function startServer(opts = {}) {
85761
86297
  `${candidate.name} is already being served by another Frizz (pid ${other}). Stop that one first \u2014 two servers on one board would fire its timers and recurring prompts twice.`
85762
86298
  );
85763
86299
  };
85764
- const activeTenants = () => tenants.active().map(({ project: open, ctx: openCtx }) => ({ project: open, board: openCtx.board }));
86300
+ const activeTenants = () => tenants.active().map(({ project: open, ctx: openCtx }) => ({ project: open, board: openCtx.board, storage: openCtx.storage }));
85765
86301
  const teardownProject = async (projectId, options) => {
85766
86302
  if (projectId === project.id) return { closed: false, stoppedWorkers: 0 };
85767
86303
  const open = tenants.get(projectId);
@@ -85841,7 +86377,8 @@ async function startServer(opts = {}) {
85841
86377
  (slug, taskId) => c.tailer.subAgentByTaskId?.(slug, taskId),
85842
86378
  // The /ws producer is the one the live UI actually renders, so a dead owner has to reach it
85843
86379
  // too — projecting only the RPC is the exact half-fix the × already had to correct once.
85844
- (slug) => c.tailer.ownerGone?.(slug) ?? false
86380
+ (slug) => c.tailer.ownerGone?.(slug) ?? false,
86381
+ c.sessionPeerThread
85845
86382
  ),
85846
86383
  // A reader's live watch on the file it shows. Gated as the read is (one set of openable roots for
85847
86384
  // both readers, plus the Markdown reader's canonical-extension check) and watched by CANONICAL
@@ -85979,7 +86516,7 @@ async function startServer(opts = {}) {
85979
86516
  if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
85980
86517
  if (statusPath && statusIdentity) {
85981
86518
  try {
85982
- runtime.removeStatus(serverAddressPathForStateDir(dirname28(statusPath)), statusIdentity);
86519
+ runtime.removeStatus(serverAddressPathForStateDir(dirname30(statusPath)), statusIdentity);
85983
86520
  } catch {
85984
86521
  }
85985
86522
  }
@@ -86111,7 +86648,7 @@ async function startServer(opts = {}) {
86111
86648
  }
86112
86649
  statusPath = serverLockPathFor(ctx.project);
86113
86650
  const webRoot = resolve12(import.meta.dirname, "..", "..", "web");
86114
- const distDir = opts.webDistDir ? resolve12(opts.webDistDir) : join52(webRoot, "dist");
86651
+ const distDir = opts.webDistDir ? resolve12(opts.webDistDir) : join54(webRoot, "dist");
86115
86652
  startupPhase = "Vite";
86116
86653
  if (opts.dev) {
86117
86654
  try {
@@ -86189,7 +86726,7 @@ async function startServer(opts = {}) {
86189
86726
  if (vite) {
86190
86727
  vite.middlewares(req, res, () => {
86191
86728
  try {
86192
- const html = readFileSync36(join52(webRoot, "index.html"), "utf8");
86729
+ const html = readFileSync37(join54(webRoot, "index.html"), "utf8");
86193
86730
  void vite.transformIndexHtml(url, html).then((out) => {
86194
86731
  res.writeHead(200, { "content-type": "text/html" });
86195
86732
  res.end(out);
@@ -86395,7 +86932,7 @@ var init_index = __esm({
86395
86932
  }
86396
86933
  };
86397
86934
  isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
86398
- serverLockPathFor = (project) => join52(project.stateDir, "server.lock");
86935
+ serverLockPathFor = (project) => join54(project.stateDir, "server.lock");
86399
86936
  MIME = {
86400
86937
  ".html": "text/html",
86401
86938
  ".js": "text/javascript",
@@ -86418,64 +86955,17 @@ init_project();
86418
86955
  init_project_launch();
86419
86956
  init_shutdown();
86420
86957
  init_logging();
86958
+ init_access_codes();
86421
86959
  import { existsSync as existsSync26 } from "node:fs";
86422
- import { join as join53 } from "node:path";
86423
-
86424
- // packages/server/src/access-codes.ts
86425
- import { randomBytes, timingSafeEqual, createHmac } from "node:crypto";
86426
- import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
86427
- import { dirname as dirname6, join as join9 } from "node:path";
86428
- var DEFAULT_CODE_TTL_MS = 5 * 6e4;
86429
- var DEFAULT_SESSION_TTL_MS = 30 * 24 * 60 * 6e4;
86430
- var SESSION_EPOCH = 1;
86431
- var SESSION_KEY_FILE = "session-key";
86432
- var SESSION_EPOCH_FILE = "session-epoch";
86433
- function writeSessionKey(path3, key) {
86434
- mkdirSync7(dirname6(path3), { recursive: true });
86435
- writeFileSync6(path3, key, { mode: 384 });
86436
- try {
86437
- chmodSync(path3, 384);
86438
- } catch {
86439
- }
86440
- }
86441
- function rotateSessionKey(stateDir) {
86442
- const key = randomBytes(32);
86443
- writeSessionKey(join9(stateDir, SESSION_KEY_FILE), key);
86444
- return key;
86445
- }
86446
- function readSessionEpoch(stateDir) {
86447
- try {
86448
- const parsed = JSON.parse(readFileSync6(join9(stateDir, SESSION_EPOCH_FILE), "utf8"));
86449
- return Number.isInteger(parsed?.epoch) && parsed.epoch >= 0 ? parsed.epoch : 0;
86450
- } catch {
86451
- return 0;
86452
- }
86453
- }
86454
- function advanceSessionEpoch(stateDir, epoch = SESSION_EPOCH) {
86455
- const from = readSessionEpoch(stateDir);
86456
- if (from >= epoch) return { advanced: false, epoch: from };
86457
- const keyPath = join9(stateDir, SESSION_KEY_FILE);
86458
- const rotatedKey = existsSync4(keyPath);
86459
- if (rotatedKey) rotateSessionKey(stateDir);
86460
- mkdirSync7(stateDir, { recursive: true });
86461
- writeFileSync6(
86462
- join9(stateDir, SESSION_EPOCH_FILE),
86463
- JSON.stringify({ epoch, advancedAt: (/* @__PURE__ */ new Date()).toISOString() }),
86464
- { mode: 384 }
86465
- );
86466
- return { advanced: true, from, to: epoch, rotatedKey };
86467
- }
86468
- function sessionEpochLogLine(advance, signedOut) {
86469
- if (!advance.advanced || !advance.rotatedKey) return null;
86470
- const count = signedOut === null ? "an unknown number of" : String(signedOut);
86471
- return `session epoch ${advance.from} -> ${advance.to}: rotated the session key and signed out ${count} recorded remote device(s); every older remote session is refused`;
86472
- }
86960
+ import { join as join55 } from "node:path";
86473
86961
 
86474
86962
  // packages/server/src/session-epoch-child.ts
86963
+ init_access_codes();
86475
86964
  import { readFileSync as readFileSync7 } from "node:fs";
86476
- import { join as join10 } from "node:path";
86965
+ import { join as join11 } from "node:path";
86477
86966
 
86478
86967
  // packages/server/src/restart-supervisor.ts
86968
+ init_access_codes();
86479
86969
  init_local_origin();
86480
86970
  init_local_image();
86481
86971
 
@@ -86684,6 +87174,7 @@ var RECOVERY_SCRIPT = `
86684
87174
  `.trim();
86685
87175
 
86686
87176
  // packages/server/src/restart-supervisor.ts
87177
+ init_audit();
86687
87178
  init_src();
86688
87179
  var SUPERVISOR_CONTROL_PREFIX = "/_frizz/control";
86689
87180
  var SUPERVISOR_RESTART_PATH = `${SUPERVISOR_CONTROL_PREFIX}/restart`;
@@ -86719,7 +87210,7 @@ async function signOutOlderSessionEpoch(options) {
86719
87210
  }
86720
87211
  function supervisorPort(stateDir, supervisorPid) {
86721
87212
  try {
86722
- const status = JSON.parse(readFileSync7(join10(stateDir, "dev-supervisor.lock"), "utf8"));
87213
+ const status = JSON.parse(readFileSync7(join11(stateDir, "dev-supervisor.lock"), "utf8"));
86723
87214
  if (status.pid !== supervisorPid) return void 0;
86724
87215
  return Number.isInteger(status.port) && status.port > 0 && status.port <= 65535 ? status.port : void 0;
86725
87216
  } catch {
@@ -86784,7 +87275,7 @@ try {
86784
87275
  ];
86785
87276
  if (!existsSync26(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
86786
87277
  for (const [name, directory, requiredFile] of required) {
86787
- if (!directory || !existsSync26(join53(directory, requiredFile)))
87278
+ if (!directory || !existsSync26(join55(directory, requiredFile)))
86788
87279
  throw new Error(`stable artifact launch is missing verified ${name}`);
86789
87280
  }
86790
87281
  }