frizz-server 0.15.22 → 0.15.23

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 (112) hide show
  1. package/dist/claude-agent-broker.js +347 -27
  2. package/dist/dev-child.js +4762 -2746
  3. package/package.json +1 -1
  4. package/runtime/cc-worker/DECISIONS.md +36 -0
  5. package/runtime/cc-worker/bin/frizz-mcp.mjs +11 -1850
  6. package/runtime/cc-worker/bin/frizzctl +14 -0
  7. package/runtime/cc-worker/bin/frizzctl.cmd +9 -0
  8. package/runtime/cc-worker/bin/frizzctl.mjs +438 -0
  9. package/runtime/cc-worker/hooks/agent-dispatch.mjs +2 -2
  10. package/runtime/cc-worker/hooks/deny-ask.mjs +3 -3
  11. package/runtime/cc-worker/hooks/deny-plan.mjs +2 -2
  12. package/runtime/cc-worker/hooks/perm-policy.mjs +42 -0
  13. package/runtime/cc-worker/hooks/scratchpad.mjs +16 -10
  14. package/runtime/cc-worker/hooks/session-seed.mjs +1 -1
  15. package/runtime/cc-worker/lib/frizz-tools.mjs +2241 -0
  16. package/runtime/cc-worker/skills/gh/SKILL.md +6 -6
  17. package/web-dist/assets/{abnfDiagram-VRR7QNED-DqTQZ2Ex.js → abnfDiagram-VRR7QNED-BW_CIF9J.js} +1 -1
  18. package/web-dist/assets/{arc-t56zg8G8.js → arc-B475yv46.js} +1 -1
  19. package/web-dist/assets/architecture-TIHT7OUA-wAP7tOoN.js +1 -0
  20. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-CSdC9-eQ.js → architectureDiagram-ZJ3FMSHR-DkPvFjFp.js} +1 -1
  21. package/web-dist/assets/{blockDiagram-677ZJIJ3-Dmw8EopV.js → blockDiagram-677ZJIJ3-Rls7Nttu.js} +1 -1
  22. package/web-dist/assets/{c4Diagram-LMCZKHZV-BCRaFnis.js → c4Diagram-LMCZKHZV-maV0q2bv.js} +1 -1
  23. package/web-dist/assets/channel-Clg0HJPA.js +1 -0
  24. package/web-dist/assets/{chunk-32BRIVSS-DqOJDRw_.js → chunk-32BRIVSS-Coi7L0de.js} +1 -1
  25. package/web-dist/assets/{chunk-52WLFC77-BsibludT.js → chunk-52WLFC77-DZaDe-48.js} +1 -1
  26. package/web-dist/assets/{chunk-C7G6YPKG-D7Me5JQU.js → chunk-C7G6YPKG-DNQ-JhD9.js} +1 -1
  27. package/web-dist/assets/{chunk-EX3LRPZG-CTa4YlOO.js → chunk-EX3LRPZG-BQCebwoA.js} +1 -1
  28. package/web-dist/assets/{chunk-FWX5IMBZ-CjqOt5Jn.js → chunk-FWX5IMBZ-CqiHtpEB.js} +2 -2
  29. package/web-dist/assets/{chunk-HOUHSVGY-BDIBDR-8.js → chunk-HOUHSVGY-Bop69PFg.js} +1 -1
  30. package/web-dist/assets/{chunk-ICXQ74PX-C4exiYRm.js → chunk-ICXQ74PX-D3PkeZ6B.js} +1 -1
  31. package/web-dist/assets/{chunk-MOJQB5TN-g8M_GVaO.js → chunk-MOJQB5TN-lidXw8-C.js} +1 -1
  32. package/web-dist/assets/{chunk-OGEWGWER-C_80lLFe.js → chunk-OGEWGWER-CzD91FAh.js} +1 -1
  33. package/web-dist/assets/{chunk-PUDLZKDR-B73Odmxv.js → chunk-PUDLZKDR-Cilkzvck.js} +1 -1
  34. package/web-dist/assets/{chunk-Q4XR5HBZ-D-VkroM1.js → chunk-Q4XR5HBZ-C37UQCfb.js} +1 -1
  35. package/web-dist/assets/{chunk-RYQCIY6F-DmPJ8s-8.js → chunk-RYQCIY6F-C0qnX-Ux.js} +1 -1
  36. package/web-dist/assets/{chunk-V7JOEXUC-BQKrRLYX.js → chunk-V7JOEXUC-Da5vOGHX.js} +1 -1
  37. package/web-dist/assets/{chunk-VAUOI2AC-DRKYvsWa.js → chunk-VAUOI2AC-FrJadrJY.js} +1 -1
  38. package/web-dist/assets/{chunk-VR4S4FIN-DaMMs6V-.js → chunk-VR4S4FIN-DYxK2Z50.js} +1 -1
  39. package/web-dist/assets/{chunk-WYO6CB5R-CQAWjeZH.js → chunk-WYO6CB5R-C164KTfT.js} +1 -1
  40. package/web-dist/assets/{chunk-XXDRQBXY-DJvt-abB.js → chunk-XXDRQBXY-D4wHtxet.js} +1 -1
  41. package/web-dist/assets/{chunk-ZGVPDNZ5-B1v24MrX.js → chunk-ZGVPDNZ5-BXKkVljT.js} +1 -1
  42. package/web-dist/assets/classDiagram-OUVF2IWQ-23bCXfvg.js +1 -0
  43. package/web-dist/assets/classDiagram-v2-EOCWNBFH-23bCXfvg.js +1 -0
  44. package/web-dist/assets/{cose-bilkent-JH36ORCC-CeNttASr.js → cose-bilkent-JH36ORCC-BFSgVzNT.js} +1 -1
  45. package/web-dist/assets/{cynefin-VYW2F7L2-ClJzZ-Yx.js → cynefin-VYW2F7L2-BSoEbT-s.js} +1 -1
  46. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-BCrL5Bu_.js → cynefinDiagram-TSTJHNR4-BBZgLbdX.js} +1 -1
  47. package/web-dist/assets/{dagre-VKFMJZFB-DO7qy19A.js → dagre-VKFMJZFB-Dx6giO7u.js} +1 -1
  48. package/web-dist/assets/{diagram-FQU43EPY-KPbJc2w8.js → diagram-FQU43EPY-BMI3qRca.js} +1 -1
  49. package/web-dist/assets/{diagram-G47NLZAW-9tqHFXzf.js → diagram-G47NLZAW-CJY-MA3L.js} +1 -1
  50. package/web-dist/assets/{diagram-NH7WQ7WH-BUbmU0cY.js → diagram-NH7WQ7WH-CbQVFJ3r.js} +1 -1
  51. package/web-dist/assets/{diagram-OA4YK3LP-QmMo96Qi.js → diagram-OA4YK3LP-CPnrakdY.js} +1 -1
  52. package/web-dist/assets/{diagram-WEI45ONY-BYAyhW16.js → diagram-WEI45ONY-D9DNwzlo.js} +1 -1
  53. package/web-dist/assets/{dist-Cewx01EF.js → dist-CO93_xVa.js} +1 -1
  54. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-BIMiEDl1.js → ebnfDiagram-CCIWWBDH-DNHudB8r.js} +1 -1
  55. package/web-dist/assets/{erDiagram-Q63AITRT-aLYQSs2d.js → erDiagram-Q63AITRT-Cwt63Xz8.js} +1 -1
  56. package/web-dist/assets/eventmodeling-45OFAUF4-Q3Nq1ecc.js +1 -0
  57. package/web-dist/assets/flowDiagram-23GEKE2U-B2snV3ot.js +1 -0
  58. package/web-dist/assets/{ganttDiagram-NO4QXBWP-Bu6_kuAX.js → ganttDiagram-NO4QXBWP-DiFpSJc2.js} +1 -1
  59. package/web-dist/assets/{gitGraph-TEB2WS4Q-yz2DuHDq.js → gitGraph-TEB2WS4Q-2-hTvDOu.js} +1 -1
  60. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-BXfbKaor.js → gitGraphDiagram-IHSO6WYX-mbc4Il71.js} +1 -1
  61. package/web-dist/assets/index-C6AXnNpg.css +2 -0
  62. package/web-dist/assets/index-DewBdeY0.js +352 -0
  63. package/web-dist/assets/{info-DKCQHKI2-BdItukhP.js → info-DKCQHKI2-BO0ZUEnn.js} +1 -1
  64. package/web-dist/assets/{infoDiagram-FWYZ7A6U-DXrq8wS3.js → infoDiagram-FWYZ7A6U-Cn3DmnjS.js} +1 -1
  65. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-iM2fPeJv.js → ishikawaDiagram-FXEZZL3T-Ckwh42aC.js} +1 -1
  66. package/web-dist/assets/{journeyDiagram-5HDEW3XC-DNa58xSH.js → journeyDiagram-5HDEW3XC-D26ySLLD.js} +1 -1
  67. package/web-dist/assets/{kanban-definition-HUTT4EX6-DhSaBCRv.js → kanban-definition-HUTT4EX6-CXKwXGxe.js} +1 -1
  68. package/web-dist/assets/{line-CQn0ApgD.js → line-BHqhqZTw.js} +1 -1
  69. package/web-dist/assets/{linear-DMSasryg.js → linear-Xq8ICqCe.js} +1 -1
  70. package/web-dist/assets/{mermaid-parser.core-BiJ3pVQT.js → mermaid-parser.core-ByagDKoG.js} +3 -3
  71. package/web-dist/assets/{mermaid.core-8ofZrJs5.js → mermaid.core-CEwbkqrb.js} +3 -3
  72. package/web-dist/assets/{mindmap-definition-LN4V7U3C-BLr4IGuI.js → mindmap-definition-LN4V7U3C-C3S3nEo5.js} +1 -1
  73. package/web-dist/assets/{packet-7NZHBO7P-D15LrSHU.js → packet-7NZHBO7P-BjcgqWf7.js} +1 -1
  74. package/web-dist/assets/{pegDiagram-2B236MQR-C9IcvZ_z.js → pegDiagram-2B236MQR-Due7wPzY.js} +1 -1
  75. package/web-dist/assets/{pie-RZYD4A2V-79-1CSta.js → pie-RZYD4A2V-C1X-Fm2K.js} +1 -1
  76. package/web-dist/assets/{pieDiagram-ENE6RG2P-dm_ORKO7.js → pieDiagram-ENE6RG2P-CbKS5jFv.js} +1 -1
  77. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-vQkbT0Ej.js → quadrantDiagram-ABIIQ3AL-CVF1CNNO.js} +1 -1
  78. package/web-dist/assets/{radar-I7S5WNFK-B8xvrM0r.js → radar-I7S5WNFK-Co7Otr5p.js} +1 -1
  79. package/web-dist/assets/{railroad-3IZDKUUU-G0rN-YuN.js → railroad-3IZDKUUU-BTwZqHtN.js} +1 -1
  80. package/web-dist/assets/railroad-abnf-AHOZXSZD-CEkmgARz.js +1 -0
  81. package/web-dist/assets/railroad-ebnf-EBAXGLYW-D2dXzrFd.js +1 -0
  82. package/web-dist/assets/railroad-peg-LSFZ7HO6-yA2XVaxN.js +1 -0
  83. package/web-dist/assets/{railroadDiagram-RFXS5EU6-BtGt9H2_.js → railroadDiagram-RFXS5EU6-DCymlvVU.js} +1 -1
  84. package/web-dist/assets/{requirementDiagram-TGXJPOKE-oHUUvJjC.js → requirementDiagram-TGXJPOKE-CEFbAu30.js} +1 -1
  85. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-BoO0DzUn.js → sankeyDiagram-HTMAVEWB-fnX7WrAj.js} +1 -1
  86. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-B344KPVn.js → sequenceDiagram-DBY2YBRQ-BUvsAYkQ.js} +1 -1
  87. package/web-dist/assets/{src-BygbIYKj.js → src-V5VjKILh.js} +1 -1
  88. package/web-dist/assets/{stateDiagram-2N3HPSRC-DMTN-MuE.js → stateDiagram-2N3HPSRC-C9LJKbHM.js} +1 -1
  89. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-Bm1cPGFT.js +1 -0
  90. package/web-dist/assets/{swimlanes-5IMT3BWC-DQEhwI0k.js → swimlanes-5IMT3BWC-aq9B5aBs.js} +1 -1
  91. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DR6Yx_Ic.js +8 -0
  92. package/web-dist/assets/{timeline-definition-FHXFAJF6-CUoCgDT7.js → timeline-definition-FHXFAJF6-DhJ9Jf4f.js} +1 -1
  93. package/web-dist/assets/{treeView-QDETBFTQ-BTHFGWtv.js → treeView-QDETBFTQ-D42FrEH4.js} +1 -1
  94. package/web-dist/assets/{treemap-6X3UGDF4-BI2j1PYm.js → treemap-6X3UGDF4-BV_9JyG6.js} +1 -1
  95. package/web-dist/assets/{vennDiagram-L72KCM5P-Cc3sJIb2.js → vennDiagram-L72KCM5P-BkwHzxsN.js} +1 -1
  96. package/web-dist/assets/{wardley-OPB4EBWU-kYXYWMgL.js → wardley-OPB4EBWU-fwm52ei3.js} +1 -1
  97. package/web-dist/assets/{wardleyDiagram-EHGQE667-KHMHPO99.js → wardleyDiagram-EHGQE667-D_UqvgcE.js} +1 -1
  98. package/web-dist/assets/{xychartDiagram-FW5EYKEG-DzSZk-Dq.js → xychartDiagram-FW5EYKEG-tKdmcTfS.js} +1 -1
  99. package/web-dist/index.html +2 -2
  100. package/web-dist/assets/architecture-TIHT7OUA-Czzg2iAA.js +0 -1
  101. package/web-dist/assets/channel-CxehNUC2.js +0 -1
  102. package/web-dist/assets/classDiagram-OUVF2IWQ-BwIDe0nq.js +0 -1
  103. package/web-dist/assets/classDiagram-v2-EOCWNBFH-BwIDe0nq.js +0 -1
  104. package/web-dist/assets/eventmodeling-45OFAUF4-jYGf30I4.js +0 -1
  105. package/web-dist/assets/flowDiagram-23GEKE2U-uJJwGAew.js +0 -1
  106. package/web-dist/assets/index-DS1RBTF4.js +0 -336
  107. package/web-dist/assets/index-DX1b-Y8G.css +0 -2
  108. package/web-dist/assets/railroad-abnf-AHOZXSZD-BaCc6741.js +0 -1
  109. package/web-dist/assets/railroad-ebnf-EBAXGLYW-cUMY4WQi.js +0 -1
  110. package/web-dist/assets/railroad-peg-LSFZ7HO6-JS2RjlNP.js +0 -1
  111. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-4HUOVSEd.js +0 -1
  112. package/web-dist/assets/swimlanesDiagram-G3AALYLV-PPFuhngt.js +0 -8
@@ -2,8 +2,9 @@ import { createRequire as __frizzCreateRequire } from "node:module"; const requi
2
2
 
3
3
  // packages/server/src/backend/claude-agent-broker.ts
4
4
  import net from "node:net";
5
- import { readFileSync as readFileSync6, realpathSync as realpathSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
6
- import { join as join8 } from "node:path";
5
+ import { readFileSync as readFileSync8, realpathSync as realpathSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
6
+ import { homedir as homedir3 } from "node:os";
7
+ import { join as join11 } from "node:path";
7
8
  import { randomUUID } from "node:crypto";
8
9
  import { fileURLToPath } from "node:url";
9
10
 
@@ -26500,7 +26501,7 @@ var BoundedAsyncQueue = class {
26500
26501
  if (value !== void 0) return Promise.resolve({ done: false, value });
26501
26502
  if (this.failure) return Promise.reject(this.failure);
26502
26503
  if (this.ended) return Promise.resolve({ done: true, value: void 0 });
26503
- return new Promise((resolve3, reject) => this.waiters.push({ resolve: resolve3, reject }));
26504
+ return new Promise((resolve4, reject) => this.waiters.push({ resolve: resolve4, reject }));
26504
26505
  }
26505
26506
  [Symbol.asyncIterator]() {
26506
26507
  return this;
@@ -26582,6 +26583,7 @@ var RealClaudeQueryHandle = class {
26582
26583
  sdkQuery;
26583
26584
  input;
26584
26585
  diagnostic;
26586
+ rateLimit;
26585
26587
  lifecycleAbort;
26586
26588
  // id → the main-thread turn count at the moment it was sent. A Map rather than a Set plus a
26587
26589
  // parallel order array because insertion order IS send order, which is all the oldest-first
@@ -26591,15 +26593,16 @@ var RealClaudeQueryHandle = class {
26591
26593
  mainThreadResults = 0;
26592
26594
  providerProgressCovered = false;
26593
26595
  closePromise;
26594
- constructor(sdkQuery, input, sessionId, lifecycleAbort, redactor, diagnostic) {
26596
+ constructor(sdkQuery, input, sessionId, lifecycleAbort, redactor, diagnostic, rateLimit) {
26595
26597
  this.sdkQuery = sdkQuery;
26596
26598
  this.input = input;
26597
26599
  this.sessionId = sessionId;
26598
26600
  this.lifecycleAbort = lifecycleAbort;
26599
26601
  this.redactor = redactor;
26600
26602
  this.diagnostic = diagnostic;
26601
- this.readyPromise = new Promise((resolve3, reject) => {
26602
- this.resolveReady = resolve3;
26603
+ this.rateLimit = rateLimit;
26604
+ this.readyPromise = new Promise((resolve4, reject) => {
26605
+ this.resolveReady = resolve4;
26603
26606
  this.rejectReady = reject;
26604
26607
  });
26605
26608
  void this.readyPromise.catch(() => void 0);
@@ -26831,6 +26834,9 @@ var RealClaudeQueryHandle = class {
26831
26834
  async pump() {
26832
26835
  try {
26833
26836
  for await (const raw of this.sdkQuery) {
26837
+ if (this.rateLimit && raw.type === "rate_limit_event") {
26838
+ this.rateLimit(raw.rate_limit_info);
26839
+ }
26834
26840
  let event;
26835
26841
  try {
26836
26842
  event = mapSdkMessage(raw);
@@ -26967,6 +26973,7 @@ function startClaudeQuery(executablePath, options) {
26967
26973
  const environment = buildEnvironment(options.env);
26968
26974
  const redact = createClaudeDiagnosticRedactor(environment);
26969
26975
  const diagnostic = guardDiagnosticCallback(options.onDiagnostic);
26976
+ const rateLimit = guardObserverCallback(options.onRateLimit);
26970
26977
  const permissionMode = validatePermissionMode(options.permissionMode ?? "default");
26971
26978
  const lifecycleAbort = new AbortController();
26972
26979
  const permissionRequests = new BoundedIdempotencyCache(MAX_PERMISSION_REQUESTS, "Claude permission request cache");
@@ -27080,7 +27087,7 @@ function startClaudeQuery(executablePath, options) {
27080
27087
  }
27081
27088
  }
27082
27089
  });
27083
- const handle = new RealClaudeQueryHandle(raw, input, sessionId, lifecycleAbort, redact, diagnostic);
27090
+ const handle = new RealClaudeQueryHandle(raw, input, sessionId, lifecycleAbort, redact, diagnostic, rateLimit);
27084
27091
  if (mcpConfigFile) {
27085
27092
  const drop = () => {
27086
27093
  try {
@@ -27773,13 +27780,13 @@ function canonicalFingerprint(value) {
27773
27780
  }
27774
27781
  function abortableCallback(promise, signal, label) {
27775
27782
  if (signal.aborted) return Promise.reject(new ClaudeAgentSdkProtocolError(`${label} aborted`));
27776
- return new Promise((resolve3, reject) => {
27783
+ return new Promise((resolve4, reject) => {
27777
27784
  const abort = () => reject(new ClaudeAgentSdkProtocolError(`${label} aborted`));
27778
27785
  signal.addEventListener("abort", abort, { once: true });
27779
27786
  promise.then(
27780
27787
  (value) => {
27781
27788
  signal.removeEventListener("abort", abort);
27782
- resolve3(value);
27789
+ resolve4(value);
27783
27790
  },
27784
27791
  (error) => {
27785
27792
  signal.removeEventListener("abort", abort);
@@ -27797,6 +27804,15 @@ function guardDiagnosticCallback(callback) {
27797
27804
  }
27798
27805
  };
27799
27806
  }
27807
+ function guardObserverCallback(callback) {
27808
+ if (!callback) return void 0;
27809
+ return (value) => {
27810
+ try {
27811
+ callback(value);
27812
+ } catch {
27813
+ }
27814
+ };
27815
+ }
27800
27816
  function validateExecutablePath(value) {
27801
27817
  const path = validateAbsolutePath(value, "executablePath");
27802
27818
  try {
@@ -28232,14 +28248,14 @@ data: ${JSON.stringify(message)}
28232
28248
  }
28233
28249
  internalRequest(method, params) {
28234
28250
  const id2 = `frizz-lazy-${++this.nextInternalId}`;
28235
- return new Promise((resolve3) => {
28236
- this.internal.set(id2, resolve3);
28251
+ return new Promise((resolve4) => {
28252
+ this.internal.set(id2, resolve4);
28237
28253
  this.send({ jsonrpc: "2.0", id: id2, method, ...params === void 0 ? {} : { params } });
28238
28254
  });
28239
28255
  }
28240
28256
  forward(message) {
28241
- return new Promise((resolve3) => {
28242
- this.forwarded.set(JSON.stringify(message.id), resolve3);
28257
+ return new Promise((resolve4) => {
28258
+ this.forwarded.set(JSON.stringify(message.id), resolve4);
28243
28259
  this.send(message);
28244
28260
  });
28245
28261
  }
@@ -28275,18 +28291,18 @@ data: ${JSON.stringify(message)}
28275
28291
  const child = this.child;
28276
28292
  if (!child) return;
28277
28293
  this.child = void 0;
28278
- await new Promise((resolve3) => {
28294
+ await new Promise((resolve4) => {
28279
28295
  const timer = setTimeout(() => {
28280
28296
  try {
28281
28297
  child.kill("SIGKILL");
28282
28298
  } catch {
28283
28299
  }
28284
28300
  ;
28285
- resolve3();
28301
+ resolve4();
28286
28302
  }, 2e3);
28287
28303
  child.once("exit", () => {
28288
28304
  clearTimeout(timer);
28289
- resolve3();
28305
+ resolve4();
28290
28306
  });
28291
28307
  try {
28292
28308
  child.stdin?.end();
@@ -28300,7 +28316,7 @@ data: ${JSON.stringify(message)}
28300
28316
  }
28301
28317
  };
28302
28318
  function readBody(request) {
28303
- return new Promise((resolve3, reject) => {
28319
+ return new Promise((resolve4, reject) => {
28304
28320
  let size = 0;
28305
28321
  const chunks = [];
28306
28322
  request.on("data", (chunk) => {
@@ -28312,7 +28328,7 @@ function readBody(request) {
28312
28328
  }
28313
28329
  chunks.push(chunk);
28314
28330
  });
28315
- request.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
28331
+ request.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
28316
28332
  request.on("error", reject);
28317
28333
  });
28318
28334
  }
@@ -28363,13 +28379,13 @@ function startLazyMcpHost(options) {
28363
28379
  })();
28364
28380
  });
28365
28381
  httpServer.requestTimeout = 0;
28366
- return new Promise((resolve3, reject) => {
28382
+ return new Promise((resolve4, reject) => {
28367
28383
  httpServer.once("error", reject);
28368
28384
  httpServer.listen(0, "127.0.0.1", () => {
28369
28385
  httpServer.off("error", reject);
28370
28386
  const address = httpServer.address();
28371
28387
  const port = typeof address === "object" && address ? address.port : 0;
28372
- resolve3({
28388
+ resolve4({
28373
28389
  port,
28374
28390
  mount(stdio, context) {
28375
28391
  mkdirSync4(options.cacheDir, { recursive: true, mode: 448 });
@@ -28460,6 +28476,288 @@ function createClaudeBrokerExitWriter(path, meta, now = () => /* @__PURE__ */ ne
28460
28476
  };
28461
28477
  }
28462
28478
 
28479
+ // packages/server/src/backend/claude-plugin-sync.ts
28480
+ import { createHash as createHash4 } from "node:crypto";
28481
+ import { readFileSync as readFileSync7, readdirSync as readdirSync4, statSync as statSync5 } from "node:fs";
28482
+ import { join as join9 } from "node:path";
28483
+
28484
+ // packages/server/src/stable-plugin-path.ts
28485
+ import { createHash as createHash3 } from "node:crypto";
28486
+ import { cpSync, lstatSync as lstatSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, readlinkSync as readlinkSync2, realpathSync as realpathSync2, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync4, symlinkSync } from "node:fs";
28487
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join8, resolve as resolve3 } from "node:path";
28488
+ var LINK_TYPE = process.platform === "win32" ? "junction" : void 0;
28489
+ function pluginFiles(root) {
28490
+ const files = [];
28491
+ const walk = (dir, prefix) => {
28492
+ for (const name of readdirSync3(dir).sort()) {
28493
+ const rel = prefix ? `${prefix}/${name}` : name;
28494
+ if (statSync4(join8(dir, name)).isDirectory()) walk(join8(dir, name), rel);
28495
+ else files.push(rel);
28496
+ }
28497
+ };
28498
+ walk(root, "");
28499
+ return files;
28500
+ }
28501
+ function pluginFingerprint(dir) {
28502
+ try {
28503
+ const root = realpathSync2(dir);
28504
+ const hash = createHash3("sha1").update(root);
28505
+ for (const rel of pluginFiles(root)) {
28506
+ const stat = statSync4(join8(root, rel));
28507
+ hash.update(`\0${rel}\0${stat.size}\0${stat.mtimeMs}`);
28508
+ }
28509
+ return hash.digest("hex");
28510
+ } catch {
28511
+ return void 0;
28512
+ }
28513
+ }
28514
+
28515
+ // packages/server/src/backend/claude-plugin-sync.ts
28516
+ function claudePluginSyncEnabled(env = process.env) {
28517
+ return env.FRIZZ_PLUGIN_SYNC_OFF !== "1";
28518
+ }
28519
+ var RELOAD_DEADLINE_MS = 8e3;
28520
+ var RELOAD_ATTEMPTS = 2;
28521
+ var ClosureUnreadable = class extends Error {
28522
+ };
28523
+ function claudeDirEntries(root, entries) {
28524
+ const stamp = (path) => {
28525
+ try {
28526
+ const stat = statSync5(path);
28527
+ if (stat.isFile()) entries.push(`${path}\0${stat.size}\0${stat.mtimeMs}`);
28528
+ } catch {
28529
+ }
28530
+ };
28531
+ const list = (dir) => {
28532
+ try {
28533
+ return readdirSync4(dir).sort();
28534
+ } catch {
28535
+ return [];
28536
+ }
28537
+ };
28538
+ for (const name of list(join9(root, "skills"))) stamp(join9(root, "skills", name, "SKILL.md"));
28539
+ const markdownUnder = (dir, depth) => {
28540
+ for (const name of list(dir)) {
28541
+ const path = join9(dir, name);
28542
+ if (name.endsWith(".md")) stamp(path);
28543
+ else if (depth < 4) markdownUnder(path, depth + 1);
28544
+ }
28545
+ };
28546
+ markdownUnder(join9(root, "agents"), 0);
28547
+ markdownUnder(join9(root, "commands"), 0);
28548
+ for (const file of ["settings.json", "settings.local.json"]) {
28549
+ let raw;
28550
+ try {
28551
+ raw = readFileSync7(join9(root, file), "utf8");
28552
+ } catch {
28553
+ continue;
28554
+ }
28555
+ let enabled;
28556
+ try {
28557
+ enabled = JSON.parse(raw)?.enabledPlugins;
28558
+ } catch {
28559
+ throw new ClosureUnreadable(file);
28560
+ }
28561
+ entries.push(`${join9(root, file)}\0${JSON.stringify(enabled ?? null)}`);
28562
+ }
28563
+ stamp(join9(root, "plugins", "installed_plugins.json"));
28564
+ }
28565
+ function claudeClosureFingerprint(closure) {
28566
+ const plugin = closure.pluginDir ? pluginFingerprint(closure.pluginDir) : "";
28567
+ if (plugin === void 0) return void 0;
28568
+ const entries = [plugin];
28569
+ try {
28570
+ claudeDirEntries(join9(closure.projectDir, ".claude"), entries);
28571
+ claudeDirEntries(closure.configDir, entries);
28572
+ } catch {
28573
+ return void 0;
28574
+ }
28575
+ return createHash4("sha1").update(entries.join("\n")).digest("hex");
28576
+ }
28577
+ function createClaudePluginSync(deps) {
28578
+ let loaded = deps.fingerprint();
28579
+ let busy = false;
28580
+ let inFlight = null;
28581
+ let failed;
28582
+ const say = (message) => {
28583
+ try {
28584
+ deps.log(message);
28585
+ } catch {
28586
+ }
28587
+ };
28588
+ const reloadWithinDeadline = () => new Promise((resolve4, reject) => {
28589
+ const timer = setTimeout(() => reject(new Error("the session did not answer in time")), deps.deadlineMs ?? RELOAD_DEADLINE_MS);
28590
+ timer.unref?.();
28591
+ deps.reload().then(
28592
+ (result) => {
28593
+ clearTimeout(timer);
28594
+ resolve4(result);
28595
+ },
28596
+ (error) => {
28597
+ clearTimeout(timer);
28598
+ reject(error);
28599
+ }
28600
+ );
28601
+ });
28602
+ const check = () => {
28603
+ if (inFlight) return;
28604
+ const current = deps.fingerprint();
28605
+ if (current === void 0 || current === loaded) return;
28606
+ if (failed?.fingerprint === current && failed.attempts >= RELOAD_ATTEMPTS) return;
28607
+ const attempt = reloadWithinDeadline().then(
28608
+ (result) => {
28609
+ loaded = current;
28610
+ failed = void 0;
28611
+ say(`plugin closure changed on disk; reloaded in place${result.errorCount ? ` with ${result.errorCount} load error(s)` : ""}`);
28612
+ },
28613
+ (error) => {
28614
+ failed = { fingerprint: current, attempts: failed?.fingerprint === current ? failed.attempts + 1 : 1 };
28615
+ say(`plugin closure changed on disk but the reload failed (attempt ${failed.attempts}/${RELOAD_ATTEMPTS}): ${error instanceof Error ? error.message : String(error)}`);
28616
+ }
28617
+ ).catch(() => void 0).finally(() => {
28618
+ if (inFlight === attempt) inFlight = null;
28619
+ });
28620
+ inFlight = attempt;
28621
+ };
28622
+ return {
28623
+ beforeInput(startsTurn) {
28624
+ if (startsTurn) {
28625
+ if (!busy) check();
28626
+ busy = true;
28627
+ }
28628
+ return inFlight;
28629
+ },
28630
+ beforeRead() {
28631
+ if (!busy) check();
28632
+ return inFlight;
28633
+ },
28634
+ turnActive() {
28635
+ busy = true;
28636
+ },
28637
+ turnEnded() {
28638
+ busy = false;
28639
+ check();
28640
+ }
28641
+ };
28642
+ }
28643
+
28644
+ // packages/server/src/backend/claude-quota-observed.ts
28645
+ import { createHash as createHash5 } from "node:crypto";
28646
+ import { mkdir, readFile, rename, rm as rm2, writeFile } from "node:fs/promises";
28647
+ import { join as join10 } from "node:path";
28648
+ var WINDOW_IDENTITY = {
28649
+ five_hour: ["5h", "5h"],
28650
+ seven_day: ["weekly", "Weekly"],
28651
+ seven_day_opus: ["weekly-opus", "Opus wk"],
28652
+ seven_day_sonnet: ["weekly-sonnet", "Sonnet wk"]
28653
+ };
28654
+ var OBSERVED_MAX_AGE_MS = 24 * 60 * 6e4;
28655
+ var REWRITE_INTERVAL_MS = 3e4;
28656
+ function finite(value) {
28657
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
28658
+ }
28659
+ function toWindow(name, utilization, resetsAt) {
28660
+ const identity = WINDOW_IDENTITY[name];
28661
+ const fraction = finite(utilization);
28662
+ if (!identity || fraction === void 0 || fraction < 0) return void 0;
28663
+ const resets = finite(resetsAt);
28664
+ return {
28665
+ key: identity[0],
28666
+ label: identity[1],
28667
+ // 0..1 → the endpoint's 0..100, without the binary-float tail (0.65 * 100 is 65.00000000000001).
28668
+ usedPercent: Math.round(fraction * 1e4) / 100,
28669
+ ...resets !== void 0 && resets > 0 ? { resetsAt: Math.round(resets) } : {}
28670
+ };
28671
+ }
28672
+ function windowsFromRateLimitInfo(info) {
28673
+ if (!info || typeof info !== "object") return [];
28674
+ const raw = info;
28675
+ const windows = /* @__PURE__ */ new Map();
28676
+ if (raw.unifiedWindows && typeof raw.unifiedWindows === "object") {
28677
+ for (const [name, value] of Object.entries(raw.unifiedWindows)) {
28678
+ if (!value || typeof value !== "object") continue;
28679
+ const w = value;
28680
+ const window = toWindow(name, w.utilization, w.resetsAt);
28681
+ if (window) windows.set(window.key, window);
28682
+ }
28683
+ }
28684
+ if (typeof raw.rateLimitType === "string") {
28685
+ const window = toWindow(raw.rateLimitType, raw.utilization, raw.resetsAt);
28686
+ if (window && !windows.has(window.key)) windows.set(window.key, window);
28687
+ const identity = WINDOW_IDENTITY[raw.rateLimitType];
28688
+ if (raw.status === "rejected" && identity) {
28689
+ const standing = windows.get(identity[0]);
28690
+ const resets = finite(raw.resetsAt);
28691
+ windows.set(identity[0], {
28692
+ key: identity[0],
28693
+ label: identity[1],
28694
+ usedPercent: Math.max(100, standing?.usedPercent ?? 0),
28695
+ ...standing?.resetsAt !== void 0 ? { resetsAt: standing.resetsAt } : resets !== void 0 && resets > 0 ? { resetsAt: Math.round(resets) } : {}
28696
+ });
28697
+ }
28698
+ }
28699
+ return [...windows.values()];
28700
+ }
28701
+ function observedQuotaPath(cacheDir, configDir) {
28702
+ const profile = createHash5("sha256").update(configDir).digest("hex").slice(0, 12);
28703
+ return join10(cacheDir, `claude-observed-${profile}.json`);
28704
+ }
28705
+ function live(window, now) {
28706
+ if (now - window.at >= OBSERVED_MAX_AGE_MS) return false;
28707
+ return window.resetsAt === void 0 || window.resetsAt * 1e3 > now;
28708
+ }
28709
+ async function readObservedClaudeQuota(cacheDir, configDir, now = Date.now()) {
28710
+ let parsed;
28711
+ try {
28712
+ parsed = JSON.parse(await readFile(observedQuotaPath(cacheDir, configDir), "utf8"));
28713
+ } catch {
28714
+ return void 0;
28715
+ }
28716
+ const list = parsed?.windows;
28717
+ if (!Array.isArray(list)) return void 0;
28718
+ const windows = [];
28719
+ for (const entry of list) {
28720
+ if (!entry || typeof entry !== "object") continue;
28721
+ const w = entry;
28722
+ const at2 = finite(w.at);
28723
+ const usedPercent = finite(w.usedPercent);
28724
+ const resetsAt = finite(w.resetsAt);
28725
+ if (typeof w.key !== "string" || typeof w.label !== "string" || at2 === void 0 || usedPercent === void 0) continue;
28726
+ const window = { key: w.key, label: w.label, usedPercent, at: at2, ...resetsAt === void 0 ? {} : { resetsAt } };
28727
+ if (live(window, now)) windows.push(window);
28728
+ }
28729
+ if (windows.length === 0) return void 0;
28730
+ return { at: Math.max(...windows.map((w) => w.at)), windows };
28731
+ }
28732
+ var lastWritten = /* @__PURE__ */ new Map();
28733
+ async function recordObservedClaudeQuota(info, where) {
28734
+ try {
28735
+ const now = where.now ?? Date.now();
28736
+ const reported = windowsFromRateLimitInfo(info);
28737
+ if (reported.length === 0) return false;
28738
+ const path = observedQuotaPath(where.cacheDir, where.configDir);
28739
+ const signature = JSON.stringify(reported);
28740
+ const last = lastWritten.get(path);
28741
+ if (last && last.signature === signature && now - last.at < REWRITE_INTERVAL_MS) return false;
28742
+ const standing = await readObservedClaudeQuota(where.cacheDir, where.configDir, now);
28743
+ const windows = new Map((standing?.windows ?? []).map((w) => [w.key, w]));
28744
+ for (const window of reported) windows.set(window.key, { ...window, at: now });
28745
+ await mkdir(where.cacheDir, { recursive: true, mode: 448 });
28746
+ const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
28747
+ try {
28748
+ await writeFile(tmp, JSON.stringify({ at: now, windows: [...windows.values()] }), { mode: 384 });
28749
+ await rename(tmp, path);
28750
+ } finally {
28751
+ await rm2(tmp, { force: true }).catch(() => {
28752
+ });
28753
+ }
28754
+ lastWritten.set(path, { at: now, signature });
28755
+ return true;
28756
+ } catch {
28757
+ return false;
28758
+ }
28759
+ }
28760
+
28463
28761
  // packages/server/src/backend/claude-agent-broker.ts
28464
28762
  var BROKER_CAPABILITIES = [CLAUDE_BROKER_CAPABILITY_SUBAGENT_STEER, CLAUDE_BROKER_CAPABILITY_CANCEL_INPUT, CLAUDE_BROKER_CAPABILITY_STOP_TASK, CLAUDE_BROKER_CAPABILITY_RELOAD_PLUGINS, CLAUDE_BROKER_CAPABILITY_RENAME, CLAUDE_BROKER_CAPABILITY_LIST_SKILLS, CLAUDE_BROKER_CAPABILITY_INPUT_ACK];
28465
28763
  var IDLE_EXIT_MS = 6 * 60 * 60 * 1e3;
@@ -28506,6 +28804,17 @@ function runClaudeBroker(config, options = {}) {
28506
28804
  const releaseRuntimeLease = leaseRuntime(config.executablePath, "claude-broker");
28507
28805
  const factory = createClaudeQueryFactory({ enabled: true, executablePath: config.executablePath });
28508
28806
  const env = { ...inheritWorkerEnvironment(config.env), ...config.workerEnv ?? {} };
28807
+ const closure = {
28808
+ pluginDir: config.pluginDir,
28809
+ projectDir: config.cwd,
28810
+ // Where claude itself will look, from the environment it is about to be started with.
28811
+ configDir: env.CLAUDE_CONFIG_DIR?.trim() || join11(env.HOME || homedir3(), ".claude")
28812
+ };
28813
+ const pluginSync = claudePluginSyncEnabled() ? createClaudePluginSync({
28814
+ fingerprint: () => claudeClosureFingerprint(closure),
28815
+ reload: () => handle.reloadPlugins(),
28816
+ log: (message) => writeDiagnostic?.({ kind: "stderr", message: `[plugin-sync] ${message}`, truncated: false })
28817
+ }) : void 0;
28509
28818
  const handle = factory.start({
28510
28819
  cwd: config.cwd,
28511
28820
  session: config.resume ? { kind: "resume", sessionId: config.sessionId } : { kind: "new", sessionId: config.sessionId },
@@ -28544,7 +28853,7 @@ function runClaudeBroker(config, options = {}) {
28544
28853
  // and unwinds the tool call (see retirePendingFor in the bridge), so a parked turn is still steerable.
28545
28854
  canUseTool: async (request, context) => {
28546
28855
  const requestId = `perm-${++permSeq}`;
28547
- return await new Promise((resolve3) => {
28856
+ return await new Promise((resolve4) => {
28548
28857
  let settled = false;
28549
28858
  let showTimer;
28550
28859
  const entry = { request, shown: false, resolve: (decision) => settle(decision) };
@@ -28553,7 +28862,7 @@ function runClaudeBroker(config, options = {}) {
28553
28862
  settled = true;
28554
28863
  clearTimeout(showTimer);
28555
28864
  pendingPermissions.delete(requestId);
28556
- resolve3(decision);
28865
+ resolve4(decision);
28557
28866
  };
28558
28867
  const withdraw = () => {
28559
28868
  const wasShown = entry.shown;
@@ -28572,6 +28881,12 @@ function runClaudeBroker(config, options = {}) {
28572
28881
  }
28573
28882
  });
28574
28883
  },
28884
+ // The account's plan windows, as every API response reports them to this session. Written by THIS
28885
+ // process because it is the one that sees the stream and it outlives the Frizz server; the server
28886
+ // only reads the file (claude-quota-observed.ts). Fire-and-forget: the recorder never rejects.
28887
+ onRateLimit: (info) => {
28888
+ void recordObservedClaudeQuota(info, { cacheDir: join11(frizzRoots().cache, "quota-cache"), configDir: closure.configDir });
28889
+ },
28575
28890
  onDiagnostic: (diagnostic) => {
28576
28891
  writeDiagnostic?.(diagnostic);
28577
28892
  if (client) write(client, { t: "diagnostic", diagnostic });
@@ -28607,6 +28922,8 @@ function runClaudeBroker(config, options = {}) {
28607
28922
  consecutiveErrors = 0;
28608
28923
  if (next.done) return { reason: "session-stream-ended" };
28609
28924
  emitEvent(next.value);
28925
+ if (next.value.kind === "result") pluginSync?.turnEnded();
28926
+ else if (next.value.kind === "assistant" || next.value.kind === "user") pluginSync?.turnActive();
28610
28927
  }
28611
28928
  })().then(
28612
28929
  (end) => shutdown(0, end.reason, end.detail),
@@ -28648,7 +28965,7 @@ function runClaudeBroker(config, options = {}) {
28648
28965
  message: `input received: id=${message?.id ?? "?"} chars=${typeof message?.text === "string" ? message.text.length : 0}${message?.parentToolUseId ? ` addressed=${message.parentToolUseId}` : ""}`,
28649
28966
  truncated: false
28650
28967
  });
28651
- void handle.send(message).then(() => {
28968
+ const forward = () => void handle.send(message).then(() => {
28652
28969
  if (requestId) write(sock, { t: "input-result", requestId });
28653
28970
  }, (error) => {
28654
28971
  const detail = error instanceof Error ? error.message : String(error);
@@ -28658,6 +28975,9 @@ function runClaudeBroker(config, options = {}) {
28658
28975
  writeDiagnostic?.(diagnostic);
28659
28976
  if (client) write(client, { t: "diagnostic", diagnostic });
28660
28977
  });
28978
+ const reloading = pluginSync?.beforeInput(!message?.parentToolUseId);
28979
+ if (reloading) void reloading.then(forward);
28980
+ else forward();
28661
28981
  seedSessionTitle(message);
28662
28982
  } else if (msg.t === "permission") {
28663
28983
  const e13 = pendingPermissions.get(msg.requestId);
@@ -28696,7 +29016,7 @@ function runClaudeBroker(config, options = {}) {
28696
29016
  );
28697
29017
  } else if (msg.t === "list-skills") {
28698
29018
  const requestId = msg.requestId;
28699
- void handle.listSkills().then(
29019
+ void (pluginSync?.beforeRead() ?? Promise.resolve()).then(() => handle.listSkills()).then(
28700
29020
  (skills) => write(sock, { t: "skills-result", requestId, skills }),
28701
29021
  (error) => write(sock, { t: "skills-result", requestId, error: error instanceof Error ? error.message : String(error) })
28702
29022
  );
@@ -28717,7 +29037,7 @@ function runClaudeBroker(config, options = {}) {
28717
29037
  const recordOwner = () => {
28718
29038
  if (!config.recordPath) return null;
28719
29039
  try {
28720
- return JSON.parse(readFileSync6(config.recordPath, "utf8")).daemonPid;
29040
+ return JSON.parse(readFileSync8(config.recordPath, "utf8")).daemonPid;
28721
29041
  } catch {
28722
29042
  return null;
28723
29043
  }
@@ -28812,7 +29132,7 @@ function startedAsProcessEntry() {
28812
29132
  const entry = process.argv[1];
28813
29133
  if (!entry) return false;
28814
29134
  try {
28815
- return realpathSync2(fileURLToPath(import.meta.url)) === realpathSync2(entry);
29135
+ return realpathSync3(fileURLToPath(import.meta.url)) === realpathSync3(entry);
28816
29136
  } catch {
28817
29137
  return false;
28818
29138
  }
@@ -28843,7 +29163,7 @@ ${error.stack ?? ""}`.slice(0, 2e3) : String(error));
28843
29163
  ${reason.stack ?? ""}`.slice(0, 2e3) : String(reason));
28844
29164
  process.exit(1);
28845
29165
  });
28846
- const lazyMcpHost = lazyMcpEnabled() ? startLazyMcpHost({ cacheDir: join8(frizzRoots().data, "mcp-cache") }) : Promise.resolve(void 0);
29166
+ const lazyMcpHost = lazyMcpEnabled() ? startLazyMcpHost({ cacheDir: join11(frizzRoots().data, "mcp-cache") }) : Promise.resolve(void 0);
28847
29167
  void lazyMcpHost.catch(() => void 0).then((host) => {
28848
29168
  running = runClaudeBroker(config, { lazyMcpHost: host });
28849
29169
  });