frizz-server 0.13.6 → 0.13.8

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 (93) hide show
  1. package/dist/claude-agent-broker.js +9604 -9545
  2. package/dist/dev-child.js +1894 -296
  3. package/package.json +1 -1
  4. package/web-dist/assets/{TerminalPane-P7iAI_rU.js → TerminalPane-CgFt4ple.js} +1 -1
  5. package/web-dist/assets/{abnfDiagram-VRR7QNED-B0JxaKjB.js → abnfDiagram-VRR7QNED-DoVWUENi.js} +1 -1
  6. package/web-dist/assets/architecture-TIHT7OUA-DJ_aT0bS.js +1 -0
  7. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-E8bWaWhF.js → architectureDiagram-ZJ3FMSHR-BE2vu5s4.js} +1 -1
  8. package/web-dist/assets/{blockDiagram-677ZJIJ3-BNg7FcxS.js → blockDiagram-677ZJIJ3-D6LvUZ6S.js} +1 -1
  9. package/web-dist/assets/{c4Diagram-LMCZKHZV-BHkry-JG.js → c4Diagram-LMCZKHZV-BDZel4B4.js} +1 -1
  10. package/web-dist/assets/channel-D3v1O4Wy.js +1 -0
  11. package/web-dist/assets/{chunk-32BRIVSS-BJLx0sIU.js → chunk-32BRIVSS-fmjhgloi.js} +1 -1
  12. package/web-dist/assets/{chunk-52WLFC77-DmMATfuh.js → chunk-52WLFC77-sfh419xE.js} +1 -1
  13. package/web-dist/assets/{chunk-C7G6YPKG-Ceg42lXA.js → chunk-C7G6YPKG-D-LGx-oa.js} +1 -1
  14. package/web-dist/assets/{chunk-EX3LRPZG-CAx16zp2.js → chunk-EX3LRPZG-DriDj_NL.js} +1 -1
  15. package/web-dist/assets/{chunk-FWX5IMBZ-DWb22U8j.js → chunk-FWX5IMBZ-DetgkllZ.js} +2 -2
  16. package/web-dist/assets/{chunk-HOUHSVGY-bKZVUQyT.js → chunk-HOUHSVGY-CDGGE0PL.js} +1 -1
  17. package/web-dist/assets/{chunk-ICXQ74PX-C3i_eW5g.js → chunk-ICXQ74PX-CIgu37mb.js} +1 -1
  18. package/web-dist/assets/{chunk-MOJQB5TN-Bt6x6J8D.js → chunk-MOJQB5TN-BNj77oeQ.js} +1 -1
  19. package/web-dist/assets/{chunk-OGEWGWER-BwJ73hmt.js → chunk-OGEWGWER-BRoVc26C.js} +1 -1
  20. package/web-dist/assets/{chunk-PUDLZKDR-DfcEHpjL.js → chunk-PUDLZKDR-B9bHTS14.js} +1 -1
  21. package/web-dist/assets/{chunk-Q4XR5HBZ-VYeS43tP.js → chunk-Q4XR5HBZ-CjqwAtw6.js} +1 -1
  22. package/web-dist/assets/{chunk-V7JOEXUC-Cs1_YKkF.js → chunk-V7JOEXUC-ClYL5Jo9.js} +1 -1
  23. package/web-dist/assets/{chunk-VAUOI2AC-CCOn0B9D.js → chunk-VAUOI2AC-DsyULg7-.js} +1 -1
  24. package/web-dist/assets/{chunk-VR4S4FIN-CL_PLrUe.js → chunk-VR4S4FIN-x_8ywB7J.js} +1 -1
  25. package/web-dist/assets/{chunk-WYO6CB5R-Dupt_rk_.js → chunk-WYO6CB5R-BDWUsOuN.js} +1 -1
  26. package/web-dist/assets/{chunk-ZGVPDNZ5-UwgE4khW.js → chunk-ZGVPDNZ5-wAbPuGaE.js} +1 -1
  27. package/web-dist/assets/classDiagram-OUVF2IWQ-LvSRVuLl.js +1 -0
  28. package/web-dist/assets/classDiagram-v2-EOCWNBFH-LvSRVuLl.js +1 -0
  29. package/web-dist/assets/{cynefin-VYW2F7L2-DGZJYJNa.js → cynefin-VYW2F7L2-DLLNSytS.js} +1 -1
  30. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-B34DgCmC.js → cynefinDiagram-TSTJHNR4-6kBJrScm.js} +1 -1
  31. package/web-dist/assets/{dagre-VKFMJZFB-C7HODJG3.js → dagre-VKFMJZFB-Z_OVqFBK.js} +1 -1
  32. package/web-dist/assets/{diagram-FQU43EPY-CunP-lqf.js → diagram-FQU43EPY-C_nG5FUG.js} +1 -1
  33. package/web-dist/assets/{diagram-G47NLZAW-C3O84bOk.js → diagram-G47NLZAW-CYQTveO9.js} +1 -1
  34. package/web-dist/assets/{diagram-NH7WQ7WH-Dv0TR4Ic.js → diagram-NH7WQ7WH-C2DkBAm5.js} +1 -1
  35. package/web-dist/assets/{diagram-OA4YK3LP-CUvcZIxp.js → diagram-OA4YK3LP-CSDwzhMR.js} +1 -1
  36. package/web-dist/assets/{diagram-WEI45ONY-CwPr97NN.js → diagram-WEI45ONY-R1MvefNj.js} +1 -1
  37. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-Dq2O1mOC.js → ebnfDiagram-CCIWWBDH-DBXUjfz2.js} +1 -1
  38. package/web-dist/assets/{erDiagram-Q63AITRT-C6dJ8Dpx.js → erDiagram-Q63AITRT-D2XH_vxG.js} +1 -1
  39. package/web-dist/assets/eventmodeling-45OFAUF4-DQtNFbyn.js +1 -0
  40. package/web-dist/assets/flowDiagram-23GEKE2U-DPAGYT4s.js +1 -0
  41. package/web-dist/assets/{ganttDiagram-NO4QXBWP-sa-TSsB5.js → ganttDiagram-NO4QXBWP-BqJqGmJu.js} +1 -1
  42. package/web-dist/assets/{gitGraph-TEB2WS4Q-BdZIuH-o.js → gitGraph-TEB2WS4Q-BpED4Y4w.js} +1 -1
  43. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-C__OynR9.js → gitGraphDiagram-IHSO6WYX-CpCzUYgA.js} +1 -1
  44. package/web-dist/assets/index-DfJaB5hp.css +1 -0
  45. package/web-dist/assets/index-vTgjYcOv.js +319 -0
  46. package/web-dist/assets/{info-DKCQHKI2-BEaDQqRu.js → info-DKCQHKI2-Bm1fWKNN.js} +1 -1
  47. package/web-dist/assets/{infoDiagram-FWYZ7A6U-RJlw-Bzq.js → infoDiagram-FWYZ7A6U-C61BGKNf.js} +1 -1
  48. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-xop_QrWs.js → ishikawaDiagram-FXEZZL3T-0Fmof1Bc.js} +1 -1
  49. package/web-dist/assets/{journeyDiagram-5HDEW3XC-O6nmpoje.js → journeyDiagram-5HDEW3XC-DHuJoS_U.js} +1 -1
  50. package/web-dist/assets/{kanban-definition-HUTT4EX6-IXwPs_L0.js → kanban-definition-HUTT4EX6-ClLsOpPq.js} +1 -1
  51. package/web-dist/assets/{line-BMRguCzE.js → line-BQNlaa-L.js} +1 -1
  52. package/web-dist/assets/{mermaid-parser.core-BCXFwFN1.js → mermaid-parser.core-Dhg0FCF0.js} +3 -3
  53. package/web-dist/assets/{mermaid.core-Cqq_9ya3.js → mermaid.core-OSL7u28D.js} +3 -3
  54. package/web-dist/assets/{mindmap-definition-LN4V7U3C-DEfWfnOg.js → mindmap-definition-LN4V7U3C-CMh2RRdG.js} +1 -1
  55. package/web-dist/assets/{packet-7NZHBO7P-CQGAnngT.js → packet-7NZHBO7P-DqizytaZ.js} +1 -1
  56. package/web-dist/assets/{pegDiagram-2B236MQR-tOrSsHlA.js → pegDiagram-2B236MQR-C_xsQ7ey.js} +1 -1
  57. package/web-dist/assets/{pie-RZYD4A2V-C0KP3XF8.js → pie-RZYD4A2V-Dg0NeJ6P.js} +1 -1
  58. package/web-dist/assets/{pieDiagram-ENE6RG2P-FzmIWC1K.js → pieDiagram-ENE6RG2P-B2przwvg.js} +1 -1
  59. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-N6zuxKqV.js → quadrantDiagram-ABIIQ3AL-B5xLDvCk.js} +1 -1
  60. package/web-dist/assets/{radar-I7S5WNFK-CB3WQRMp.js → radar-I7S5WNFK-DItULivd.js} +1 -1
  61. package/web-dist/assets/{railroad-3IZDKUUU-Ck8x0o2o.js → railroad-3IZDKUUU-BVSgw6fD.js} +1 -1
  62. package/web-dist/assets/railroad-abnf-AHOZXSZD-Du1T7UC_.js +1 -0
  63. package/web-dist/assets/railroad-ebnf-EBAXGLYW-Ck5QAc8a.js +1 -0
  64. package/web-dist/assets/railroad-peg-LSFZ7HO6-CA1OLi4_.js +1 -0
  65. package/web-dist/assets/{railroadDiagram-RFXS5EU6-6TyF6de4.js → railroadDiagram-RFXS5EU6-DQh-iy6H.js} +1 -1
  66. package/web-dist/assets/{requirementDiagram-TGXJPOKE-D6n7xpc4.js → requirementDiagram-TGXJPOKE-B2bjpE-s.js} +1 -1
  67. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-B7R6Natf.js → sankeyDiagram-HTMAVEWB-C-vr6CRd.js} +1 -1
  68. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CFbbuWF6.js → sequenceDiagram-DBY2YBRQ-C48rDu8k.js} +1 -1
  69. package/web-dist/assets/{stateDiagram-2N3HPSRC-DwXu0kos.js → stateDiagram-2N3HPSRC-BEHBNPlt.js} +1 -1
  70. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BiZG09Uh.js +1 -0
  71. package/web-dist/assets/{swimlanes-5IMT3BWC-Clie2Qk3.js → swimlanes-5IMT3BWC-BK0pUXUO.js} +1 -1
  72. package/web-dist/assets/swimlanesDiagram-G3AALYLV-CenRab4t.js +8 -0
  73. package/web-dist/assets/{timeline-definition-FHXFAJF6-DWDsPzuQ.js → timeline-definition-FHXFAJF6-CQ3rH4CA.js} +1 -1
  74. package/web-dist/assets/{treeView-QDETBFTQ-Ddd-Wxpb.js → treeView-QDETBFTQ-DrgwDaib.js} +1 -1
  75. package/web-dist/assets/{treemap-6X3UGDF4-CuZ3jD4K.js → treemap-6X3UGDF4-CGlOb_mN.js} +1 -1
  76. package/web-dist/assets/{vennDiagram-L72KCM5P-BoE4aTMb.js → vennDiagram-L72KCM5P-BB9TjfQX.js} +1 -1
  77. package/web-dist/assets/{wardley-OPB4EBWU-CfaztQL0.js → wardley-OPB4EBWU-C8EK8ow0.js} +1 -1
  78. package/web-dist/assets/{wardleyDiagram-EHGQE667-pxcV16qp.js → wardleyDiagram-EHGQE667-CUeOWZBB.js} +1 -1
  79. package/web-dist/assets/{xychartDiagram-FW5EYKEG--d5N7oDL.js → xychartDiagram-FW5EYKEG-8ZymnyMt.js} +1 -1
  80. package/web-dist/index.html +2 -2
  81. package/web-dist/assets/architecture-TIHT7OUA-ZNmKXuTL.js +0 -1
  82. package/web-dist/assets/channel-CTEEc7oT.js +0 -1
  83. package/web-dist/assets/classDiagram-OUVF2IWQ-D5na7mob.js +0 -1
  84. package/web-dist/assets/classDiagram-v2-EOCWNBFH-D5na7mob.js +0 -1
  85. package/web-dist/assets/eventmodeling-45OFAUF4-B8cYCwrU.js +0 -1
  86. package/web-dist/assets/flowDiagram-23GEKE2U-C-EWCSnf.js +0 -1
  87. package/web-dist/assets/index-CGrszN1x.js +0 -319
  88. package/web-dist/assets/index-d_g5kIKJ.css +0 -1
  89. package/web-dist/assets/railroad-abnf-AHOZXSZD-QzrMOCpt.js +0 -1
  90. package/web-dist/assets/railroad-ebnf-EBAXGLYW-CwEnUu4A.js +0 -1
  91. package/web-dist/assets/railroad-peg-LSFZ7HO6-BoDEbNAA.js +0 -1
  92. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-CQctakxF.js +0 -1
  93. package/web-dist/assets/swimlanesDiagram-G3AALYLV-CGOfs7yz.js +0 -8
package/dist/dev-child.js CHANGED
@@ -12362,7 +12362,7 @@ var init_interactions = __esm({
12362
12362
  InteractionFieldId = external_exports.string().min(1).max(128).regex(/^[A-Za-z_][A-Za-z0-9_.:-]*$/).refine((value) => !["__proto__", "constructor", "prototype"].includes(value), "reserved field id");
12363
12363
  InteractionDecisionId = external_exports.string().min(1).max(80).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
12364
12364
  InteractionProvider = external_exports.object({
12365
- kind: external_exports.enum(["claude", "codex", "frizz"]),
12365
+ kind: external_exports.enum(["claude", "codex", "acp", "frizz"]),
12366
12366
  name: InteractionLabel.optional(),
12367
12367
  version: InteractionOpaqueId.optional()
12368
12368
  }).strict();
@@ -13200,6 +13200,20 @@ function attachmentExtension(name) {
13200
13200
  function isAllowedAttachmentName(name) {
13201
13201
  return ATTACHMENT_EXT_SET.has(attachmentExtension(name));
13202
13202
  }
13203
+ function acpSlugParts(model) {
13204
+ if (typeof model !== "string" || !model.startsWith(ACP_MODEL_PREFIX)) return void 0;
13205
+ const rest = model.slice(ACP_MODEL_PREFIX.length);
13206
+ const at = rest.indexOf("@");
13207
+ const agent = at === -1 ? rest : rest.slice(0, at);
13208
+ const id = at === -1 ? "" : rest.slice(at + 1);
13209
+ return agent ? { agent, ...id ? { model: id } : {} } : void 0;
13210
+ }
13211
+ function acpAgentIdFromModel(model) {
13212
+ return acpSlugParts(model)?.agent;
13213
+ }
13214
+ function acpModelIdFromModel(model) {
13215
+ return acpSlugParts(model)?.model;
13216
+ }
13203
13217
  function isDirectSubAgent(agent) {
13204
13218
  return (agent.depth ?? 1) === 1;
13205
13219
  }
@@ -13268,9 +13282,9 @@ function trimAwaitingTitle(raw2) {
13268
13282
  }
13269
13283
  function splitAwaitingFrontmatter(raw2) {
13270
13284
  const lines = raw2.split("\n").map((l) => l.replace(/\r$/, ""));
13271
- const delimiter2 = lines.findIndex((l) => /^\s*---+\s*$/.test(l));
13272
- const frontmatter = delimiter2 === -1 ? lines : lines.slice(0, delimiter2);
13273
- const after = delimiter2 === -1 ? [] : lines.slice(delimiter2 + 1);
13285
+ const delimiter3 = lines.findIndex((l) => /^\s*---+\s*$/.test(l));
13286
+ const frontmatter = delimiter3 === -1 ? lines : lines.slice(0, delimiter3);
13287
+ const after = delimiter3 === -1 ? [] : lines.slice(delimiter3 + 1);
13274
13288
  const rest = [];
13275
13289
  const yamlLines = [];
13276
13290
  const titleLines = [];
@@ -13554,6 +13568,11 @@ function questionsCancelledWakeMessage(count) {
13554
13568
  function queuedThread(t) {
13555
13569
  return t.kind === "session" && t.foreign !== true && t.needsYou === true && t.state !== "archived";
13556
13570
  }
13571
+ function requireEffortOutsideAcp(profile, ctx) {
13572
+ if (profile.backend !== "acp" && profile.effort === void 0) {
13573
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["effort"], message: `effort is required for a ${profile.backend} profile` });
13574
+ }
13575
+ }
13557
13576
  function formatElapsed(ms) {
13558
13577
  if (!Number.isFinite(ms) || ms < 0) return "an unknown time";
13559
13578
  const s = Math.floor(ms / 1e3);
@@ -13704,7 +13723,7 @@ function parseGithubWakeSteer(text2) {
13704
13723
  function frizzRoute(path3) {
13705
13724
  return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
13706
13725
  }
13707
- var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, RETIRED_AWAITING_REPLACEMENT, AWAITING_KEY_RE, AWAITING_YAML_KEYS, AWAITING_SEQUENCE_KEYS, AWAITING_HINT_MAX, AWAITING_HINT_VALUE_MAX, AWAITING_TITLE_MAX, 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, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_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, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountLogoutInput, AccountLogoutResult, AccountLoginStartInput, AccountLoginStartResult, AccountLoginStatusInput, AccountLoginStatusResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, 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, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13726
+ var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, AcpAgent, 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_TITLE_MAX, 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, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_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, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, AccountLoginStartInput, AccountLoginStartResult, AccountLoginStatusInput, AccountLoginStatusResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, 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, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13708
13727
  var init_src = __esm({
13709
13728
  "packages/shared/src/index.ts"() {
13710
13729
  "use strict";
@@ -13823,7 +13842,7 @@ var init_src = __esm({
13823
13842
  "exited"
13824
13843
  // the worker process that owned this session is gone, or is no longer driving its turn
13825
13844
  ]);
13826
- Backend = external_exports.enum(["claude", "codex"]);
13845
+ Backend = external_exports.enum(["claude", "codex", "acp"]);
13827
13846
  CodexModel = external_exports.object({
13828
13847
  slug: external_exports.string(),
13829
13848
  displayName: external_exports.string(),
@@ -13837,6 +13856,24 @@ var init_src = __esm({
13837
13856
  contextWindow: external_exports.number().int().positive().optional(),
13838
13857
  maxContextWindow: external_exports.number().int().positive().optional()
13839
13858
  });
13859
+ AcpAgent = external_exports.object({
13860
+ id: external_exports.string(),
13861
+ label: external_exports.string(),
13862
+ command: external_exports.string(),
13863
+ available: external_exports.boolean()
13864
+ });
13865
+ ACP_MODEL_PREFIX = "acp:";
13866
+ AcpAgentModel = external_exports.object({ id: external_exports.string(), name: external_exports.string() });
13867
+ AcpAgentModels = external_exports.object({
13868
+ agentId: external_exports.string(),
13869
+ models: external_exports.array(AcpAgentModel),
13870
+ /** The model the agent opens a session on when Frizz asks for none. */
13871
+ current: external_exports.string().optional(),
13872
+ /** Why the list is empty, when the probe could not open a session (not installed, not logged in…). */
13873
+ error: external_exports.string().optional(),
13874
+ probedAt: external_exports.string()
13875
+ });
13876
+ AcpAgentModelsInput = external_exports.object({ agentId: external_exports.string().min(1).max(100), refresh: external_exports.boolean().optional() }).strict();
13840
13877
  ThreadProfileOption = external_exports.object({
13841
13878
  model: external_exports.string().min(1),
13842
13879
  label: external_exports.string().min(1),
@@ -14848,14 +14885,15 @@ var init_src = __esm({
14848
14885
  codex: ProviderAuth,
14849
14886
  emails: AccountEmails
14850
14887
  });
14851
- AccountLogoutInput = external_exports.object({ backend: external_exports.enum(["claude", "codex"]) }).strict();
14888
+ AccountBackend = external_exports.enum(["claude", "codex"]);
14889
+ AccountLogoutInput = external_exports.object({ backend: AccountBackend }).strict();
14852
14890
  AccountLogoutResult = external_exports.object({
14853
14891
  status: external_exports.enum(["done", "blocked", "failed"]),
14854
14892
  auth: ProviderAuth,
14855
14893
  activeThreads: external_exports.number().int().positive().optional(),
14856
14894
  detail: external_exports.string().max(200).optional()
14857
14895
  });
14858
- AccountLoginStartInput = external_exports.object({ backend: external_exports.enum(["claude", "codex"]) }).strict();
14896
+ AccountLoginStartInput = external_exports.object({ backend: AccountBackend }).strict();
14859
14897
  AccountLoginStartResult = external_exports.object({ attemptId: ThreadSlug });
14860
14898
  AccountLoginStatusInput = external_exports.object({ attemptId: ThreadSlug }).strict();
14861
14899
  AccountLoginStatusResult = external_exports.object({
@@ -14930,6 +14968,15 @@ var init_src = __esm({
14930
14968
  // it (codex compacts at 90% of the resolved window). Unset ⇒ nothing is sent and the model's stock
14931
14969
  // window applies. Optional so an old blob parses; defaultSettings leaves it unset.
14932
14970
  codexContextWindow: external_exports.number().int().positive().optional(),
14971
+ // The operator's OWN Agent Client Protocol agents, merged over the built-in catalogue by id
14972
+ // (server/backend/acp-agents.ts). `command` is an executable name on PATH or an absolute path;
14973
+ // `args` is what puts it into ACP mode (`["acp"]`, `["--acp"]`). Optional so an old blob parses.
14974
+ acpAgents: external_exports.array(external_exports.object({
14975
+ id: external_exports.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9._-]*$/i),
14976
+ label: external_exports.string().trim().min(1).max(80),
14977
+ command: external_exports.string().trim().min(1).max(1024),
14978
+ args: external_exports.array(external_exports.string().max(1024)).max(32).optional()
14979
+ })).max(32).optional(),
14933
14980
  // The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
14934
14981
  // unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
14935
14982
  // the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
@@ -14956,28 +15003,38 @@ var init_src = __esm({
14956
15003
  DispatchPreferences = external_exports.object({
14957
15004
  backend: Backend,
14958
15005
  claude: DispatchProviderPreferences,
14959
- codex: DispatchProviderPreferences
15006
+ codex: DispatchProviderPreferences,
15007
+ // Optional because every record written before the ACP backend existed lacks it, and a required
15008
+ // key would fail those records' parse and silently reset the operator's saved profile.
15009
+ acp: DispatchProviderPreferences.optional()
14960
15010
  });
14961
- DispatchProfileSnapshot = external_exports.object({
15011
+ DispatchProfileSnapshotShape = external_exports.object({
14962
15012
  backend: Backend,
14963
15013
  model: external_exports.string().trim().min(1).max(200),
14964
- effort: Settings.shape.effort.unwrap(),
15014
+ // Required for Claude and Codex (the refinement below); absent for an ACP agent, which runs with
15015
+ // whatever model and effort its own CLI is configured for — Frizz has no effort axis to offer there.
15016
+ effort: Settings.shape.effort,
14965
15017
  // IGNORED: dispatch permission is decided server-side (workerDispatchPermission) from the
14966
15018
  // non-interactive floor plus the operator's Settings choice, never per dispatch. Optional so old
14967
15019
  // clients that still send it parse.
14968
15020
  permissionMode: PermissionMode.optional()
14969
15021
  }).strict();
15022
+ DispatchProfileSnapshot = DispatchProfileSnapshotShape.superRefine(requireEffortOutsideAcp);
14970
15023
  SetDispatchPreferenceInput = external_exports.discriminatedUnion("field", [
14971
15024
  external_exports.object({ field: external_exports.literal("backend"), value: Backend }),
14972
15025
  external_exports.object({
14973
15026
  field: external_exports.literal("profile"),
14974
15027
  backend: Backend,
14975
15028
  model: external_exports.string().trim().min(1).max(200),
14976
- effort: Settings.shape.effort.unwrap()
15029
+ // Optional ONLY for an ACP profile (see DispatchProfileSnapshot); a Claude/Codex profile without
15030
+ // an effort is refused below rather than silently stored as "default".
15031
+ effort: Settings.shape.effort
14977
15032
  }),
14978
15033
  external_exports.object({ field: external_exports.literal("model"), backend: Backend, value: external_exports.string().trim().min(1).max(200) }),
14979
15034
  external_exports.object({ field: external_exports.literal("effort"), backend: Backend, value: Settings.shape.effort.unwrap() })
14980
- ]);
15035
+ ]).superRefine((update, ctx) => {
15036
+ if (update.field === "profile") requireEffortOutsideAcp(update, ctx);
15037
+ });
14981
15038
  DispatchInput = external_exports.object({
14982
15039
  // Optional: when omitted, dispatch derives a fallback title from the prompt (Claude later renames
14983
15040
  // the session via ai-title, which the UI prefers for display). The thread FILE always gets a
@@ -15379,9 +15436,9 @@ ${DISPATCH_TASK_BANNER}
15379
15436
  page: external_exports.number().int().positive(),
15380
15437
  pageCount: external_exports.number().int().positive()
15381
15438
  });
15382
- GithubBatchInput = DispatchProfileSnapshot.extend({
15439
+ GithubBatchInput = DispatchProfileSnapshotShape.extend({
15383
15440
  items: external_exports.array(external_exports.object({ kind: external_exports.enum(["issue", "pr"]), number: external_exports.number().int().positive() })).min(1)
15384
- }).strict();
15441
+ }).strict().superRefine(requireEffortOutsideAcp);
15385
15442
  GithubBatchResult = external_exports.object({
15386
15443
  dispatched: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), slug: ThreadSlug })),
15387
15444
  failed: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), error: external_exports.string() }))
@@ -19055,7 +19112,7 @@ var init_project_scope = __esm({
19055
19112
 
19056
19113
  // packages/server/src/storage.ts
19057
19114
  function isHeadlessRow(row) {
19058
- return row.backend === "codex" && row.codex_runtime === "app-server" || row.backend === "claude" && row.claude_runtime === "broker";
19115
+ return row.backend === "codex" && row.codex_runtime === "app-server" || row.backend === "claude" && row.claude_runtime === "broker" || row.backend === "acp";
19059
19116
  }
19060
19117
  function isBrokerClaudeRow(row) {
19061
19118
  return row.backend === "claude" && row.claude_runtime === "broker";
@@ -19078,7 +19135,7 @@ function groupBySlug(rows) {
19078
19135
  }
19079
19136
  function ensureStorageSchema(db) {
19080
19137
  db.exec(STORAGE_SCHEMA);
19081
- for (const column of ["pinned_at TEXT"]) {
19138
+ for (const column of ["pinned_at TEXT", "acp_agent TEXT"]) {
19082
19139
  try {
19083
19140
  db.exec(`ALTER TABLE session ADD COLUMN ${column}`);
19084
19141
  } catch {
@@ -19673,6 +19730,7 @@ function createStorage(source, projectId) {
19673
19730
  const agentSessionStmt = scope.prepare("UPDATE session SET agent_session_id = ? WHERE project_id = @project_id AND slug = ?");
19674
19731
  const codexRuntimeStmt = scope.prepare("UPDATE session SET codex_runtime = ? WHERE project_id = @project_id AND slug = ?");
19675
19732
  const claudeRuntimeStmt = scope.prepare("UPDATE session SET claude_runtime = ? WHERE project_id = @project_id AND slug = ?");
19733
+ const acpAgentStmt = scope.prepare("UPDATE session SET acp_agent = ? WHERE project_id = @project_id AND slug = ?");
19676
19734
  const profileStmt = scope.prepare("UPDATE session SET model = ?, effort = ?, profile_set_at = ? WHERE project_id = @project_id AND slug = ?");
19677
19735
  const permissionModeStmt = scope.prepare("UPDATE session SET permission_mode = ?, permission_set_at = ? WHERE project_id = @project_id AND slug = ?");
19678
19736
  const permissionPendingStmt = scope.prepare("UPDATE session SET permission_pending = ? WHERE project_id = @project_id AND slug = ?");
@@ -19802,7 +19860,8 @@ function createStorage(source, projectId) {
19802
19860
  runtime_control: row.runtime_control ?? null,
19803
19861
  runtime_control_revision: row.runtime_control_revision ?? 0,
19804
19862
  codex_runtime: row.codex_runtime ?? null,
19805
- claude_runtime: row.claude_runtime ?? null
19863
+ claude_runtime: row.claude_runtime ?? null,
19864
+ acp_agent: row.acp_agent ?? null
19806
19865
  });
19807
19866
  const getAdoptionRuntimeSnapshot = db.transaction((slug) => ({
19808
19867
  // Claim first is intentional: a finalized claim disappearing before the current-row validation
@@ -19834,7 +19893,7 @@ function createStorage(source, projectId) {
19834
19893
  const retireAdoptionAttempt = (claim, retiredAtMs = Date.now()) => {
19835
19894
  putRetiredAdoptionAttempt.run(claim.attempt_token, claim.slug, claim.session_id, retiredAtMs);
19836
19895
  };
19837
- const withAdoptionSpawnFence = (slug, attemptToken, leaseExpiresAtMs, spawn7) => {
19896
+ const withAdoptionSpawnFence = (slug, attemptToken, leaseExpiresAtMs, spawn8) => {
19838
19897
  ThreadSlug.parse(slug);
19839
19898
  if (!Number.isSafeInteger(leaseExpiresAtMs)) return { acquired: false };
19840
19899
  db.exec("BEGIN IMMEDIATE");
@@ -19866,7 +19925,7 @@ function createStorage(source, projectId) {
19866
19925
  bound = true;
19867
19926
  return true;
19868
19927
  };
19869
- const value = spawn7(bindPane);
19928
+ const value = spawn8(bindPane);
19870
19929
  if (!bound) {
19871
19930
  if (db.inTransaction) db.exec("ROLLBACK");
19872
19931
  throw new Error("adoption spawn returned without binding its exact pane");
@@ -20185,6 +20244,7 @@ function createStorage(source, projectId) {
20185
20244
  setAgentSession: (slug, agentSessionId) => void agentSessionStmt.run(agentSessionId, slug),
20186
20245
  setCodexRuntime: (slug, runtime) => void codexRuntimeStmt.run(runtime, slug),
20187
20246
  setClaudeRuntime: (slug, runtime) => void claudeRuntimeStmt.run(runtime, slug),
20247
+ setAcpAgent: (slug, agentId) => void acpAgentStmt.run(agentId, slug),
20188
20248
  setProfile: (slug, model, effort) => void profileStmt.run(model, effort, (/* @__PURE__ */ new Date()).toISOString(), slug),
20189
20249
  setPermissionMode: (slug, permissionMode) => void permissionModeStmt.run(permissionMode, (/* @__PURE__ */ new Date()).toISOString(), slug),
20190
20250
  setPermissionPending: (slug, permissionMode) => void permissionPendingStmt.run(permissionMode, slug),
@@ -20413,6 +20473,8 @@ var init_storage = __esm({
20413
20473
  -- Claude transport discriminator: 'broker' = a session-broker-owned Agent SDK session; NULL/'tmux'
20414
20474
  -- is the pre-broker legacy value, same story.
20415
20475
  claude_runtime TEXT,
20476
+ -- ACP agent id (backend/acp-agents.ts) for backend='acp' rows; also in the ALTER list below.
20477
+ acp_agent TEXT,
20416
20478
  -- THE RECURRING PROMPT (scheduler.ts SOURCES 4, 5 and 7): one text, three independent triggers \u2014
20417
20479
  -- every time the thread rests, every N ms on a clock, and/or every time its context is compacted.
20418
20480
  -- All flags 0 = off; there is no separate enable column, because another flag could only ever
@@ -20865,9 +20927,16 @@ function defaultDispatchPreferences(settings, codexModels = []) {
20865
20927
  return {
20866
20928
  backend,
20867
20929
  claude: backend === "claude" ? selected : { permissionMode: "auto" },
20868
- codex: backend === "codex" ? selected : { permissionMode: "default" }
20930
+ codex: backend === "codex" ? selected : { permissionMode: "default" },
20931
+ // The ACP slot exists only once the operator has chosen an agent: it has no permission axis (the
20932
+ // agent's own CLI decides), so there is no default worth writing, and every record written before
20933
+ // the backend existed lacks the key.
20934
+ ...backend === "acp" ? { acp: selected } : {}
20869
20935
  };
20870
20936
  }
20937
+ function profileOf(prefs, backend) {
20938
+ return prefs[backend] ?? {};
20939
+ }
20871
20940
  function getDispatchPreferences(storage, settings, home, codexModels = []) {
20872
20941
  const machine = readMachineDispatchPreferences(home);
20873
20942
  if (machine) return machine;
@@ -20884,13 +20953,13 @@ function setDispatchPreference(storage, settings, home, update, codexModels = []
20884
20953
  ...current,
20885
20954
  backend: update.backend,
20886
20955
  [update.backend]: {
20887
- ...current[update.backend],
20956
+ ...profileOf(current, update.backend),
20888
20957
  model: update.model,
20889
20958
  effort: update.effort
20890
20959
  }
20891
20960
  };
20892
20961
  } else {
20893
- const profile = current[update.backend];
20962
+ const profile = profileOf(current, update.backend);
20894
20963
  next = {
20895
20964
  ...current,
20896
20965
  ...update.field === "model" ? { backend: update.backend } : {},
@@ -20928,8 +20997,8 @@ function userInfoParts(raw2) {
20928
20997
  const encoded = encodedMatch?.index ?? -1;
20929
20998
  const index = literal === -1 ? encoded : encoded === -1 ? literal : Math.min(literal, encoded);
20930
20999
  if (index <= 0) return null;
20931
- const delimiter2 = value.slice(index, index + (index === encoded ? 3 : 1));
20932
- return { username: value.slice(0, index), delimiter: delimiter2, password: value.slice(index + delimiter2.length) };
21000
+ const delimiter3 = value.slice(index, index + (index === encoded ? 3 : 1));
21001
+ return { username: value.slice(0, index), delimiter: delimiter3, password: value.slice(index + delimiter3.length) };
20933
21002
  }
20934
21003
  function redactUserInfoToken(raw2, options) {
20935
21004
  const parts = userInfoParts(raw2);
@@ -20952,11 +21021,11 @@ function redactCredentialSyntax(raw2, options = {}) {
20952
21021
  let value = raw2;
20953
21022
  value = value.replace(
20954
21023
  URL_USERINFO_RE,
20955
- (_whole, scheme, username, delimiter2, password) => `${scheme}${username}${delimiter2}${replacement(options, password)}@`
21024
+ (_whole, scheme, username, delimiter3, password) => `${scheme}${username}${delimiter3}${replacement(options, password)}@`
20956
21025
  );
20957
21026
  value = value.replace(
20958
21027
  URL_ENCODED_USERINFO_RE,
20959
- (_whole, scheme, username, delimiter2, password) => `${scheme}${username}${delimiter2}${replacement(options, password)}@`
21028
+ (_whole, scheme, username, delimiter3, password) => `${scheme}${username}${delimiter3}${replacement(options, password)}@`
20960
21029
  );
20961
21030
  value = value.replace(
20962
21031
  USER_FLAG_RE,
@@ -21605,6 +21674,7 @@ function threadProfileOptions(backend) {
21605
21674
  }))
21606
21675
  };
21607
21676
  }
21677
+ if (backend === "acp") return { backend, options: [] };
21608
21678
  throw new Error("This thread has an unknown backend; its runtime profile cannot be changed");
21609
21679
  }
21610
21680
  function validateThreadProfile(backend, model, effort) {
@@ -24897,7 +24967,7 @@ ${ask}`;
24897
24967
  void nowMs;
24898
24968
  void backend;
24899
24969
  if (state.foreign) return { permPrompt: false };
24900
- if (turn === "in-flight" && row.backend !== "codex" && permMarkerBlocks(state, row)) {
24970
+ if (turn === "in-flight" && row.backend !== "codex" && row.backend !== "acp" && permMarkerBlocks(state, row)) {
24901
24971
  return { permPrompt: true };
24902
24972
  }
24903
24973
  return { permPrompt: false };
@@ -25221,7 +25291,7 @@ ${ask}`;
25221
25291
  if (state.stallLogged) return;
25222
25292
  if (row.exited && rowIsArchived(row)) return;
25223
25293
  state.stallLogged = true;
25224
- const detail2 = isBrokerClaudeRow(row) && deps.project.stateDir ? `no worker terminal to capture (broker runtime). Daemon diagnostics: ${claudeBrokerDiagnosticLogPath(deps.project.stateDir, row.session_id)}` : isHeadlessRow(row) ? `no worker terminal to capture (headless ${row.backend === "codex" ? "codex app-server" : "claude broker"} runtime)` : "no worker output captured (this build captures no terminal)";
25294
+ const detail2 = isBrokerClaudeRow(row) && deps.project.stateDir ? `no worker terminal to capture (broker runtime). Daemon diagnostics: ${claudeBrokerDiagnosticLogPath(deps.project.stateDir, row.session_id)}` : isHeadlessRow(row) ? `no worker terminal to capture (headless ${row.backend === "codex" ? "codex app-server" : row.backend === "acp" ? "ACP agent" : "claude broker"} runtime)` : "no worker output captured (this build captures no terminal)";
25225
25295
  log.error(
25226
25296
  "tailer",
25227
25297
  `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)}`
@@ -25367,7 +25437,7 @@ ${detail2}
25367
25437
  if (primeProgress && primed % PRIME_PROGRESS_EVERY === 0) primeProgress(primed, rows.length);
25368
25438
  primed++;
25369
25439
  const backend = resolveBackend(row.backend);
25370
- const nativeId = row.agent_session_id ?? row.transcript_id ?? row.session_id;
25440
+ const nativeId = row.backend === "acp" ? row.session_id : row.agent_session_id ?? row.transcript_id ?? row.session_id;
25371
25441
  const known = states.get(row.slug);
25372
25442
  const runtimeGeneration = row.runtime_generation ?? 0;
25373
25443
  const keepable = known !== void 0 && known.sessionId === row.session_id && known.nativeSessionId === nativeId && known.runtimeGeneration === runtimeGeneration;
@@ -25388,7 +25458,7 @@ ${detail2}
25388
25458
  states.set(row.slug, state);
25389
25459
  }
25390
25460
  const prevNoTranscript = state.noTranscript;
25391
- if (row.backend !== "codex" && !resolveTranscript(state, row, nowMs)) continue;
25461
+ if (row.backend !== "codex" && row.backend !== "acp" && !resolveTranscript(state, row, nowMs)) continue;
25392
25462
  if (!state.primed) {
25393
25463
  if (primeDeferred(row)) {
25394
25464
  primeIncomplete = true;
@@ -26546,19 +26616,19 @@ function searchPath2(env) {
26546
26616
  function vendoredNativeBeside(launcherJs, arch, access) {
26547
26617
  const triple = WINDOWS_TRIPLE[arch];
26548
26618
  if (!triple) return void 0;
26549
- const { dirname: dirname25, join: join47 } = win322;
26550
- const pkgRoot = dirname25(dirname25(launcherJs));
26619
+ const { dirname: dirname26, join: join49 } = win322;
26620
+ const pkgRoot = dirname26(dirname26(launcherJs));
26551
26621
  const platformPkg = `codex-win32-${arch}`;
26552
26622
  const vendorRoots = [
26553
- join47(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
26623
+ join49(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
26554
26624
  // nested under the package
26555
- join47(dirname25(pkgRoot), platformPkg, "vendor"),
26625
+ join49(dirname26(pkgRoot), platformPkg, "vendor"),
26556
26626
  // hoisted beside it: <prefix>\node_modules\@openai\<pkg>
26557
- join47(pkgRoot, "vendor")
26627
+ join49(pkgRoot, "vendor")
26558
26628
  // the pre-platform-package layout, binaries inside @openai/codex itself
26559
26629
  ];
26560
26630
  for (const root of vendorRoots) {
26561
- const exe = join47(root, triple, "bin", "codex.exe");
26631
+ const exe = join49(root, triple, "bin", "codex.exe");
26562
26632
  if (access(exe, "exists")) return exe;
26563
26633
  }
26564
26634
  return void 0;
@@ -31659,6 +31729,62 @@ var init_auth_status = __esm({
31659
31729
  }
31660
31730
  });
31661
31731
 
31732
+ // packages/server/src/backend/codex-execution-health.ts
31733
+ import { execFile as execFile5 } from "node:child_process";
31734
+ function probeHostMacOSServices() {
31735
+ return new Promise((resolve12) => {
31736
+ execFile5(MACOS_SERVICE_PROBE[0], MACOS_SERVICE_PROBE.slice(1), {
31737
+ timeout: MACOS_SERVICE_PROBE_TIMEOUT_MS,
31738
+ killSignal: "SIGKILL",
31739
+ maxBuffer: 64 * 1024
31740
+ }, (error) => resolve12(!error));
31741
+ });
31742
+ }
31743
+ var MACOS_SERVICE_PROBE, MACOS_SERVICE_PROBE_TIMEOUT_MS, CodexExecutionHealth;
31744
+ var init_codex_execution_health = __esm({
31745
+ "packages/server/src/backend/codex-execution-health.ts"() {
31746
+ "use strict";
31747
+ MACOS_SERVICE_PROBE = ["/bin/launchctl", "print", "system/com.apple.configd"];
31748
+ MACOS_SERVICE_PROBE_TIMEOUT_MS = 3e3;
31749
+ CodexExecutionHealth = class {
31750
+ constructor(platform3 = process.platform, hostProbe = probeHostMacOSServices) {
31751
+ this.platform = platform3;
31752
+ this.hostProbe = hostProbe;
31753
+ }
31754
+ checking = /* @__PURE__ */ new WeakMap();
31755
+ check(connection, scope) {
31756
+ if (this.platform !== "darwin") return Promise.resolve();
31757
+ const pending = this.checking.get(connection);
31758
+ if (pending) return pending;
31759
+ const check = this.probe(connection, scope);
31760
+ this.checking.set(connection, check);
31761
+ void check.finally(() => this.checking.delete(connection)).catch(() => void 0);
31762
+ return check;
31763
+ }
31764
+ async probe(connection, scope) {
31765
+ let detail2;
31766
+ try {
31767
+ const raw2 = await connection.request("command/exec", {
31768
+ command: MACOS_SERVICE_PROBE,
31769
+ cwd: "/",
31770
+ timeoutMs: MACOS_SERVICE_PROBE_TIMEOUT_MS,
31771
+ sandboxPolicy: { type: "dangerFullAccess" }
31772
+ }, MACOS_SERVICE_PROBE_TIMEOUT_MS + 1e3);
31773
+ const result = raw2;
31774
+ if (result?.exitCode === 0 && typeof result.stdout === "string" && result.stdout.includes("system/com.apple.configd = {")) return;
31775
+ detail2 = typeof result?.exitCode === "number" ? `launchctl exited ${result.exitCode}` : "invalid probe response";
31776
+ } catch {
31777
+ detail2 = "the local service probe failed or timed out";
31778
+ }
31779
+ const hostHealthy = await this.hostProbe().catch(() => false);
31780
+ throw new Error(
31781
+ `Codex cannot access macOS system services (${detail2}; ${scope}). New conversations and turns are blocked; existing work and conversation history were not changed. ` + (hostHealthy ? "Frizz's host context is healthy, but this backend is not. Stop this project's Codex turns before replacing its shared backend; restarting Frizz alone can reuse it." : "Frizz's host context also failed the check. Relaunch Frizz from a fresh terminal after stopping this project's Codex turns; its shared backend must also be replaced.")
31782
+ );
31783
+ }
31784
+ };
31785
+ }
31786
+ });
31787
+
31662
31788
  // packages/server/src/backend/codex-app-server.ts
31663
31789
  import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
31664
31790
  import { StringDecoder as StringDecoder3 } from "node:string_decoder";
@@ -32320,6 +32446,7 @@ var init_codex_app_server = __esm({
32320
32446
  init_auth_status();
32321
32447
  init_codex_mcp();
32322
32448
  init_codex_error();
32449
+ init_codex_execution_health();
32323
32450
  init_types2();
32324
32451
  init_logging();
32325
32452
  CODEX_APP_SERVER_PROVIDER = "codex-app-server";
@@ -32603,7 +32730,7 @@ var init_codex_app_server = __esm({
32603
32730
  child.on("exit", () => this.fail("exit", new Error("Codex app-server exited")));
32604
32731
  child.on("error", () => this.fail("error", new Error("Codex app-server process failed")));
32605
32732
  }
32606
- async request(method, params) {
32733
+ async request(method, params, timeoutMs = this.timeoutMs) {
32607
32734
  if (this.closed) throw new Error("Codex app-server connection is closed");
32608
32735
  if (this.pending.size >= MAX_OUTBOUND_REQUESTS) throw new Error("Codex app-server outbound request queue is full");
32609
32736
  if (!Number.isSafeInteger(this.nextId)) throw new Error("Codex app-server request id space is exhausted");
@@ -32612,7 +32739,7 @@ var init_codex_app_server = __esm({
32612
32739
  const timer = setTimeout(() => {
32613
32740
  this.pending.delete(id);
32614
32741
  reject(new Error(`Codex app-server request timed out: ${method}`));
32615
- }, this.timeoutMs);
32742
+ }, timeoutMs);
32616
32743
  this.pending.set(id, { resolve: resolve12, reject, timer });
32617
32744
  });
32618
32745
  try {
@@ -32887,6 +33014,8 @@ var init_codex_app_server = __esm({
32887
33014
  host;
32888
33015
  codexBin;
32889
33016
  timeoutMs;
33017
+ executionHealth;
33018
+ executionScope = "";
32890
33019
  /** Identity of the app-server PROCESS behind the current connection (see CodexAppServerAttachment). */
32891
33020
  daemonGeneration = "";
32892
33021
  /** ChatGPT account the current app-server cached when its process started. */
@@ -32936,6 +33065,7 @@ var init_codex_app_server = __esm({
32936
33065
  operationWaiters = /* @__PURE__ */ new Set();
32937
33066
  shutdownPromise = null;
32938
33067
  constructor(options) {
33068
+ this.executionHealth = options.executionHealth ?? new CodexExecutionHealth();
32939
33069
  this.options = options;
32940
33070
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
32941
33071
  this.makeId = options.id ?? randomUUID11;
@@ -33172,6 +33302,7 @@ var init_codex_app_server = __esm({
33172
33302
  const releaseOperation = this.beginOperation();
33173
33303
  try {
33174
33304
  const connection = await this.ensureConnected();
33305
+ await this.checkExecutionHealth(connection);
33175
33306
  const binding = this.bindingForScope(input.threadSlug, input.sessionId);
33176
33307
  if (!binding) throw new Error("Codex app-server steer requires a bridge-owned session");
33177
33308
  if (binding.connection_epoch !== this.connectionEpoch || binding.state !== "active") {
@@ -33365,7 +33496,8 @@ var init_codex_app_server = __esm({
33365
33496
  if (bound === 0) return;
33366
33497
  const releaseOperation = this.beginOperation();
33367
33498
  try {
33368
- await this.ensureConnected();
33499
+ const connection = await this.ensureConnected();
33500
+ await this.checkExecutionHealth(connection);
33369
33501
  await this.autoResumeInterruptedTurns();
33370
33502
  } catch {
33371
33503
  } finally {
@@ -33609,6 +33741,7 @@ var init_codex_app_server = __esm({
33609
33741
  for (let attempt = 0; ; attempt++) {
33610
33742
  const desiredAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
33611
33743
  const connection = await this.ensureConnected();
33744
+ await this.checkExecutionHealth(connection);
33612
33745
  if (!desiredAccountId || this.processAuthAccountId === desiredAccountId) return connection;
33613
33746
  if (attempt >= MAX_AUTH_REPLACEMENT_ATTEMPTS) return connection;
33614
33747
  if (!this.authReplacement) {
@@ -33621,6 +33754,17 @@ var init_codex_app_server = __esm({
33621
33754
  if (!await this.awaitAuthReplacement(this.authReplacement)) return connection;
33622
33755
  }
33623
33756
  }
33757
+ async checkExecutionHealth(connection) {
33758
+ try {
33759
+ await this.executionHealth.check(connection, this.executionScope);
33760
+ } catch (error) {
33761
+ const detail2 = error.message;
33762
+ this.options.diagnostic?.({ event: "execution-health-failed", detail: detail2 });
33763
+ log.error("codex", detail2);
33764
+ throw error;
33765
+ }
33766
+ if (this.connection !== connection) throw new Error("Codex app-server changed during its execution health check");
33767
+ }
33624
33768
  /** Await an armed replacement for at most AUTH_REPLACEMENT_WAIT_MS. `false` = it is still waiting for
33625
33769
  * the project to go idle, so the caller should proceed rather than hang; a rejection propagates. */
33626
33770
  async awaitAuthReplacement(replacement2) {
@@ -33695,6 +33839,7 @@ var init_codex_app_server = __esm({
33695
33839
  authAccountId
33696
33840
  });
33697
33841
  const child = attachment.process;
33842
+ this.executionScope = `project ${this.options.projectDir}, backend PID ${attachment.daemonPid}, generation ${attachment.generation}`;
33698
33843
  let connection;
33699
33844
  connection = new JsonlRpcConnection(
33700
33845
  child,
@@ -35219,7 +35364,7 @@ var init_thread_links = __esm({
35219
35364
  });
35220
35365
 
35221
35366
  // packages/server/src/frizz.ts
35222
- import { execFile as execFile5 } from "node:child_process";
35367
+ import { execFile as execFile6 } from "node:child_process";
35223
35368
  import { lstatSync as lstatSync2, realpathSync as realpathSync7 } from "node:fs";
35224
35369
  import { basename as basename5, dirname as dirname10, join as join27, resolve as resolve6 } from "node:path";
35225
35370
  import { promisify as promisify5 } from "node:util";
@@ -35279,7 +35424,7 @@ var init_frizz = __esm({
35279
35424
  "packages/server/src/frizz.ts"() {
35280
35425
  "use strict";
35281
35426
  init_src();
35282
- execFileP = promisify5(execFile5);
35427
+ execFileP = promisify5(execFile6);
35283
35428
  }
35284
35429
  });
35285
35430
 
@@ -35571,6 +35716,12 @@ var init_workerPrompt = __esm({
35571
35716
  "codex": {
35572
35717
  "SESSION_KIND": "codex",
35573
35718
  "RESUME_CMD": "codex resume"
35719
+ },
35720
+ // An ACP agent has no resume command Frizz can name: the session lives inside the agent and Frizz
35721
+ // re-opens it over the protocol. The token still has to resolve, so it names the mechanism.
35722
+ "acp": {
35723
+ "SESSION_KIND": "coding-agent",
35724
+ "RESUME_CMD": "(Frizz re-opens the session over ACP)"
35574
35725
  }
35575
35726
  };
35576
35727
  INTRO = `You are a dispatched worker agent \u2014 a top-level \`{{FRIZZ_SESSION_KIND}}\` session frizz spawned to drive ONE
@@ -36102,7 +36253,23 @@ emptied window. Whether to keep notes, and whether to arm anything, is yours to
36102
36253
  even with \`fork_turns: "none"\`, and one undifferentiated "keep the doc current" mandate is what once
36103
36254
  made a child replace a whole shared document with its task notes and then delete the replacement as a
36104
36255
  misguided rollback. One file per writer \u2014 \`<agent>-<topic>.md\` \u2014 removes that failure entirely: there
36105
- is nothing to merge, so there is nothing to clobber. Never edit or delete a file another agent wrote.`
36256
+ is nothing to merge, so there is nothing to clobber. Never edit or delete a file another agent wrote.`,
36257
+ acp: `## Your scratch directory
36258
+
36259
+ \`.frizz/threads/<session-id>/\` (exact path in your session-start context) \u2014 a folder that is YOURS, for
36260
+ as many files as you like, in whatever format you like. It starts EMPTY and nothing is expected in it.
36261
+ Frizz reads nothing here automatically.
36262
+
36263
+ **IT IS OPTIONAL, IT IS NOT A DELIVERABLE, AND WRITING NOTES IS NOT DOING THE WORK.** It exists in case
36264
+ you want it. A single direct task usually needs nothing here: just do the task. Never let a note stand
36265
+ in for an action \u2014 recording "next: X" when the human asked for X is not progress on X, and a turn that
36266
+ ends right after a scratch write is nearly always a turn that stopped for no reason. When something is
36267
+ worth writing, write it AS YOU GO, mid-work, and then keep working.
36268
+
36269
+ **Notes here can come back after a compaction, if you want that.** Nothing points you at this directory
36270
+ once your context is summarized \u2014 but the frizz \`goal\` tool can arm a goal, and one armed with
36271
+ \`post_compaction: true\` re-sends a prompt of your choosing (which can link a file here) into the
36272
+ emptied window. Whether to keep notes, and whether to arm anything, is yours to decide per effort.`
36106
36273
  };
36107
36274
  BACKEND = {
36108
36275
  claude: `## Sub-agents
@@ -36361,7 +36528,31 @@ The sandbox governs what you may touch, and a denial is the
36361
36528
  sandbox \u2014 not a bug: \`read-only\` (inspect, never write), \`workspace-write\` (edit inside the repo,
36362
36529
  denied outside), or \`danger-full-access\` (unrestricted). Approvals are off (\`approvalPolicy: never\`), so a
36363
36530
  sandbox-denied action fails straight back to you rather than prompting a human \u2014 adapt, or surface
36364
- the blocker in your final message.`
36531
+ the blocker in your final message.`,
36532
+ acp: `## Your harness, and the frizz tools inside it
36533
+
36534
+ You are running as a coding agent that Frizz drives over the Agent Client Protocol (ACP). Frizz did
36535
+ not build your harness: your file, shell, search and sub-agent tools are your own, your permission
36536
+ prompts are your harness's (Frizz shows each one to the human as a card and relays the answer), and
36537
+ Frizz's own tools reach you as an MCP server named \`frizz\`. Your harness spells its tools with its own
36538
+ prefix \u2014 \`frizz_ask\`, \`frizz_done\`, \`frizz_watch\` \u2026 on OpenCode; \`mcp__frizz__ask\` on others \u2014 so
36539
+ wherever this contract writes \`mcp__frizz__<verb>\`, call the \`<verb>\` tool of the \`frizz\` server the
36540
+ way your harness lists it. Every verb below exists there: \`ask\`, \`unask\`, \`done\`, \`watch\`,
36541
+ \`unwatch\`, \`watch_pr\`, \`timer\`, \`goal\`, \`title\`, \`link\`, \`unlink\`, \`activity\`, \`spawn_thread\`.
36542
+
36543
+ ## Sub-agents
36544
+
36545
+ Frizz does not dispatch sub-agents for you and cannot see the ones your harness runs, so a helper you
36546
+ spawn through your own tools is invisible on the dashboard until you report what it found. Prefer to
36547
+ do the work inline; when you do fan out, collect every child's result before you rest and fold it into
36548
+ your own write-up. \`SendMessage\` and the \`Agent\` tool named elsewhere in this contract are Claude
36549
+ Code's \u2014 use your harness's equivalent, or none.
36550
+
36551
+ ## Follow-ups arrive between turns
36552
+
36553
+ A message the human sends while you are working is QUEUED and delivered as your next turn the moment
36554
+ this one ends \u2014 nothing can interrupt you mid-turn except a stop. So rest promptly when a turn's work is
36555
+ done rather than idling inside it, and read the newest human message first when a turn opens.`
36365
36556
  };
36366
36557
  SPAWN_THREAD = `## Spawning a separate frizz thread
36367
36558
 
@@ -36452,12 +36643,98 @@ Dispatches share a vocabulary for the deliverable and quality bar, not for fleet
36452
36643
  For a non-trivial change: plan, implement, run the repo's build/lint/test gates, inspect every changed
36453
36644
  call site and downstream effect, self-review the diff, fix confirmed findings, and rerun affected
36454
36645
  checks. Add fresh-context reviewer agents only under the explicit delegation policy above. Review
36455
- advice is evidence to judge, not a verdict to copy. Depth scales with blast radius.`
36646
+ advice is evidence to judge, not a verdict to copy. Depth scales with blast radius.`,
36647
+ acp: `## Thread types
36648
+
36649
+ Recognize which KIND of effort you own and match the deliverable to it:
36650
+
36651
+ - **Research** \u2014 find out what's true. Deliverable is FINDINGS: traces, measurements, exact paths and
36652
+ errors, each load-bearing claim carrying a primary-source \`file:line\` or URL you actually opened. A
36653
+ bug investigation is headed for a FIX, so close with ranked fix options and one recommendation.
36654
+ - **Audit** \u2014 adversarially verify something that exists. Check every prong against the reference,
36655
+ re-verify load-bearing verdicts, cite evidence. Complete = every prong checked.
36656
+ - **Implementation** \u2014 land a DECIDED thing. Plan briefly \u2192 implement \u2192 run the repo's gates \u2192
36657
+ self-review the diff \u2192 fold in every real finding. Complete = MERGED into the project's mainline with
36658
+ docs updated and gates green.
36659
+ - **Planning** \u2014 the DESIGN is the deliverable: a durable plan file, open questions surfaced and then
36660
+ resolved into decisions in that file. A plan that exists only in chat has not been written.
36661
+
36662
+ ## Substantive implementation
36663
+
36664
+ For a non-trivial change: plan, implement, run the repo's build/lint/test gates, inspect every changed
36665
+ call site and downstream effect, self-review the diff, fix confirmed findings, and rerun affected
36666
+ checks. Depth scales with blast radius and yields to the project's conventions.`
36456
36667
  };
36457
36668
  MONITORS_DIR_FALLBACK = "<frizz>/cc-worker/skills/gh/scripts";
36458
36669
  }
36459
36670
  });
36460
36671
 
36672
+ // packages/server/src/backend/acp-agents.ts
36673
+ import { accessSync as accessSync3, constants, statSync as statSync14 } from "node:fs";
36674
+ import { delimiter as delimiter2, isAbsolute as isAbsolute6, join as join30 } from "node:path";
36675
+ function isExecutableFile(path3) {
36676
+ try {
36677
+ if (!statSync14(path3).isFile()) return false;
36678
+ if (process.platform !== "win32") accessSync3(path3, constants.X_OK);
36679
+ return true;
36680
+ } catch {
36681
+ return false;
36682
+ }
36683
+ }
36684
+ function findOnPath(command, env = process.env) {
36685
+ if (isAbsolute6(command)) return isExecutableFile(command) ? command : void 0;
36686
+ const dirs = (env.PATH ?? env.Path ?? "").split(delimiter2).filter(Boolean);
36687
+ const exts = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
36688
+ for (const dir of dirs) {
36689
+ for (const ext of exts) {
36690
+ const candidate = join30(dir, command + ext.toLowerCase());
36691
+ if (isExecutableFile(candidate)) return candidate;
36692
+ if (ext && isExecutableFile(join30(dir, command + ext))) return join30(dir, command + ext);
36693
+ }
36694
+ }
36695
+ return void 0;
36696
+ }
36697
+ function acpAgentSpecs(custom2) {
36698
+ const byId = /* @__PURE__ */ new Map();
36699
+ for (const spec of ACP_AGENT_CATALOGUE) byId.set(spec.id, spec);
36700
+ for (const spec of custom2 ?? []) if (spec.id && spec.command) byId.set(spec.id, { ...spec, args: [...spec.args ?? []] });
36701
+ return [...byId.values()];
36702
+ }
36703
+ function listAcpAgents(custom2, env = process.env) {
36704
+ return acpAgentSpecs(custom2).map((spec) => {
36705
+ const bin = findOnPath(spec.command, env);
36706
+ return bin ? { ...spec, bin } : { ...spec };
36707
+ });
36708
+ }
36709
+ function resolveAcpAgent(id, custom2, env = process.env) {
36710
+ const spec = acpAgentSpecs(custom2).find((s) => s.id === id);
36711
+ if (!spec) return void 0;
36712
+ const bin = findOnPath(spec.command, env);
36713
+ return bin ? { ...spec, bin } : { ...spec };
36714
+ }
36715
+ var ACP_AGENT_CATALOGUE;
36716
+ var init_acp_agents = __esm({
36717
+ "packages/server/src/backend/acp-agents.ts"() {
36718
+ "use strict";
36719
+ init_src();
36720
+ ACP_AGENT_CATALOGUE = [
36721
+ { id: "opencode", label: "OpenCode", command: "opencode", args: ["acp"] },
36722
+ { id: "cursor", label: "Cursor agent", command: "cursor-agent", args: ["acp"] },
36723
+ { id: "gemini", label: "Gemini CLI", command: "gemini", args: ["--acp"] },
36724
+ { id: "copilot", label: "GitHub Copilot CLI", command: "copilot", args: ["--acp"] },
36725
+ { id: "kilo", label: "Kilo", command: "kilo", args: ["acp"] },
36726
+ { id: "qwen", label: "Qwen Code", command: "qwen", args: ["--acp"] },
36727
+ { id: "goose", label: "goose", command: "goose", args: ["acp"] },
36728
+ { id: "kimi", label: "Kimi CLI", command: "kimi", args: ["acp"] },
36729
+ // `grok agent stdio` is ACP over stdio (registry: `npx @xai-official/grok agent stdio`); verified
36730
+ // live 2026-09-16 against grok 1.0.13 — it mounted the probe MCP server and called its tool.
36731
+ { id: "grok", label: "Grok Build", command: "grok", args: ["agent", "stdio"] },
36732
+ // pi has no native ACP mode; `pi-acp` is its registry wrapper and takes no arguments.
36733
+ { id: "pi", label: "pi", command: "pi-acp", args: [] }
36734
+ ];
36735
+ }
36736
+ });
36737
+
36461
36738
  // packages/server/src/backend/claude-broker-client.ts
36462
36739
  import net from "node:net";
36463
36740
  function connectClaudeBroker(socketPath, handlers, options = {}) {
@@ -37360,8 +37637,8 @@ var init_claude_agent_broker_bridge = __esm({
37360
37637
  });
37361
37638
 
37362
37639
  // packages/server/src/dispatch.ts
37363
- import { closeSync as closeSync12, constants, existsSync as existsSync10, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync20, realpathSync as realpathSync8, statSync as statSync14, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
37364
- import { basename as basename6, join as join30, dirname as dirname12 } from "node:path";
37640
+ import { closeSync as closeSync12, constants as constants2, existsSync as existsSync10, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync20, realpathSync as realpathSync8, statSync as statSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
37641
+ import { basename as basename6, join as join31, dirname as dirname12 } from "node:path";
37365
37642
  import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
37366
37643
  function fallbackTitle(prompt) {
37367
37644
  const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
@@ -37380,7 +37657,7 @@ function fallbackTitle(prompt) {
37380
37657
  }
37381
37658
  function resolveSlug(frizzDir, base, taken) {
37382
37659
  base = ThreadSlug.parse(base);
37383
- const isTaken = (slug) => existsSync10(join30(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
37660
+ const isTaken = (slug) => existsSync10(join31(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
37384
37661
  if (!isTaken(base)) return base;
37385
37662
  for (let n = 2; ; n++) {
37386
37663
  const suffix = `-${n}`;
@@ -37397,12 +37674,12 @@ function resolveLegacyThreadFile(projectDir, value) {
37397
37674
  if (!parsed.success) return null;
37398
37675
  try {
37399
37676
  const projectRoot = realpathSync8(projectDir);
37400
- const frizzPath = join30(projectRoot, ".frizz");
37677
+ const frizzPath = join31(projectRoot, ".frizz");
37401
37678
  const frizzStat = lstatSync3(frizzPath);
37402
37679
  if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
37403
37680
  const realFrizz = realpathSync8(frizzPath);
37404
37681
  if (dirname12(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
37405
- const path3 = join30(realFrizz, `${parsed.data}.md`);
37682
+ const path3 = join31(realFrizz, `${parsed.data}.md`);
37406
37683
  const before = lstatSync3(path3);
37407
37684
  if (!before.isFile() || before.isSymbolicLink()) return null;
37408
37685
  const realPath = realpathSync8(path3);
@@ -37410,7 +37687,7 @@ function resolveLegacyThreadFile(projectDir, value) {
37410
37687
  let contents;
37411
37688
  let openedBefore;
37412
37689
  let openedAfter;
37413
- const fd = openSync13(path3, constants.O_RDONLY | constants.O_NOFOLLOW);
37690
+ const fd = openSync13(path3, constants2.O_RDONLY | constants2.O_NOFOLLOW);
37414
37691
  try {
37415
37692
  openedBefore = fstatSync2(fd);
37416
37693
  contents = readFileSync20(fd);
@@ -37446,7 +37723,7 @@ function boardAuthorizesAdoption(board, slug) {
37446
37723
  return !board.errorItems.some((item) => item.file === `${slug}.md`);
37447
37724
  }
37448
37725
  function ensureSafeDirectDirectory(parent, name) {
37449
- const path3 = join30(parent, name);
37726
+ const path3 = join31(parent, name);
37450
37727
  try {
37451
37728
  mkdirSync13(path3);
37452
37729
  } catch (error) {
@@ -37476,8 +37753,8 @@ function loadWorkerPrompt(kind = "claude") {
37476
37753
  function monitorScriptsDir() {
37477
37754
  const plugin = workerPluginDir();
37478
37755
  if (!plugin) return void 0;
37479
- const dir = join30(plugin, "skills", "gh", "scripts");
37480
- return existsSync10(join30(dir, "ci-watch.mjs")) ? dir : void 0;
37756
+ const dir = join31(plugin, "skills", "gh", "scripts");
37757
+ return existsSync10(join31(dir, "ci-watch.mjs")) ? dir : void 0;
37481
37758
  }
37482
37759
  function codexScratchpadHookConfig(hookScript, sessionId) {
37483
37760
  if (!hookScript || !sessionId) return {};
@@ -37489,7 +37766,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
37489
37766
  }
37490
37767
  ]
37491
37768
  });
37492
- const bashBackgroundHook = join30(dirname12(hookScript), "bash-background.mjs");
37769
+ const bashBackgroundHook = join31(dirname12(hookScript), "bash-background.mjs");
37493
37770
  return {
37494
37771
  bypass_hook_trust: true,
37495
37772
  hooks: {
@@ -37515,7 +37792,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
37515
37792
  }
37516
37793
  function scratchpadHookScript() {
37517
37794
  const plugin = workerPluginDir();
37518
- return plugin ? join30(plugin, "hooks", "scratchpad.mjs") : void 0;
37795
+ return plugin ? join31(plugin, "hooks", "scratchpad.mjs") : void 0;
37519
37796
  }
37520
37797
  function composePrompt(sessionId, prompt, kind = "claude") {
37521
37798
  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.";
@@ -37531,10 +37808,10 @@ function scratchpadOrientation(sessionId, kind = "claude") {
37531
37808
  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.`;
37532
37809
  }
37533
37810
  function frizzConfigBlock(projectDir) {
37534
- const path3 = join30(projectDir, "FRIZZ.md");
37811
+ const path3 = join31(projectDir, "FRIZZ.md");
37535
37812
  let body;
37536
37813
  try {
37537
- const st = statSync14(path3);
37814
+ const st = statSync15(path3);
37538
37815
  if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
37539
37816
  body = readFileSync20(path3, "utf8").trim();
37540
37817
  } catch {
@@ -37575,7 +37852,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
37575
37852
  const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
37576
37853
  const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
37577
37854
  if (!pluginDir) return void 0;
37578
- const scriptPath = join30(pluginDir, "bin", FRIZZ_MCP.script);
37855
+ const scriptPath = join31(pluginDir, "bin", FRIZZ_MCP.script);
37579
37856
  if (!existsSync10(scriptPath)) return void 0;
37580
37857
  return {
37581
37858
  scriptPath,
@@ -37658,7 +37935,7 @@ function buildClaudeResumeCommand(opts) {
37658
37935
  }
37659
37936
  function createDispatcher(deps) {
37660
37937
  const readBoardSource = deps.readBoard ?? readBoard;
37661
- const frizzDir = join30(deps.project.dir, ".frizz");
37938
+ const frizzDir = join31(deps.project.dir, ".frizz");
37662
37939
  const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
37663
37940
  function cleanupPrewrites(built) {
37664
37941
  for (const path3 of new Set(built.prewrite.map((file) => file.path))) {
@@ -37671,7 +37948,7 @@ function createDispatcher(deps) {
37671
37948
  function cleanupDispatchFiles(scratchRel, built, sessionId) {
37672
37949
  cleanupPrewrites(built);
37673
37950
  try {
37674
- rmSync9(join30(deps.project.dir, scratchRel), { force: true, recursive: true });
37951
+ rmSync9(join31(deps.project.dir, scratchRel), { force: true, recursive: true });
37675
37952
  } catch {
37676
37953
  }
37677
37954
  cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
@@ -37681,7 +37958,7 @@ function createDispatcher(deps) {
37681
37958
  input = DispatchInput.parse(input);
37682
37959
  const settings = deps.getSettings();
37683
37960
  const kind = opts?.backend ?? "claude";
37684
- if (deps.preflightAuth && await deps.preflightAuth(kind).catch(() => "unknown") === "signed-out") {
37961
+ if (kind !== "acp" && deps.preflightAuth && await deps.preflightAuth(kind).catch(() => "unknown") === "signed-out") {
37685
37962
  throw new ProviderAuthRequiredError(kind);
37686
37963
  }
37687
37964
  if (kind === "codex" && deps.preflightCodexBinary && await deps.preflightCodexBinary().catch(() => "unknown") === "missing") {
@@ -37754,6 +38031,52 @@ function createDispatcher(deps) {
37754
38031
  throw new Error(`Codex app-server could not start this thread: ${err.message}. Check that \`codex\` is installed and its app-server protocol matches the pinned revision (re-pin if you upgraded codex).`);
37755
38032
  }
37756
38033
  }
38034
+ if (kind === "acp") {
38035
+ const bridge = deps.acpBridge;
38036
+ const agentId = acpAgentIdFromModel(model);
38037
+ if (!bridge || !agentId) {
38038
+ cleanupDispatchFiles(scratchRel, { argv: [], env: {}, prewrite: [] }, sessionId);
38039
+ throw new Error(!bridge ? "The ACP bridge is unavailable; cannot start this thread." : `An ACP dispatch needs an agent: pick one in the composer (model \`acp:<agent>\`), got ${JSON.stringify(model ?? null)}.`);
38040
+ }
38041
+ const firstPrompt = [loadWorkerPrompt("acp"), scratchpadOrientation(sessionId, kind), frizzConfigBlock(deps.project.dir), prompt].filter(Boolean).join("\n\n");
38042
+ try {
38043
+ const spawned = await bridge.spawnDispatch({ threadSlug: slug, sessionId, cwd: deps.project.dir, agentId, modelId: acpModelIdFromModel(model), prompt: firstPrompt, userText: input.prompt });
38044
+ deps.storage.upsertSession({
38045
+ slug,
38046
+ session_id: sessionId,
38047
+ thread_name: threadIdentityName(slug),
38048
+ spawned_at: (/* @__PURE__ */ new Date()).toISOString(),
38049
+ last_read_at: null,
38050
+ unread: 0,
38051
+ exited: 0,
38052
+ archived: 0,
38053
+ rested_at: null,
38054
+ title_auto: input.title?.trim() ? 0 : 1,
38055
+ title_locked: 0,
38056
+ title: registryTitle,
38057
+ state: "open",
38058
+ meta: null,
38059
+ seen_at: null,
38060
+ transcript_id: null,
38061
+ model: model ?? null,
38062
+ effort: null,
38063
+ permission_mode: permissionMode
38064
+ });
38065
+ deps.storage.setBackend(slug, "acp");
38066
+ deps.storage.setAgentSession(slug, spawned.acpSessionId);
38067
+ deps.storage.setAcpAgent(slug, agentId);
38068
+ void deps.board.rebuild().catch(() => {
38069
+ });
38070
+ return { slug, sessionId };
38071
+ } catch (err) {
38072
+ try {
38073
+ bridge.releaseSession(slug, sessionId, "session-deleted");
38074
+ } catch {
38075
+ }
38076
+ cleanupDispatchFiles(scratchRel, { argv: [], env: {}, prewrite: [] }, sessionId);
38077
+ throw new Error(`The ACP agent could not start this thread: ${err.message}`);
38078
+ }
38079
+ }
37757
38080
  if (kind === "claude") {
37758
38081
  const bridge = deps.claudeBroker;
37759
38082
  if (!bridge) {
@@ -38059,6 +38382,8 @@ var init_dispatch = __esm({
38059
38382
  init_worker_plugin_dir();
38060
38383
  init_workerPrompt();
38061
38384
  init_codex();
38385
+ init_acp_agents();
38386
+ init_src();
38062
38387
  init_claude_agent_broker_bridge();
38063
38388
  init_claude_effort();
38064
38389
  init_auth_status();
@@ -38101,7 +38426,10 @@ var init_dispatch = __esm({
38101
38426
  FRIZZ_MD_MAX_BYTES = 64 * 1024;
38102
38427
  WORKER_DISPATCH_PERMISSION = {
38103
38428
  claude: "auto",
38104
- codex: "bypassPermissions"
38429
+ codex: "bypassPermissions",
38430
+ // An ACP agent's permissions are its own (plans/acp-backend.md, decision 7): Frizz sends no mode and
38431
+ // mirrors whatever the agent asks as a card. The value here is recorded on the row and nothing else.
38432
+ acp: "auto"
38105
38433
  };
38106
38434
  missingPluginReported = false;
38107
38435
  }
@@ -38113,7 +38441,7 @@ import {
38113
38441
  watch as fsWatch
38114
38442
  } from "node:fs";
38115
38443
  import { homedir as homedir14 } from "node:os";
38116
- import { dirname as dirname13, join as join31 } from "node:path";
38444
+ import { dirname as dirname13, join as join32 } from "node:path";
38117
38445
  import watcher from "@parcel/watcher";
38118
38446
  function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
38119
38447
  if (!liveness) return false;
@@ -38653,7 +38981,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
38653
38981
  // from today's dispatch preference: unknown/migrated rows remain unmarked, while rows whose
38654
38982
  // database default was explicitly normalized to "claude" get the same per-thread identity as
38655
38983
  // Codex rows.
38656
- backend: row.backend === "claude" || row.backend === "codex" ? row.backend : void 0,
38984
+ backend: row.backend === "claude" || row.backend === "codex" || row.backend === "acp" ? row.backend : void 0,
38657
38985
  // Only a persisted, validated per-session value is exposed. A migrated row stays visibly unknown;
38658
38986
  // never label it with today's global defaults (which may not match its running process).
38659
38987
  permissionMode,
@@ -38960,7 +39288,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
38960
39288
  if (parcelSub || stopped) return Promise.resolve();
38961
39289
  if (watchSetup) return watchSetup;
38962
39290
  const setup = (async () => {
38963
- const dir = join31(project.dir, ".frizz");
39291
+ const dir = join32(project.dir, ".frizz");
38964
39292
  const roots = /* @__PURE__ */ new Set([dir]);
38965
39293
  try {
38966
39294
  roots.add(realpathSync9(dir));
@@ -38975,7 +39303,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
38975
39303
  }
38976
39304
  if (events.some((event) => isBoardRelevantFrizzPath(roots, event.path))) scheduleRebuild();
38977
39305
  },
38978
- { ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join31(dir, name)) }
39306
+ { ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join32(dir, name)) }
38979
39307
  );
38980
39308
  if (stopped) {
38981
39309
  await next.unsubscribe();
@@ -39293,7 +39621,7 @@ var init_wake_store = __esm({
39293
39621
  });
39294
39622
 
39295
39623
  // packages/server/src/github-review.ts
39296
- import { execFile as execFile6 } from "node:child_process";
39624
+ import { execFile as execFile7 } from "node:child_process";
39297
39625
  import { promisify as promisify6 } from "node:util";
39298
39626
  function refKey(ref) {
39299
39627
  return `${ref.kind === "issue" ? "issue:" : ""}${ref.owner}/${ref.repo}#${ref.number}`;
@@ -39712,7 +40040,7 @@ var execFileAsync4, GH_TIMEOUT_MS, GH_MAX_BUFFER, REQUEST_TIMEOUT_MS, MAX_REFS_P
39712
40040
  var init_github_review = __esm({
39713
40041
  "packages/server/src/github-review.ts"() {
39714
40042
  "use strict";
39715
- execFileAsync4 = promisify6(execFile6);
40043
+ execFileAsync4 = promisify6(execFile7);
39716
40044
  GH_TIMEOUT_MS = 3e4;
39717
40045
  GH_MAX_BUFFER = 1024 * 1024;
39718
40046
  REQUEST_TIMEOUT_MS = 3e4;
@@ -39849,7 +40177,7 @@ var init_pr_watch_noise = __esm({
39849
40177
  });
39850
40178
 
39851
40179
  // packages/server/src/scheduler.ts
39852
- import { execFile as execFile7 } from "node:child_process";
40180
+ import { execFile as execFile8 } from "node:child_process";
39853
40181
  import { promisify as promisify7 } from "node:util";
39854
40182
  import { createHash as createHash12, randomUUID as randomUUID14 } from "node:crypto";
39855
40183
  function failureSignature(rollup) {
@@ -41740,7 +42068,7 @@ var init_scheduler = __esm({
41740
42068
  init_pr_watch_noise();
41741
42069
  init_github_review();
41742
42070
  init_logging();
41743
- execFileAsync5 = promisify7(execFile7);
42071
+ execFileAsync5 = promisify7(execFile8);
41744
42072
  LIMIT_FENCE_PREFIX = "limit";
41745
42073
  LIMIT_HINT_PREFIX = "limit:";
41746
42074
  LIMIT_RESUME_GRACE_MS = 6e4;
@@ -41862,7 +42190,7 @@ var init_resume = __esm({
41862
42190
  });
41863
42191
 
41864
42192
  // packages/server/src/backend/claude.ts
41865
- import { join as join32 } from "node:path";
42193
+ import { join as join33 } from "node:path";
41866
42194
  function toolResultText2(content) {
41867
42195
  if (typeof content === "string") return content;
41868
42196
  if (!Array.isArray(content)) return "";
@@ -41953,7 +42281,7 @@ function createClaudeBackend(opts) {
41953
42281
  return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
41954
42282
  },
41955
42283
  transcriptPath(sessionId) {
41956
- return join32(opts.logDir, `${sessionId}.jsonl`);
42284
+ return join33(opts.logDir, `${sessionId}.jsonl`);
41957
42285
  },
41958
42286
  parseLine(line) {
41959
42287
  return parseClaudeLine(line);
@@ -41977,6 +42305,1181 @@ var init_claude = __esm({
41977
42305
  }
41978
42306
  });
41979
42307
 
42308
+ // packages/server/src/backend/acp-transcript.ts
42309
+ import { appendFileSync as appendFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync14, readFileSync as readFileSync21 } from "node:fs";
42310
+ import { dirname as dirname14, join as join34 } from "node:path";
42311
+ function acpTranscriptPath(stateDir, sessionId) {
42312
+ return join34(stateDir, ACP_TRANSCRIPT_DIR, `${sessionId}.jsonl`);
42313
+ }
42314
+ function parseAcpRecord(line) {
42315
+ if (!line.trim()) return void 0;
42316
+ let raw2;
42317
+ try {
42318
+ raw2 = JSON.parse(line);
42319
+ } catch {
42320
+ return void 0;
42321
+ }
42322
+ if (!raw2 || typeof raw2 !== "object" || typeof raw2.kind !== "string") return void 0;
42323
+ const kind = raw2.kind;
42324
+ if (kind === "acp-session" || kind === "acp-note" || EVENT_KINDS.has(kind)) return raw2;
42325
+ return void 0;
42326
+ }
42327
+ function parseAcpLine(line) {
42328
+ const rec = parseAcpRecord(line);
42329
+ if (!rec || rec.kind === "acp-session" || rec.kind === "acp-note") return [];
42330
+ const { messageId: _m, acp: _a, ...ev } = rec;
42331
+ return [ev];
42332
+ }
42333
+ function createAcpBackend(opts) {
42334
+ return {
42335
+ kind: "acp",
42336
+ buildSpawn(_o) {
42337
+ throw new Error("acp runs via the ACP bridge; this argv builder has no live caller");
42338
+ },
42339
+ buildResume(_o) {
42340
+ throw new Error("acp runs via the ACP bridge; this argv builder has no live caller");
42341
+ },
42342
+ // Deterministic: the bridge names the file after the frizz session id, so no discovery step.
42343
+ transcriptPath(sessionId) {
42344
+ return acpTranscriptPath(opts.stateDir, sessionId);
42345
+ },
42346
+ parseLine: parseAcpLine,
42347
+ foldLine(state, line) {
42348
+ const rec = parseAcpRecord(line);
42349
+ if (!rec) return;
42350
+ if (rec.kind === "acp-session") {
42351
+ if (rec.model) {
42352
+ state.model = rec.model;
42353
+ state.profileAt = rec.at;
42354
+ state.profileRevision = (state.profileRevision ?? 0) + 1;
42355
+ }
42356
+ return;
42357
+ }
42358
+ for (const ev of parseAcpLine(line)) applyEvent(state, ev);
42359
+ }
42360
+ };
42361
+ }
42362
+ function clip2(text2, max) {
42363
+ return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
42364
+ }
42365
+ function toolTarget(rawInput, acp) {
42366
+ const input = rawInput && typeof rawInput === "object" ? rawInput : void 0;
42367
+ const command = typeof input?.command === "string" ? input.command : void 0;
42368
+ const path3 = typeof input?.filePath === "string" ? input.filePath : typeof input?.file_path === "string" ? input.file_path : typeof input?.path === "string" ? input.path : acp?.locations?.[0];
42369
+ return { command, path: path3, input };
42370
+ }
42371
+ function toolCallFor(ev, acp) {
42372
+ const { command, path: path3, input } = toolTarget(ev.input, acp);
42373
+ const detail2 = command ?? path3 ?? acp?.title ?? "";
42374
+ const call = { name: ev.name, status: "pending" };
42375
+ if (detail2) call.detail = clip2(detail2, 500);
42376
+ if (command) call.command = command;
42377
+ else if (input && Object.keys(input).length) call.input = clip2(JSON.stringify(input, null, 2), TOOL_TEXT_MAX);
42378
+ return call;
42379
+ }
42380
+ function upgradeToolTarget(call, acp) {
42381
+ if (!acp || call.command || call.detail && call.detail !== call.name) return;
42382
+ const { command, path: path3, input } = toolTarget(acp.input, acp);
42383
+ const detail2 = command ?? path3;
42384
+ if (!detail2) return;
42385
+ call.detail = clip2(detail2, 500);
42386
+ if (command) call.command = command;
42387
+ else if (input && Object.keys(input).length && !call.input) call.input = clip2(JSON.stringify(input, null, 2), TOOL_TEXT_MAX);
42388
+ }
42389
+ function projectAcpTranscript(raw2, identityPrefix = "acp") {
42390
+ const out = [];
42391
+ let current;
42392
+ let currentMessageId;
42393
+ const openTools = /* @__PURE__ */ new Map();
42394
+ const lines = raw2.split("\n");
42395
+ const close2 = () => {
42396
+ current = void 0;
42397
+ currentMessageId = void 0;
42398
+ };
42399
+ const openAssistant = (i, at) => {
42400
+ if (!current) {
42401
+ current = { sourceId: `${identityPrefix}:${i}`, role: "assistant", text: "", tools: [], parts: [], ...at ? { at } : {} };
42402
+ out.push(current);
42403
+ }
42404
+ return current;
42405
+ };
42406
+ const appendText = (msg, text2) => {
42407
+ const last = msg.parts[msg.parts.length - 1];
42408
+ if (last?.kind === "text") last.text += text2;
42409
+ else msg.parts.push({ kind: "text", text: text2 });
42410
+ msg.text = msg.parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n\n");
42411
+ };
42412
+ const appendTool = (msg, tool) => {
42413
+ const last = msg.parts[msg.parts.length - 1];
42414
+ if (last?.kind === "tools") last.tools.push(tool);
42415
+ else msg.parts.push({ kind: "tools", tools: [tool] });
42416
+ msg.tools.push(tool);
42417
+ };
42418
+ for (let i = 0; i < lines.length; i++) {
42419
+ const rec = parseAcpRecord(lines[i]);
42420
+ if (!rec) continue;
42421
+ switch (rec.kind) {
42422
+ case "acp-session":
42423
+ case "acp-note":
42424
+ case "title":
42425
+ case "context-usage":
42426
+ case "compaction":
42427
+ case "agent-report":
42428
+ case "agent-instruction":
42429
+ break;
42430
+ case "user-message": {
42431
+ close2();
42432
+ const text2 = rec.text ?? "";
42433
+ out.push({ sourceId: `${identityPrefix}:${i}`, role: "user", text: text2, tools: [], parts: [], ...rec.at ? { at: rec.at } : {}, ...rec.synthetic ? { wake: true } : {} });
42434
+ break;
42435
+ }
42436
+ case "turn-start":
42437
+ break;
42438
+ case "turn-end":
42439
+ close2();
42440
+ break;
42441
+ case "reasoning": {
42442
+ close2();
42443
+ out.push({ sourceId: `${identityPrefix}:${i}`, role: "assistant", kind: "reasoning", text: rec.text, tools: [], parts: [], ...rec.at ? { at: rec.at } : {} });
42444
+ break;
42445
+ }
42446
+ case "assistant-text": {
42447
+ const msg = openAssistant(i, rec.at);
42448
+ const mid = rec.messageId;
42449
+ if (mid && mid === currentMessageId) {
42450
+ const last = msg.parts[msg.parts.length - 1];
42451
+ if (last?.kind === "text") {
42452
+ last.text += rec.text;
42453
+ msg.text = msg.parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n\n");
42454
+ break;
42455
+ }
42456
+ }
42457
+ currentMessageId = mid;
42458
+ appendText(msg, rec.text);
42459
+ break;
42460
+ }
42461
+ case "tool-call": {
42462
+ const msg = openAssistant(i, rec.at);
42463
+ currentMessageId = void 0;
42464
+ const call = toolCallFor(rec, rec.acp);
42465
+ openTools.set(rec.id, call);
42466
+ appendTool(msg, call);
42467
+ break;
42468
+ }
42469
+ case "tool-result": {
42470
+ const call = openTools.get(rec.id);
42471
+ if (!call) break;
42472
+ openTools.delete(rec.id);
42473
+ const failed = rec.acp?.status === "failed" || rec.acp?.kind === "failed";
42474
+ call.status = failed ? "failed" : "completed";
42475
+ upgradeToolTarget(call, rec.acp);
42476
+ if (rec.text) call.output = clip2(rec.text, TOOL_TEXT_MAX);
42477
+ break;
42478
+ }
42479
+ case "provider-error": {
42480
+ const msg = openAssistant(i, rec.at);
42481
+ msg.providerError = rec.error;
42482
+ close2();
42483
+ break;
42484
+ }
42485
+ }
42486
+ }
42487
+ return out;
42488
+ }
42489
+ function readAcpTranscriptFile(absPath, nativeId = absPath) {
42490
+ try {
42491
+ return projectAcpTranscript(readFileSync21(absPath, "utf8"), `acp:${nativeId}`);
42492
+ } catch {
42493
+ return [];
42494
+ }
42495
+ }
42496
+ var ACP_TRANSCRIPT_DIR, EVENT_KINDS, AcpTranscriptWriter, TOOL_TEXT_MAX;
42497
+ var init_acp_transcript = __esm({
42498
+ "packages/server/src/backend/acp-transcript.ts"() {
42499
+ "use strict";
42500
+ init_tailer();
42501
+ ACP_TRANSCRIPT_DIR = "acp";
42502
+ EVENT_KINDS = /* @__PURE__ */ new Set([
42503
+ "turn-start",
42504
+ "turn-end",
42505
+ "provider-error",
42506
+ "assistant-text",
42507
+ "user-message",
42508
+ "tool-call",
42509
+ "tool-result",
42510
+ "reasoning",
42511
+ "agent-report",
42512
+ "agent-instruction",
42513
+ "title",
42514
+ "compaction",
42515
+ "context-usage"
42516
+ ]);
42517
+ AcpTranscriptWriter = class {
42518
+ constructor(path3) {
42519
+ this.path = path3;
42520
+ mkdirSync14(dirname14(path3), { recursive: true });
42521
+ if (!existsSync12(path3)) appendFileSync3(path3, "");
42522
+ }
42523
+ append(record) {
42524
+ appendFileSync3(this.path, JSON.stringify(record) + "\n");
42525
+ }
42526
+ };
42527
+ TOOL_TEXT_MAX = 12e3;
42528
+ }
42529
+ });
42530
+
42531
+ // packages/server/src/backend/acp-types.ts
42532
+ function parseSessionUpdate(raw2) {
42533
+ const parsed = AcpSessionUpdateKnown.safeParse(raw2);
42534
+ return parsed.success ? parsed.data : { sessionUpdate: "unknown", raw: raw2 };
42535
+ }
42536
+ function contentText(block) {
42537
+ if (!block) return void 0;
42538
+ if (block.type === "text" && typeof block.text === "string") return block.text;
42539
+ if (block.type === "resource") {
42540
+ const r = block.resource;
42541
+ return typeof r?.text === "string" ? r.text : void 0;
42542
+ }
42543
+ return void 0;
42544
+ }
42545
+ var ACP_PROTOCOL_VERSION, JsonRpcId, JsonRpcError, JsonRpcInbound, ACP_ERROR_AUTH_REQUIRED, ACP_ERROR_METHOD_NOT_FOUND, ACP_ERROR_INTERNAL, AcpAuthMethod, AcpAgentCapabilities, AcpInitializeResult, AcpMcpServerStdio, AcpConfigOption, AcpNewSessionResult, AcpSetConfigOptionResult, AcpLoadSessionResult, AcpContentBlock, AcpToolKind, AcpToolCallStatus, AcpToolCallContent, AcpToolCallLocation, ToolCallFields, AcpToolCallUpdate, AcpPlanEntry, AcpSessionUpdateKnown, AcpSessionNotification, AcpStopReason, AcpPromptResult, AcpPermissionOptionKind, AcpPermissionOption, AcpRequestPermissionParams;
42546
+ var init_acp_types = __esm({
42547
+ "packages/server/src/backend/acp-types.ts"() {
42548
+ "use strict";
42549
+ init_zod();
42550
+ ACP_PROTOCOL_VERSION = 1;
42551
+ JsonRpcId = external_exports.union([external_exports.string(), external_exports.number()]);
42552
+ JsonRpcError = external_exports.object({
42553
+ code: external_exports.number(),
42554
+ message: external_exports.string(),
42555
+ data: external_exports.unknown().optional()
42556
+ }).passthrough();
42557
+ JsonRpcInbound = external_exports.union([
42558
+ external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, result: external_exports.unknown() }).passthrough(),
42559
+ external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, error: JsonRpcError }).passthrough(),
42560
+ external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, method: external_exports.string(), params: external_exports.unknown().optional() }).passthrough(),
42561
+ external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), method: external_exports.string(), params: external_exports.unknown().optional() }).passthrough()
42562
+ ]);
42563
+ ACP_ERROR_AUTH_REQUIRED = -32e3;
42564
+ ACP_ERROR_METHOD_NOT_FOUND = -32601;
42565
+ ACP_ERROR_INTERNAL = -32603;
42566
+ AcpAuthMethod = external_exports.object({
42567
+ id: external_exports.string(),
42568
+ name: external_exports.string().optional(),
42569
+ description: external_exports.string().optional(),
42570
+ // Absent = "agent" (call `authenticate {methodId}`); "terminal" = relaunch the same program
42571
+ // interactively with `args`/`env` — which Frizz does not do; it tells the operator instead.
42572
+ type: external_exports.string().optional()
42573
+ }).passthrough();
42574
+ AcpAgentCapabilities = external_exports.object({
42575
+ loadSession: external_exports.boolean().optional(),
42576
+ promptCapabilities: external_exports.object({ image: external_exports.boolean().optional(), audio: external_exports.boolean().optional(), embeddedContext: external_exports.boolean().optional() }).passthrough().optional(),
42577
+ mcpCapabilities: external_exports.object({ http: external_exports.boolean().optional(), sse: external_exports.boolean().optional() }).passthrough().optional(),
42578
+ // Object markers: `{}` present = supported, absent = not.
42579
+ sessionCapabilities: external_exports.object({
42580
+ list: external_exports.unknown().optional(),
42581
+ resume: external_exports.unknown().optional(),
42582
+ close: external_exports.unknown().optional(),
42583
+ fork: external_exports.unknown().optional(),
42584
+ delete: external_exports.unknown().optional()
42585
+ }).passthrough().optional()
42586
+ }).passthrough();
42587
+ AcpInitializeResult = external_exports.object({
42588
+ protocolVersion: external_exports.number(),
42589
+ agentCapabilities: AcpAgentCapabilities.optional(),
42590
+ authMethods: external_exports.array(AcpAuthMethod).optional(),
42591
+ agentInfo: external_exports.object({ name: external_exports.string().optional(), title: external_exports.string().optional(), version: external_exports.string().optional() }).passthrough().optional()
42592
+ }).passthrough();
42593
+ AcpMcpServerStdio = external_exports.object({
42594
+ name: external_exports.string(),
42595
+ command: external_exports.string(),
42596
+ args: external_exports.array(external_exports.string()),
42597
+ env: external_exports.array(external_exports.object({ name: external_exports.string(), value: external_exports.string() }))
42598
+ });
42599
+ AcpConfigOption = external_exports.object({
42600
+ id: external_exports.string(),
42601
+ name: external_exports.string().optional(),
42602
+ category: external_exports.string().optional(),
42603
+ type: external_exports.string().optional(),
42604
+ currentValue: external_exports.unknown().optional(),
42605
+ options: external_exports.array(external_exports.object({ value: external_exports.string(), name: external_exports.string().optional() }).passthrough()).optional()
42606
+ }).passthrough();
42607
+ AcpNewSessionResult = external_exports.object({
42608
+ sessionId: external_exports.string().min(1),
42609
+ configOptions: external_exports.array(AcpConfigOption).nullish(),
42610
+ modes: external_exports.unknown().optional()
42611
+ }).passthrough();
42612
+ AcpSetConfigOptionResult = external_exports.object({ configOptions: external_exports.array(AcpConfigOption).nullish() }).passthrough();
42613
+ AcpLoadSessionResult = external_exports.object({
42614
+ configOptions: external_exports.array(AcpConfigOption).nullish(),
42615
+ modes: external_exports.unknown().optional()
42616
+ }).passthrough();
42617
+ AcpContentBlock = external_exports.union([
42618
+ external_exports.object({ type: external_exports.literal("text"), text: external_exports.string() }).passthrough(),
42619
+ external_exports.object({ type: external_exports.literal("resource_link"), uri: external_exports.string(), name: external_exports.string().optional() }).passthrough(),
42620
+ external_exports.object({ type: external_exports.literal("resource"), resource: external_exports.object({ uri: external_exports.string(), text: external_exports.string().optional() }).passthrough() }).passthrough(),
42621
+ external_exports.object({ type: external_exports.literal("image"), mimeType: external_exports.string().optional(), data: external_exports.string().optional() }).passthrough(),
42622
+ external_exports.object({ type: external_exports.string() }).passthrough()
42623
+ ]);
42624
+ AcpToolKind = external_exports.enum(["read", "edit", "delete", "move", "search", "execute", "think", "fetch", "switch_mode", "other"]);
42625
+ AcpToolCallStatus = external_exports.enum(["pending", "in_progress", "completed", "failed"]);
42626
+ AcpToolCallContent = external_exports.union([
42627
+ external_exports.object({ type: external_exports.literal("content"), content: AcpContentBlock }).passthrough(),
42628
+ external_exports.object({ type: external_exports.literal("diff"), path: external_exports.string(), oldText: external_exports.string().nullish(), newText: external_exports.string() }).passthrough(),
42629
+ external_exports.object({ type: external_exports.literal("terminal"), terminalId: external_exports.string() }).passthrough(),
42630
+ external_exports.object({ type: external_exports.string() }).passthrough()
42631
+ ]);
42632
+ AcpToolCallLocation = external_exports.object({ path: external_exports.string(), line: external_exports.number().nullish() }).passthrough();
42633
+ ToolCallFields = {
42634
+ toolCallId: external_exports.string(),
42635
+ title: external_exports.string().optional(),
42636
+ kind: external_exports.string().optional(),
42637
+ status: external_exports.string().optional(),
42638
+ content: external_exports.array(AcpToolCallContent).nullish(),
42639
+ locations: external_exports.array(AcpToolCallLocation).nullish(),
42640
+ rawInput: external_exports.unknown().optional(),
42641
+ rawOutput: external_exports.unknown().optional()
42642
+ };
42643
+ AcpToolCallUpdate = external_exports.object(ToolCallFields).passthrough();
42644
+ AcpPlanEntry = external_exports.object({ content: external_exports.string(), priority: external_exports.string().optional(), status: external_exports.string().optional() }).passthrough();
42645
+ AcpSessionUpdateKnown = external_exports.discriminatedUnion("sessionUpdate", [
42646
+ external_exports.object({ sessionUpdate: external_exports.literal("user_message_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
42647
+ external_exports.object({ sessionUpdate: external_exports.literal("agent_message_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
42648
+ external_exports.object({ sessionUpdate: external_exports.literal("agent_thought_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
42649
+ external_exports.object({ sessionUpdate: external_exports.literal("tool_call"), ...ToolCallFields }).passthrough(),
42650
+ external_exports.object({ sessionUpdate: external_exports.literal("tool_call_update"), ...ToolCallFields }).passthrough(),
42651
+ external_exports.object({ sessionUpdate: external_exports.literal("plan"), entries: external_exports.array(AcpPlanEntry) }).passthrough(),
42652
+ external_exports.object({ sessionUpdate: external_exports.literal("usage_update"), used: external_exports.number(), size: external_exports.number(), cost: external_exports.unknown().optional() }).passthrough(),
42653
+ external_exports.object({ sessionUpdate: external_exports.literal("session_info_update"), title: external_exports.string().nullish(), updatedAt: external_exports.string().nullish() }).passthrough(),
42654
+ external_exports.object({ sessionUpdate: external_exports.literal("config_option_update"), configOptions: external_exports.array(AcpConfigOption) }).passthrough(),
42655
+ external_exports.object({ sessionUpdate: external_exports.literal("compaction_update"), compactionId: external_exports.string().optional(), status: external_exports.string().optional() }).passthrough()
42656
+ ]);
42657
+ AcpSessionNotification = external_exports.object({ sessionId: external_exports.string(), update: external_exports.unknown() }).passthrough();
42658
+ AcpStopReason = external_exports.enum(["end_turn", "max_tokens", "max_turn_requests", "refusal", "cancelled"]);
42659
+ AcpPromptResult = external_exports.object({
42660
+ // A string, not the enum: a future reason must not turn a finished turn into a parse failure.
42661
+ stopReason: external_exports.string(),
42662
+ usage: external_exports.object({ inputTokens: external_exports.number().optional(), outputTokens: external_exports.number().optional(), totalTokens: external_exports.number().optional() }).passthrough().nullish()
42663
+ }).passthrough();
42664
+ AcpPermissionOptionKind = external_exports.enum(["allow_once", "allow_always", "reject_once", "reject_always"]);
42665
+ AcpPermissionOption = external_exports.object({
42666
+ optionId: external_exports.string(),
42667
+ name: external_exports.string().optional(),
42668
+ // A string: an unknown kind renders as an opaque choice rather than failing the whole request.
42669
+ kind: external_exports.string().optional()
42670
+ }).passthrough();
42671
+ AcpRequestPermissionParams = external_exports.object({
42672
+ sessionId: external_exports.string(),
42673
+ toolCall: AcpToolCallUpdate,
42674
+ options: external_exports.array(AcpPermissionOption)
42675
+ }).passthrough();
42676
+ }
42677
+ });
42678
+
42679
+ // packages/server/src/backend/acp-rpc.ts
42680
+ import { spawn as spawn6 } from "node:child_process";
42681
+ import { StringDecoder as StringDecoder4 } from "node:string_decoder";
42682
+ var spawnAcpChild, AcpRequestError, AcpRemoteError, AcpConnectionClosed, AcpRequestTimeout, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINE_BYTES, AcpConnection;
42683
+ var init_acp_rpc = __esm({
42684
+ "packages/server/src/backend/acp-rpc.ts"() {
42685
+ "use strict";
42686
+ init_acp_types();
42687
+ spawnAcpChild = ({ command, args, cwd, env }) => spawn6(command, args, { cwd, env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
42688
+ AcpRequestError = class extends Error {
42689
+ constructor(code, message, data) {
42690
+ super(message);
42691
+ this.code = code;
42692
+ this.data = data;
42693
+ }
42694
+ };
42695
+ AcpRemoteError = class extends Error {
42696
+ constructor(method, error) {
42697
+ super(`${method}: ${error.message} (${error.code})`);
42698
+ this.method = method;
42699
+ this.error = error;
42700
+ }
42701
+ };
42702
+ AcpConnectionClosed = class extends Error {
42703
+ constructor(method, reason) {
42704
+ super(`${method}: agent connection closed (${reason})`);
42705
+ this.method = method;
42706
+ this.reason = reason;
42707
+ }
42708
+ };
42709
+ AcpRequestTimeout = class extends Error {
42710
+ constructor(method, timeoutMs) {
42711
+ super(`${method}: no response from the agent within ${Math.round(timeoutMs / 1e3)}s`);
42712
+ this.method = method;
42713
+ this.timeoutMs = timeoutMs;
42714
+ }
42715
+ };
42716
+ DEFAULT_TIMEOUT_MS = 6e4;
42717
+ DEFAULT_MAX_LINE_BYTES = 8 * 1024 * 1024;
42718
+ AcpConnection = class {
42719
+ constructor(process2, options) {
42720
+ this.process = process2;
42721
+ this.options = options;
42722
+ const maxLine = options.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES;
42723
+ this.exitPromise = new Promise((resolve12) => {
42724
+ process2.on("exit", (code, signal) => {
42725
+ this.settleClosed(`exit ${signal ?? code ?? "?"}`);
42726
+ resolve12({ code, signal });
42727
+ });
42728
+ process2.on("error", (err) => {
42729
+ this.settleClosed(err.message);
42730
+ resolve12({ code: null, signal: null });
42731
+ });
42732
+ });
42733
+ process2.stdout?.on("data", (chunk) => {
42734
+ this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
42735
+ let nl;
42736
+ while ((nl = this.buffer.indexOf("\n")) >= 0) {
42737
+ const line = this.buffer.slice(0, nl);
42738
+ this.buffer = this.buffer.slice(nl + 1);
42739
+ if (this.overflowing) {
42740
+ this.overflowing = false;
42741
+ continue;
42742
+ }
42743
+ this.handleLine(line);
42744
+ }
42745
+ if (this.buffer.length > maxLine) {
42746
+ options.onDiagnostic?.({ kind: "oversized-line", message: `dropped a stdout line longer than ${maxLine} bytes` });
42747
+ this.buffer = "";
42748
+ this.overflowing = true;
42749
+ }
42750
+ });
42751
+ process2.stderr?.on("data", (chunk) => {
42752
+ const text2 = String(chunk);
42753
+ for (const line of text2.split("\n")) {
42754
+ if (!line.trim()) continue;
42755
+ this.stderrTail.push(line.slice(0, 2e3));
42756
+ if (this.stderrTail.length > 40) this.stderrTail.shift();
42757
+ options.onDiagnostic?.({ kind: "stderr", message: line.slice(0, 2e3) });
42758
+ }
42759
+ });
42760
+ }
42761
+ nextId = 1;
42762
+ pending = /* @__PURE__ */ new Map();
42763
+ decoder = new StringDecoder4("utf8");
42764
+ buffer = "";
42765
+ overflowing = false;
42766
+ closedReason;
42767
+ exitPromise;
42768
+ stderrTail = [];
42769
+ /** The last lines the agent wrote to stderr — what a failed dispatch shows the operator. */
42770
+ get recentStderr() {
42771
+ return this.stderrTail;
42772
+ }
42773
+ get closed() {
42774
+ return this.closedReason !== void 0;
42775
+ }
42776
+ /** Resolves when the process has exited (or failed to start). */
42777
+ get exited() {
42778
+ return this.exitPromise;
42779
+ }
42780
+ request(method, params, opts = {}) {
42781
+ if (this.closedReason !== void 0) return Promise.reject(new AcpConnectionClosed(method, this.closedReason));
42782
+ const id = this.nextId++;
42783
+ const timeoutMs = opts.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
42784
+ return new Promise((resolve12, reject) => {
42785
+ const timer = timeoutMs > 0 ? setTimeout(() => {
42786
+ this.pending.delete(id);
42787
+ reject(new AcpRequestTimeout(method, timeoutMs));
42788
+ }, timeoutMs) : void 0;
42789
+ timer?.unref?.();
42790
+ this.pending.set(id, { method, resolve: resolve12, reject, timer });
42791
+ if (!this.write({ jsonrpc: "2.0", id, method, params })) {
42792
+ this.pending.delete(id);
42793
+ if (timer) clearTimeout(timer);
42794
+ reject(new AcpConnectionClosed(method, this.closedReason ?? "stdin closed"));
42795
+ }
42796
+ });
42797
+ }
42798
+ /** A prompt turn is long-lived by design (its response IS the end of the turn), so it is the one
42799
+ * request that must not time out; callers pass `timeoutMs: 0` through this alias for readability. */
42800
+ requestOpenEnded(method, params) {
42801
+ return this.request(method, params, { timeoutMs: 0 });
42802
+ }
42803
+ notify(method, params) {
42804
+ this.write({ jsonrpc: "2.0", method, params });
42805
+ }
42806
+ /** Close stdin (the protocol's teardown) and, if the agent has not exited after `graceMs`, kill it. */
42807
+ async close(graceMs = 3e3) {
42808
+ try {
42809
+ this.process.stdin?.end();
42810
+ } catch {
42811
+ }
42812
+ if (this.closedReason !== void 0) return;
42813
+ const exited = await Promise.race([this.exitPromise.then(() => true), new Promise((r) => setTimeout(() => r(false), graceMs).unref?.())]);
42814
+ if (!exited) {
42815
+ try {
42816
+ this.process.kill("SIGKILL");
42817
+ } catch {
42818
+ }
42819
+ }
42820
+ }
42821
+ write(frame) {
42822
+ if (this.closedReason !== void 0) return false;
42823
+ const stdin = this.process.stdin;
42824
+ if (!stdin || stdin.writableEnded || stdin.destroyed) return false;
42825
+ try {
42826
+ stdin.write(JSON.stringify(frame) + "\n");
42827
+ return true;
42828
+ } catch (err) {
42829
+ this.settleClosed(err.message);
42830
+ return false;
42831
+ }
42832
+ }
42833
+ settleClosed(reason) {
42834
+ if (this.closedReason !== void 0) return;
42835
+ this.closedReason = reason;
42836
+ for (const [id, p] of this.pending) {
42837
+ this.pending.delete(id);
42838
+ if (p.timer) clearTimeout(p.timer);
42839
+ p.reject(new AcpConnectionClosed(p.method, reason));
42840
+ }
42841
+ }
42842
+ handleLine(line) {
42843
+ if (!line.trim()) return;
42844
+ let raw2;
42845
+ try {
42846
+ raw2 = JSON.parse(line);
42847
+ } catch {
42848
+ this.options.onDiagnostic?.({ kind: "junk-line", message: line.slice(0, 500) });
42849
+ return;
42850
+ }
42851
+ const parsed = JsonRpcInbound.safeParse(raw2);
42852
+ if (!parsed.success) {
42853
+ this.options.onDiagnostic?.({ kind: "protocol", message: `unrecognized frame: ${line.slice(0, 500)}` });
42854
+ return;
42855
+ }
42856
+ const frame = parsed.data;
42857
+ if (typeof frame.method === "string") {
42858
+ if (frame.id !== void 0) this.handleRequest(frame.id, frame.method, frame.params);
42859
+ else this.options.onNotification(frame.method, frame.params);
42860
+ return;
42861
+ }
42862
+ const id = frame.id;
42863
+ const p = this.pending.get(id);
42864
+ if (!p) {
42865
+ this.options.onDiagnostic?.({ kind: "protocol", message: `response to unknown request id ${String(id)}` });
42866
+ return;
42867
+ }
42868
+ this.pending.delete(id);
42869
+ if (p.timer) clearTimeout(p.timer);
42870
+ if (frame.error !== void 0) p.reject(new AcpRemoteError(p.method, frame.error));
42871
+ else p.resolve(frame.result);
42872
+ }
42873
+ handleRequest(id, method, params) {
42874
+ void this.options.onRequest(method, params).then(
42875
+ (result) => {
42876
+ this.write({ jsonrpc: "2.0", id, result: result ?? {} });
42877
+ },
42878
+ (err) => {
42879
+ const e = err instanceof AcpRequestError ? { code: err.code, message: err.message, ...err.data !== void 0 ? { data: err.data } : {} } : { code: -32603, message: err instanceof Error ? err.message : String(err) };
42880
+ if (!(err instanceof AcpRequestError)) this.options.onDiagnostic?.({ kind: "unhandled-request", message: `${method}: ${e.message}` });
42881
+ this.write({ jsonrpc: "2.0", id, error: e });
42882
+ }
42883
+ );
42884
+ }
42885
+ };
42886
+ }
42887
+ });
42888
+
42889
+ // packages/server/src/backend/acp-bridge.ts
42890
+ function clip3(text2, max) {
42891
+ return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
42892
+ }
42893
+ function frizzMcpServer(mcp, slug) {
42894
+ if (!mcp) return [];
42895
+ return [{
42896
+ name: FRIZZ_MCP.name,
42897
+ command: process.execPath,
42898
+ args: [mcp.scriptPath],
42899
+ env: Object.entries(frizzMcpEnv({ ...mcp, slug })).map(([name, value]) => ({ name, value }))
42900
+ }];
42901
+ }
42902
+ function toolResultText3(tc) {
42903
+ const parts = [];
42904
+ for (const c of tc.content ?? []) {
42905
+ if (c.type === "content") {
42906
+ const t = contentText(c.content);
42907
+ if (t) parts.push(t);
42908
+ } else if (c.type === "diff") parts.push(`--- ${c.path}
42909
+ ${c.newText}`);
42910
+ }
42911
+ if (parts.length) return parts.join("\n");
42912
+ const raw2 = tc.rawOutput;
42913
+ if (raw2 && typeof raw2 === "object" && typeof raw2.output === "string") return raw2.output;
42914
+ if (typeof tc.rawOutput === "string") return tc.rawOutput;
42915
+ return "";
42916
+ }
42917
+ function buildAcpPermissionInteraction(params, owner, agent) {
42918
+ const tc = params.toolCall;
42919
+ const input = tc.rawInput && typeof tc.rawInput === "object" ? tc.rawInput : void 0;
42920
+ const command = typeof input?.command === "string" ? input.command : "";
42921
+ const isCommand = tc.kind === "execute" && command !== "";
42922
+ const isFile = (tc.kind === "edit" || tc.kind === "delete" || tc.kind === "move") && (tc.locations?.[0]?.path || typeof input?.filePath === "string" || typeof input?.path === "string");
42923
+ const payloadKind = isCommand ? "command-approval" : isFile ? "file-approval" : "permission-approval";
42924
+ const optionByDecision = /* @__PURE__ */ new Map();
42925
+ const allowedDecisions = [];
42926
+ const add = (id, semantic, label, optionId, description) => {
42927
+ if (optionByDecision.has(id)) return;
42928
+ optionByDecision.set(id, optionId);
42929
+ allowedDecisions.push({ id, semantic, label: clip3(label, 150), ...description ? { description: clip3(description, 500) } : {} });
42930
+ };
42931
+ for (const opt of params.options) {
42932
+ const label = opt.name ?? opt.kind ?? opt.optionId;
42933
+ const once = payloadKind === "permission-approval" ? "grant-turn" : "accept";
42934
+ const always = payloadKind === "permission-approval" ? "grant-session" : "acceptForSession";
42935
+ const deny = payloadKind === "permission-approval" ? "deny" : "decline";
42936
+ if (opt.kind === "allow_once") add(once, "approve", label, opt.optionId, "Allow this once.");
42937
+ else if (opt.kind === "allow_always") add(always, "approve", label, opt.optionId, "Allow for the rest of this session.");
42938
+ else if (opt.kind === "reject_once" || opt.kind === "reject_always") add(deny, "deny", label, opt.optionId, "Block this tool call.");
42939
+ }
42940
+ if (!allowedDecisions.some((d) => d.semantic === "deny")) return null;
42941
+ const title = clip3(tc.title ?? (isCommand ? "Run a command?" : "Approve a tool call?"), 150);
42942
+ const previewLines = [command || "", ...Object.entries(input ?? {}).filter(([k, v]) => k !== "command" && v !== "" && v !== null).map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`)].filter(Boolean);
42943
+ const preview = clip3(redactCredentialSyntax(previewLines.join("\n\n")), 6e3);
42944
+ const pathLabel = clip3(tc.locations?.[0]?.path ?? (typeof input?.filePath === "string" ? input.filePath : typeof input?.path === "string" ? input.path : ""), 1e3);
42945
+ const base = {
42946
+ protocolVersion: INTERACTION_PROTOCOL_VERSION,
42947
+ contentFormat: "plain-text",
42948
+ provider: { kind: "acp", name: agent.label },
42949
+ source: { kind: "tool", id: clip3(tc.toolCallId, 500), label: clip3(tc.title ?? tc.kind ?? "tool", 150) },
42950
+ owner: { projectId: owner.projectId, threadSlug: owner.threadSlug, sessionId: owner.sessionId, turnId: clip3(tc.toolCallId, 500), itemId: clip3(tc.toolCallId, 500), sessionEpoch: 0, capabilityRevision: 0 },
42951
+ providerRequestId: clip3(tc.toolCallId, 500),
42952
+ allowedDecisions,
42953
+ expiresAt: null
42954
+ };
42955
+ const payload = payloadKind === "command-approval" ? { kind: "command-approval", title, command: { summary: clip3(command.replace(/\s+/g, " "), 150), preview, redacted: true, workingDirectoryLabel: clip3(owner.cwd, 1e3) } } : payloadKind === "file-approval" ? { kind: "file-approval", title, operation: tc.kind === "delete" ? "delete" : tc.kind === "move" ? "move" : "write", pathLabel: pathLabel || "(unknown path)", ...preview ? { diffPreview: preview } : {} } : { kind: "permission-approval", title, permission: clip3(tc.kind ?? "tool", 250), ...preview ? { preview } : {}, workingDirectoryLabel: clip3(owner.cwd, 1e3) };
42956
+ const parsed = InteractionRequest.safeParse({ ...base, payload });
42957
+ return parsed.success ? { request: parsed.data, optionByDecision } : null;
42958
+ }
42959
+ function createAcpBridge(options) {
42960
+ return new AcpBridge(options);
42961
+ }
42962
+ var AUTH_HINT, ACP_MODEL_CACHE_MS, AcpBridge;
42963
+ var init_acp_bridge = __esm({
42964
+ "packages/server/src/backend/acp-bridge.ts"() {
42965
+ "use strict";
42966
+ init_src();
42967
+ init_credential_redaction();
42968
+ init_logging();
42969
+ init_acp_agents();
42970
+ init_acp_rpc();
42971
+ init_acp_transcript();
42972
+ init_acp_types();
42973
+ init_types2();
42974
+ init_worker_env();
42975
+ AUTH_HINT = "The agent needs a login first. ";
42976
+ ACP_MODEL_CACHE_MS = 10 * 6e4;
42977
+ AcpBridge = class {
42978
+ constructor(options) {
42979
+ this.options = options;
42980
+ this.unsubscribe = options.interactions?.subscribe((change) => {
42981
+ for (const live of this.sessions.values()) {
42982
+ const pending = live.pendingPerms.get(change.interactionId);
42983
+ if (!pending || change.lifecycle === "pending") continue;
42984
+ live.pendingPerms.delete(change.interactionId);
42985
+ const record = options.interactions.get(pending.scope, change.interactionId);
42986
+ const decisionId = change.lifecycle === "resolved" ? record?.resolution?.decisionId : void 0;
42987
+ const optionId = decisionId ? pending.optionByDecision.get(decisionId) : void 0;
42988
+ pending.resolve(optionId ? { outcome: { outcome: "selected", optionId } } : { outcome: { outcome: "cancelled" } });
42989
+ }
42990
+ });
42991
+ }
42992
+ sessions = /* @__PURE__ */ new Map();
42993
+ unsubscribe;
42994
+ closed = false;
42995
+ now() {
42996
+ return (this.options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
42997
+ }
42998
+ diagnostic(slug, kind, message) {
42999
+ this.options.onDiagnostic?.({ threadSlug: slug, kind, message });
43000
+ if (kind !== "stderr") log.debug("acp", `[${slug}] ${kind}: ${message}`);
43001
+ }
43002
+ /** The agents this bridge can launch, with the operator's own entries merged in. */
43003
+ agents() {
43004
+ return acpAgentSpecs(this.options.customAgents?.());
43005
+ }
43006
+ resolveAgent(agentId) {
43007
+ const agent = resolveAcpAgent(agentId, this.options.customAgents?.(), this.options.env ?? process.env);
43008
+ if (!agent) throw new Error(`Unknown ACP agent "${agentId}". Add it under settings.acpAgents or pick one of: ${this.agents().map((a) => a.id).join(", ")}.`);
43009
+ if (!agent.bin) throw new Error(`The ACP agent "${agent.label}" is not installed: \`${agent.command}\` is not on PATH. Install it, or point settings.acpAgents at its executable.`);
43010
+ return agent;
43011
+ }
43012
+ // ---- session lifecycle ----------------------------------------------------------------------
43013
+ /** Spawn the agent, initialize, and open (or load) a session. Throws an actionable error on any
43014
+ * failure and leaves nothing behind. */
43015
+ async open(input) {
43016
+ const { threadSlug: slug, sessionId, cwd } = input;
43017
+ const agent = this.resolveAgent(input.agentId);
43018
+ const env = inheritWorkerEnvironment(this.options.env ?? process.env);
43019
+ const spawn8 = this.options.spawn ?? spawnAcpChild;
43020
+ const proc = spawn8({ command: agent.bin, args: agent.args, cwd, env });
43021
+ const writer = new AcpTranscriptWriter(acpTranscriptPath(this.options.stateDir, sessionId));
43022
+ let live;
43023
+ const conn = new AcpConnection(proc, {
43024
+ onRequest: (method, params) => live ? this.handleRequest(live, method, params) : Promise.reject(new AcpRequestError(ACP_ERROR_INTERNAL, "session not ready")),
43025
+ onNotification: (method, params) => {
43026
+ if (live && method === "session/update") this.handleNotification(live, params);
43027
+ },
43028
+ onDiagnostic: (d) => this.diagnostic(slug, d.kind, d.message),
43029
+ requestTimeoutMs: this.options.initializeTimeoutMs ?? 6e4
43030
+ });
43031
+ const fail = async (err) => {
43032
+ await conn.close(500);
43033
+ const stderr = conn.recentStderr.slice(-3).join(" | ");
43034
+ const e = err instanceof Error ? err : new Error(String(err));
43035
+ throw new Error(`${e.message}${stderr ? ` (agent stderr: ${clip3(stderr, 400)})` : ""}`);
43036
+ };
43037
+ let init;
43038
+ try {
43039
+ init = AcpInitializeResult.parse(await conn.request("initialize", {
43040
+ protocolVersion: ACP_PROTOCOL_VERSION,
43041
+ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
43042
+ clientInfo: { name: "frizz", version: "0" }
43043
+ }));
43044
+ } catch (err) {
43045
+ return fail(err instanceof Error ? new Error(`${agent.label} did not complete the ACP handshake: ${err.message}`) : err);
43046
+ }
43047
+ const mcpServers = frizzMcpServer(this.options.frizzMcp, slug);
43048
+ live = { slug, sessionId, cwd, agent, conn, init, acpSessionId: "", writer, loading: false, queue: [], pendingPerms: /* @__PURE__ */ new Map(), tools: /* @__PURE__ */ new Map(), exited: false };
43049
+ void conn.exited.then(() => {
43050
+ if (live) this.onExit(live);
43051
+ });
43052
+ const authError = (err) => {
43053
+ const hint = (init.authMethods ?? []).map((m) => m.description ?? m.name ?? m.id).filter(Boolean).join("; ");
43054
+ return new Error(`${AUTH_HINT}${agent.label} refused to open a session: ${err.error.message}.${hint ? ` ${hint}.` : ""}`);
43055
+ };
43056
+ let resumed = "fresh";
43057
+ if (input.acpSessionId && init.agentCapabilities?.loadSession) {
43058
+ live.loading = true;
43059
+ try {
43060
+ const loaded = AcpLoadSessionResult.parse(await conn.request("session/load", { sessionId: input.acpSessionId, cwd, mcpServers }));
43061
+ live.acpSessionId = input.acpSessionId;
43062
+ this.adoptModel(live, loaded.configOptions);
43063
+ resumed = "loaded";
43064
+ } catch (err) {
43065
+ if (err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED) return fail(authError(err));
43066
+ this.diagnostic(slug, "protocol", `session/load ${input.acpSessionId} failed (${err.message}); starting a fresh session`);
43067
+ } finally {
43068
+ live.loading = false;
43069
+ }
43070
+ }
43071
+ if (!live.acpSessionId) {
43072
+ try {
43073
+ const created = AcpNewSessionResult.parse(await conn.request("session/new", { cwd, mcpServers }));
43074
+ live.acpSessionId = created.sessionId;
43075
+ this.adoptModel(live, created.configOptions);
43076
+ } catch (err) {
43077
+ if (err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED) return fail(authError(err));
43078
+ return fail(err instanceof Error ? new Error(`${agent.label} could not open a session: ${err.message}`) : err);
43079
+ }
43080
+ }
43081
+ const modelProblem = await this.applyModel(live, input.modelId);
43082
+ if (resumed === "fresh") {
43083
+ writer.append({ kind: "acp-session", at: this.now(), agent: { id: agent.id, ...init.agentInfo?.name ? { name: init.agentInfo.name } : {}, ...init.agentInfo?.version ? { version: init.agentInfo.version } : {} }, acpSessionId: live.acpSessionId, cwd, ...live.model ? { model: live.model } : {} });
43084
+ if (input.acpSessionId) writer.append({ kind: "acp-note", at: this.now(), text: `Resumed with a fresh ${agent.label} session: the previous one (${input.acpSessionId}) could not be loaded, so the agent no longer has the earlier conversation.` });
43085
+ }
43086
+ if (modelProblem) writer.append({ kind: "acp-note", at: this.now(), text: modelProblem });
43087
+ this.sessions.set(sessionId, live);
43088
+ return { live, resumed };
43089
+ }
43090
+ /** The agent's model option (`category: "model"`, else the one literally named `model`). */
43091
+ modelOption(configOptions) {
43092
+ return configOptions?.find((o) => o.category === "model") ?? configOptions?.find((o) => o.id === "model");
43093
+ }
43094
+ adoptModel(live, configOptions) {
43095
+ const option = this.modelOption(configOptions);
43096
+ if (!option) return;
43097
+ live.modelOptionId = option.id;
43098
+ if (typeof option.currentValue === "string") live.model = option.currentValue;
43099
+ }
43100
+ /** Ask the agent for the thread's model when it is not already the one in effect. Returns the note
43101
+ * to write when the agent refused — the thread runs on, on the agent's own model, and says so. */
43102
+ async applyModel(live, modelId) {
43103
+ if (!modelId || modelId === live.model) return void 0;
43104
+ try {
43105
+ const res = AcpSetConfigOptionResult.parse(await live.conn.request("session/set_config_option", { sessionId: live.acpSessionId, configId: live.modelOptionId ?? "model", value: modelId }));
43106
+ this.adoptModel(live, res.configOptions);
43107
+ if (live.model !== modelId && res.configOptions?.length) return `${live.agent.label} did not switch to ${modelId}; the session runs on ${live.model ?? "its own default model"}.`;
43108
+ if (!res.configOptions?.length) live.model = modelId;
43109
+ return void 0;
43110
+ } catch (err) {
43111
+ const message = err instanceof AcpRemoteError ? err.error.message : err.message;
43112
+ this.diagnostic(live.slug, "protocol", `session/set_config_option ${modelId} failed: ${message}`);
43113
+ return `${live.agent.label} could not switch to ${modelId} (${clip3(message, 200)}); the session runs on ${live.model ?? "its own default model"}.`;
43114
+ }
43115
+ }
43116
+ // ---- the model catalogue an agent advertises -----------------------------------------------------
43117
+ modelProbes = /* @__PURE__ */ new Map();
43118
+ modelCache = /* @__PURE__ */ new Map();
43119
+ /** The models an agent offers, read by opening a throwaway session (there is no other way to ask:
43120
+ * ACP advertises models only in a session's config options). Cached per agent for ten minutes; a
43121
+ * probe that cannot open a session (not installed, not logged in) yields an empty list with the
43122
+ * reason, never a throw, so the composer degrades to the agent's own default. */
43123
+ agentModels(agentId, cwd, opts = {}) {
43124
+ const cached2 = this.modelCache.get(agentId);
43125
+ if (cached2 && !opts.refresh && Date.now() - Date.parse(cached2.probedAt) < ACP_MODEL_CACHE_MS) return Promise.resolve(cached2);
43126
+ const inflight = this.modelProbes.get(agentId);
43127
+ if (inflight) return inflight;
43128
+ const probe = this.probeModels(agentId, cwd).then((result) => {
43129
+ if (!result.error) this.modelCache.set(agentId, result);
43130
+ return result;
43131
+ }).finally(() => this.modelProbes.delete(agentId));
43132
+ this.modelProbes.set(agentId, probe);
43133
+ return probe;
43134
+ }
43135
+ async probeModels(agentId, cwd) {
43136
+ const probedAt = this.now();
43137
+ const empty = (error) => ({ agentId, models: [], error, probedAt });
43138
+ let agent;
43139
+ try {
43140
+ agent = this.resolveAgent(agentId);
43141
+ } catch (err) {
43142
+ return empty(err.message);
43143
+ }
43144
+ const env = inheritWorkerEnvironment(this.options.env ?? process.env);
43145
+ const spawn8 = this.options.spawn ?? spawnAcpChild;
43146
+ let proc;
43147
+ try {
43148
+ proc = spawn8({ command: agent.bin, args: agent.args, cwd, env });
43149
+ } catch (err) {
43150
+ return empty(`${agent.label} could not start: ${err.message}`);
43151
+ }
43152
+ const conn = new AcpConnection(proc, {
43153
+ onRequest: () => Promise.reject(new AcpRequestError(ACP_ERROR_METHOD_NOT_FOUND, "probe session takes no requests")),
43154
+ onNotification: () => {
43155
+ },
43156
+ onDiagnostic: (d) => this.diagnostic(`probe:${agentId}`, d.kind, d.message),
43157
+ requestTimeoutMs: this.options.initializeTimeoutMs ?? 6e4
43158
+ });
43159
+ try {
43160
+ const init = AcpInitializeResult.parse(await conn.request("initialize", {
43161
+ protocolVersion: ACP_PROTOCOL_VERSION,
43162
+ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
43163
+ clientInfo: { name: "frizz", version: "0" }
43164
+ }));
43165
+ const created = AcpNewSessionResult.parse(await conn.request("session/new", { cwd, mcpServers: [] }));
43166
+ const option = this.modelOption(created.configOptions);
43167
+ const current = typeof option?.currentValue === "string" ? option.currentValue : void 0;
43168
+ void init;
43169
+ return {
43170
+ agentId,
43171
+ models: (option?.options ?? []).map((o) => ({ id: o.value, name: o.name ?? o.value })),
43172
+ ...current ? { current } : {},
43173
+ probedAt
43174
+ };
43175
+ } catch (err) {
43176
+ const message = err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED ? `${AUTH_HINT}${agent.label} refused to open a session: ${err.error.message}` : `${agent.label}: ${err.message}`;
43177
+ return empty(clip3(message, 400));
43178
+ } finally {
43179
+ await conn.close(500);
43180
+ }
43181
+ }
43182
+ onExit(live) {
43183
+ if (live.exited) return;
43184
+ live.exited = true;
43185
+ this.retirePermissions(live, "provider-cancelled");
43186
+ if (live.turn) {
43187
+ }
43188
+ if (this.sessions.get(live.sessionId) === live) this.sessions.delete(live.sessionId);
43189
+ this.options.onStatusChange?.();
43190
+ }
43191
+ // ---- public API --------------------------------------------------------------------------------
43192
+ async spawnDispatch(input) {
43193
+ if (this.closed) throw new Error("ACP bridge is closed");
43194
+ const { live } = await this.open(input);
43195
+ void this.runTurn(live, input.prompt, input.userText, void 0);
43196
+ return this.info(live);
43197
+ }
43198
+ /** Deliver a follow-up: to the live session, to a re-opened one (load or fresh), or into the queue
43199
+ * when a turn is running. Returns the (possibly new) ACP session id so the caller can re-pin it. */
43200
+ async followUp(input) {
43201
+ if (this.closed) throw new Error("ACP bridge is closed");
43202
+ let live = this.sessions.get(input.sessionId);
43203
+ let resumed = "live";
43204
+ if (!live || live.exited || live.conn.closed) {
43205
+ const opened = await this.open(input);
43206
+ live = opened.live;
43207
+ resumed = opened.resumed;
43208
+ }
43209
+ if (live.turn) {
43210
+ live.queue.push({ text: input.text, ...input.deliveryId ? { deliveryId: input.deliveryId } : {} });
43211
+ return { ...this.info(live), state: "queued", resumed };
43212
+ }
43213
+ void this.runTurn(live, input.text, input.text, input.deliveryId);
43214
+ return { ...this.info(live), state: "delivered", resumed };
43215
+ }
43216
+ /** `session/cancel`, then wait (bounded) for the prompt to return. `interrupted: false` when no turn
43217
+ * was running. The queue is NOT discarded — a follow-up typed during the turn still goes out. */
43218
+ async interruptTurn(threadSlug, sessionId) {
43219
+ const live = this.sessions.get(sessionId);
43220
+ if (!live || live.slug !== threadSlug || !live.turn) return { interrupted: false };
43221
+ live.turn.cancelRequested = true;
43222
+ live.conn.notify("session/cancel", { sessionId: live.acpSessionId });
43223
+ const settled = await Promise.race([live.turn.done.then(() => true), new Promise((r) => setTimeout(() => r(false), 15e3).unref?.())]);
43224
+ if (!settled) {
43225
+ await live.conn.close(1e3);
43226
+ return { interrupted: true };
43227
+ }
43228
+ return { interrupted: true };
43229
+ }
43230
+ releaseSession(threadSlug, sessionId, reason) {
43231
+ const live = this.sessions.get(sessionId);
43232
+ if (!live || live.slug !== threadSlug) return;
43233
+ this.sessions.delete(sessionId);
43234
+ live.queue.length = 0;
43235
+ this.retirePermissions(live, "provider-cancelled");
43236
+ if (live.turn) live.conn.notify("session/cancel", { sessionId: live.acpSessionId });
43237
+ void live.conn.close(reason === "shutdown" ? 1e3 : 3e3);
43238
+ }
43239
+ turnLiveness(threadSlug, sessionId) {
43240
+ const live = this.sessions.get(sessionId);
43241
+ if (!live || live.slug !== threadSlug) return void 0;
43242
+ return { sessionLive: !live.exited && !live.conn.closed, turnActive: live.turn !== void 0, queued: live.queue.length };
43243
+ }
43244
+ session(threadSlug, sessionId) {
43245
+ const live = this.sessions.get(sessionId);
43246
+ return live && live.slug === threadSlug ? this.info(live) : void 0;
43247
+ }
43248
+ ownsInteraction(scope, interactionId) {
43249
+ const live = this.sessions.get(scope.sessionId);
43250
+ return live !== void 0 && live.slug === scope.threadSlug && live.pendingPerms.has(interactionId);
43251
+ }
43252
+ async shutdown() {
43253
+ this.closed = true;
43254
+ this.unsubscribe?.();
43255
+ const all = [...this.sessions.values()];
43256
+ this.sessions.clear();
43257
+ await Promise.all(all.map(async (live) => {
43258
+ this.retirePermissions(live, "provider-cancelled");
43259
+ await live.conn.close(1e3);
43260
+ }));
43261
+ }
43262
+ info(live) {
43263
+ return {
43264
+ acpSessionId: live.acpSessionId,
43265
+ agent: { id: live.agent.id, ...live.init.agentInfo?.name ? { name: live.init.agentInfo.name } : {}, ...live.init.agentInfo?.version ? { version: live.init.agentInfo.version } : {} },
43266
+ ...live.model ? { model: live.model } : {}
43267
+ };
43268
+ }
43269
+ // ---- the turn ---------------------------------------------------------------------------------
43270
+ async runTurn(live, sendText, recordText, deliveryId) {
43271
+ let finish;
43272
+ const done = new Promise((r) => {
43273
+ finish = r;
43274
+ });
43275
+ const turn = { startedAt: this.now(), ...deliveryId ? { deliveryId } : {}, text: "", messageText: "", thought: "", cancelRequested: false, done, finish };
43276
+ live.turn = turn;
43277
+ live.writer.append({ kind: "user-message", at: turn.startedAt, text: recordText, synthetic: false });
43278
+ live.writer.append({ kind: "turn-start", at: turn.startedAt });
43279
+ this.options.onStatusChange?.();
43280
+ try {
43281
+ const raw2 = await live.conn.requestOpenEnded("session/prompt", { sessionId: live.acpSessionId, prompt: [{ type: "text", text: sendText }] });
43282
+ const result = AcpPromptResult.parse(raw2);
43283
+ this.flushThought(live);
43284
+ const finalText = this.flushText(live, true);
43285
+ live.writer.append({ kind: "turn-end", at: this.now(), ...finalText !== void 0 ? { finalText } : {}, successful: result.stopReason !== "refusal" && result.stopReason !== "cancelled" });
43286
+ if (result.stopReason === "cancelled") live.writer.append({ kind: "acp-note", at: this.now(), text: "Turn cancelled." });
43287
+ } catch (err) {
43288
+ this.flushThought(live);
43289
+ const finalText = this.flushText(live, true);
43290
+ const at = this.now();
43291
+ const abortedByUs = turn.cancelRequested && err instanceof AcpRemoteError && err.error.code === ACP_ERROR_INTERNAL;
43292
+ if (abortedByUs) {
43293
+ live.writer.append({ kind: "turn-end", at, ...finalText !== void 0 ? { finalText } : {}, successful: false });
43294
+ live.writer.append({ kind: "acp-note", at, text: "Turn cancelled." });
43295
+ } else {
43296
+ const message = err instanceof AcpRemoteError ? err.error.code === ACP_ERROR_AUTH_REQUIRED ? `${AUTH_HINT}${err.error.message}` : err.error.message : err.message;
43297
+ live.writer.append({ kind: "provider-error", at, error: { message: clip3(message, 2e3), ...err instanceof AcpRemoteError ? { code: String(err.error.code) } : {} } });
43298
+ live.writer.append({ kind: "turn-end", at, ...finalText !== void 0 ? { finalText } : {}, successful: false });
43299
+ }
43300
+ } finally {
43301
+ this.retirePermissions(live, "turn-ended");
43302
+ if (live.turn === turn) live.turn = void 0;
43303
+ turn.finish();
43304
+ this.options.onStatusChange?.();
43305
+ const next = live.queue.shift();
43306
+ if (next && !live.exited && !live.conn.closed) void this.runTurn(live, next.text, next.text, next.deliveryId);
43307
+ }
43308
+ }
43309
+ /** Write the buffered text as one record. Returns, for a FINAL flush, the whole text of the agent's
43310
+ * last message (every partial flush included) — what the turn-end carries as `finalText`, so the
43311
+ * fold's preview and fence parsing see the complete message, never the last chunk. */
43312
+ flushText(live, final) {
43313
+ const turn = live.turn;
43314
+ if (!turn) return void 0;
43315
+ if (turn.flushTimer) {
43316
+ clearTimeout(turn.flushTimer);
43317
+ turn.flushTimer = void 0;
43318
+ }
43319
+ if (turn.text) {
43320
+ const text2 = turn.text;
43321
+ turn.text = "";
43322
+ live.writer.append({ kind: "assistant-text", at: this.now(), text: text2, final, ...turn.messageId ? { messageId: turn.messageId } : {} });
43323
+ }
43324
+ return final && turn.messageText ? turn.messageText : void 0;
43325
+ }
43326
+ flushThought(live) {
43327
+ const turn = live.turn;
43328
+ if (!turn?.thought.trim()) {
43329
+ if (turn) turn.thought = "";
43330
+ return;
43331
+ }
43332
+ live.writer.append({ kind: "reasoning", at: this.now(), text: turn.thought });
43333
+ turn.thought = "";
43334
+ }
43335
+ scheduleFlush(live) {
43336
+ const turn = live.turn;
43337
+ if (!turn || turn.flushTimer) return;
43338
+ turn.flushTimer = setTimeout(() => {
43339
+ turn.flushTimer = void 0;
43340
+ this.flushText(live, false);
43341
+ }, this.options.flushMs ?? 1e3);
43342
+ turn.flushTimer.unref?.();
43343
+ }
43344
+ handleNotification(live, params) {
43345
+ if (live.loading) return;
43346
+ const parsed = AcpSessionNotification.safeParse(params);
43347
+ if (!parsed.success) {
43348
+ this.diagnostic(live.slug, "protocol", "malformed session/update");
43349
+ return;
43350
+ }
43351
+ const update = parseSessionUpdate(parsed.data.update);
43352
+ const turn = live.turn;
43353
+ switch (update.sessionUpdate) {
43354
+ case "agent_thought_chunk": {
43355
+ if (!turn) return;
43356
+ const t = contentText(update.content);
43357
+ if (t) turn.thought += t;
43358
+ return;
43359
+ }
43360
+ case "agent_message_chunk": {
43361
+ if (!turn) return;
43362
+ this.flushThought(live);
43363
+ const t = contentText(update.content);
43364
+ if (!t) return;
43365
+ if (update.messageId && turn.messageId && update.messageId !== turn.messageId) {
43366
+ this.flushText(live, false);
43367
+ turn.messageText = "";
43368
+ }
43369
+ if (update.messageId) turn.messageId = update.messageId;
43370
+ turn.text += t;
43371
+ turn.messageText += t;
43372
+ this.scheduleFlush(live);
43373
+ return;
43374
+ }
43375
+ case "tool_call": {
43376
+ this.flushThought(live);
43377
+ this.flushText(live, false);
43378
+ if (turn) turn.messageText = "";
43379
+ const meta = { ...update.kind ? { kind: update.kind } : {}, ...update.title ? { title: update.title } : {}, locations: (update.locations ?? []).map((l) => l.path), written: true };
43380
+ live.tools.set(update.toolCallId, meta);
43381
+ live.writer.append({ kind: "tool-call", at: this.now(), id: update.toolCallId, name: update.title ?? update.kind ?? "tool", input: update.rawInput ?? {}, acp: { ...meta.kind ? { kind: meta.kind } : {}, ...meta.title ? { title: meta.title } : {}, locations: meta.locations ?? [] } });
43382
+ if (update.status === "completed" || update.status === "failed") this.recordToolResult(live, update);
43383
+ return;
43384
+ }
43385
+ case "tool_call_update": {
43386
+ let meta = live.tools.get(update.toolCallId);
43387
+ if (!meta) {
43388
+ meta = { written: false };
43389
+ live.tools.set(update.toolCallId, meta);
43390
+ }
43391
+ if (update.kind) meta.kind = update.kind;
43392
+ if (update.title) meta.title = update.title;
43393
+ if (update.locations?.length) meta.locations = update.locations.map((l) => l.path);
43394
+ if (!meta.written) {
43395
+ this.flushThought(live);
43396
+ this.flushText(live, false);
43397
+ meta.written = true;
43398
+ live.writer.append({ kind: "tool-call", at: this.now(), id: update.toolCallId, name: meta.title ?? meta.kind ?? "tool", input: update.rawInput ?? {}, acp: { ...meta.kind ? { kind: meta.kind } : {}, ...meta.title ? { title: meta.title } : {}, locations: meta.locations ?? [] } });
43399
+ }
43400
+ if (update.status === "completed" || update.status === "failed") this.recordToolResult(live, update);
43401
+ return;
43402
+ }
43403
+ case "usage_update":
43404
+ this.flushText(live, false);
43405
+ if (update.used > 0) live.writer.append({ kind: "context-usage", at: this.now(), tokens: update.used, window: update.size });
43406
+ return;
43407
+ case "session_info_update":
43408
+ if (update.title) live.writer.append({ kind: "title", title: clip3(update.title, 200) });
43409
+ return;
43410
+ case "config_option_update":
43411
+ this.adoptModel(live, update.configOptions);
43412
+ return;
43413
+ case "plan":
43414
+ case "user_message_chunk":
43415
+ case "compaction_update":
43416
+ return;
43417
+ case "unknown":
43418
+ this.diagnostic(live.slug, "update", `unhandled session/update ${String(update.raw?.sessionUpdate)}`);
43419
+ return;
43420
+ }
43421
+ }
43422
+ recordToolResult(live, tc) {
43423
+ const meta = live.tools.get(tc.toolCallId);
43424
+ if (meta?.status === "completed" || meta?.status === "failed") return;
43425
+ if (meta) meta.status = tc.status;
43426
+ const input = tc.rawInput && typeof tc.rawInput === "object" ? tc.rawInput : void 0;
43427
+ live.writer.append({ kind: "tool-result", at: this.now(), id: tc.toolCallId, text: clip3(toolResultText3(tc), 2e4), acp: { ...tc.status ? { kind: tc.status } : {}, ...meta?.locations?.length ? { locations: meta.locations } : {}, ...input && Object.keys(input).length ? { input } : {} } });
43428
+ }
43429
+ // ---- the agent asking us ------------------------------------------------------------------------
43430
+ async handleRequest(live, method, params) {
43431
+ if (method === "session/request_permission") return this.handlePermission(live, params);
43432
+ throw new AcpRequestError(ACP_ERROR_METHOD_NOT_FOUND, `frizz does not implement ${method}`);
43433
+ }
43434
+ handlePermission(live, params) {
43435
+ const parsed = AcpRequestPermissionParams.safeParse(params);
43436
+ const cancelled = { outcome: { outcome: "cancelled" } };
43437
+ if (!parsed.success) return Promise.resolve(cancelled);
43438
+ const store = this.options.interactions;
43439
+ const reject = parsed.data.options.find((o) => o.kind === "reject_once") ?? parsed.data.options.find((o) => o.kind === "reject_always");
43440
+ const failClosed = reject ? { outcome: { outcome: "selected", optionId: reject.optionId } } : cancelled;
43441
+ if (!store) {
43442
+ this.diagnostic(live.slug, "permission", "no interaction store; denying the request");
43443
+ return Promise.resolve(failClosed);
43444
+ }
43445
+ const built = buildAcpPermissionInteraction(parsed.data, { projectId: this.options.projectId, threadSlug: live.slug, sessionId: live.sessionId, cwd: live.cwd }, { id: live.agent.id, label: live.agent.label });
43446
+ if (!built) {
43447
+ this.diagnostic(live.slug, "permission", "request could not be represented as a card; denying");
43448
+ return Promise.resolve(failClosed);
43449
+ }
43450
+ const scope = { projectId: this.options.projectId, threadSlug: live.slug, sessionId: live.sessionId };
43451
+ let id;
43452
+ try {
43453
+ id = store.create(built.request).interaction.id;
43454
+ } catch (err) {
43455
+ this.diagnostic(live.slug, "permission", `could not record the approval: ${err.message}`);
43456
+ return Promise.resolve(failClosed);
43457
+ }
43458
+ return new Promise((resolve12) => {
43459
+ live.pendingPerms.set(id, { resolve: resolve12, optionByDecision: built.optionByDecision, scope });
43460
+ this.options.onStatusChange?.();
43461
+ });
43462
+ }
43463
+ /** Cancel every open card for a session that can no longer answer one, answering the agent
43464
+ * `cancelled` for each. Hygiene, so it never throws. */
43465
+ retirePermissions(live, reason) {
43466
+ if (!live.pendingPerms.size) return;
43467
+ const store = this.options.interactions;
43468
+ let swept = [];
43469
+ try {
43470
+ swept = store?.cancelForSession(live.slug, live.sessionId, reason) ?? [];
43471
+ } catch {
43472
+ }
43473
+ for (const record of swept) live.pendingPerms.get(record.id)?.resolve({ outcome: { outcome: "cancelled" } });
43474
+ for (const [id, pending] of live.pendingPerms) {
43475
+ pending.resolve({ outcome: { outcome: "cancelled" } });
43476
+ live.pendingPerms.delete(id);
43477
+ }
43478
+ }
43479
+ };
43480
+ }
43481
+ });
43482
+
41980
43483
  // packages/server/src/login-utility.ts
41981
43484
  import { randomBytes } from "node:crypto";
41982
43485
  import pty from "node-pty";
@@ -42154,27 +43657,27 @@ var init_login_utility = __esm({
42154
43657
  });
42155
43658
 
42156
43659
  // packages/server/src/backend/codex-app-server-diagnostics.ts
42157
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as statSync15 } from "node:fs";
42158
- import { join as join33 } from "node:path";
43660
+ import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync15, renameSync as renameSync8, statSync as statSync16 } from "node:fs";
43661
+ import { join as join35 } from "node:path";
42159
43662
  function codexDiagnosticLogPath(stateDir, projectId) {
42160
- return join33(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
43663
+ return join35(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
42161
43664
  }
42162
43665
  function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
42163
- const dir = join33(stateDir, "codex-app-server");
43666
+ const dir = join35(stateDir, "codex-app-server");
42164
43667
  const path3 = codexDiagnosticLogPath(stateDir, projectId);
42165
43668
  let ensured = false;
42166
43669
  return (event) => {
42167
43670
  if (event.event === "stderr") return;
42168
43671
  try {
42169
43672
  if (!ensured) {
42170
- mkdirSync14(dir, { recursive: true });
43673
+ mkdirSync15(dir, { recursive: true });
42171
43674
  ensured = true;
42172
43675
  }
42173
43676
  try {
42174
- if (statSync15(path3).size > MAX_BYTES2) renameSync8(path3, `${path3}.1`);
43677
+ if (statSync16(path3).size > MAX_BYTES2) renameSync8(path3, `${path3}.1`);
42175
43678
  } catch {
42176
43679
  }
42177
- appendFileSync3(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
43680
+ appendFileSync4(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
42178
43681
  `);
42179
43682
  } catch {
42180
43683
  }
@@ -42189,10 +43692,10 @@ var init_codex_app_server_diagnostics = __esm({
42189
43692
  });
42190
43693
 
42191
43694
  // packages/server/src/backend/stale-socket-sweep.ts
42192
- import { execFile as execFile8 } from "node:child_process";
43695
+ import { execFile as execFile9 } from "node:child_process";
42193
43696
  import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
42194
43697
  import { connect as connectSocket } from "node:net";
42195
- import { join as join34 } from "node:path";
43698
+ import { join as join36 } from "node:path";
42196
43699
  function parseLsofSocketNames(stdout) {
42197
43700
  const referenced = /* @__PURE__ */ new Set();
42198
43701
  for (const line of stdout.split("\n")) {
@@ -42209,7 +43712,7 @@ function sweepStaleSockets(options, deps = {}) {
42209
43712
  const keep = new Set(options.keep ?? []);
42210
43713
  let candidates;
42211
43714
  try {
42212
- candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join34(options.dir, name)).filter((path3) => !keep.has(path3));
43715
+ candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join36(options.dir, name)).filter((path3) => !keep.has(path3));
42213
43716
  } catch {
42214
43717
  return;
42215
43718
  }
@@ -42236,7 +43739,7 @@ var init_stale_socket_sweep = __esm({
42236
43739
  "packages/server/src/backend/stale-socket-sweep.ts"() {
42237
43740
  "use strict";
42238
43741
  defaultListReferenced = (prefix, done) => {
42239
- execFile8("lsof", ["-U", "-F", "n"], { timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => {
43742
+ execFile9("lsof", ["-U", "-F", "n"], { timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => {
42240
43743
  if (!stdout) return done(null);
42241
43744
  if (error && !stdout.includes(prefix)) return done(null);
42242
43745
  done(parseLsofSocketNames(stdout));
@@ -42259,8 +43762,8 @@ var init_stale_socket_sweep = __esm({
42259
43762
 
42260
43763
  // packages/server/src/orphan-reaper.ts
42261
43764
  import { execFileSync as execFileSync4 } from "node:child_process";
42262
- import { readFileSync as readFileSync21 } from "node:fs";
42263
- import { dirname as dirname14 } from "node:path";
43765
+ import { readFileSync as readFileSync22 } from "node:fs";
43766
+ import { dirname as dirname15 } from "node:path";
42264
43767
  function firstTokenBasename(command) {
42265
43768
  const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
42266
43769
  const slash = first.lastIndexOf("/");
@@ -42488,7 +43991,7 @@ function sweepOrphansOnce(deps = {}) {
42488
43991
  }
42489
43992
  function sweepStaleBrokerSockets() {
42490
43993
  try {
42491
- sweepStaleSockets({ dir: dirname14(claudeBrokerSocketPath("", "")), prefix: CLAUDE_BROKER_SOCKET_PREFIX });
43994
+ sweepStaleSockets({ dir: dirname15(claudeBrokerSocketPath("", "")), prefix: CLAUDE_BROKER_SOCKET_PREFIX });
42492
43995
  } catch {
42493
43996
  }
42494
43997
  }
@@ -42538,7 +44041,7 @@ var init_orphan_reaper = __esm({
42538
44041
  defaultExec = (file, args) => execFileSync4(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
42539
44042
  defaultReadEnv = (pid) => {
42540
44043
  try {
42541
- return readFileSync21(`/proc/${pid}/environ`, "utf8");
44044
+ return readFileSync22(`/proc/${pid}/environ`, "utf8");
42542
44045
  } catch {
42543
44046
  return null;
42544
44047
  }
@@ -42686,7 +44189,7 @@ var init_thread_hibernation = __esm({
42686
44189
  });
42687
44190
 
42688
44191
  // packages/server/src/context.ts
42689
- import { join as join35 } from "node:path";
44192
+ import { join as join37 } from "node:path";
42690
44193
  import { randomUUID as randomUUID15 } from "node:crypto";
42691
44194
  import { homedir as homedir15 } from "node:os";
42692
44195
  function reconcileSessions(storage) {
@@ -42713,6 +44216,9 @@ function partialContextCleanup(resources) {
42713
44216
  codexAppServer: createRetryableCleanup(async () => {
42714
44217
  await resources.codexAppServer?.shutdown();
42715
44218
  }),
44219
+ acpBridge: createRetryableCleanup(async () => {
44220
+ await resources.acpBridge?.shutdown();
44221
+ }),
42716
44222
  claudeBroker: createRetryableCleanup(async () => {
42717
44223
  resources.claudeBroker?.close();
42718
44224
  resources.claudeRuntimeIngest?.close();
@@ -42836,7 +44342,7 @@ async function createContext(opts = {}) {
42836
44342
  function createContextUnchecked(opts, resources) {
42837
44343
  const home = opts.home ?? homedir15();
42838
44344
  const project = opts.project ?? resolveProject();
42839
- const storage = createStorage(opts.database ?? join35(project.stateDir, "ui.db"), project.id);
44345
+ const storage = createStorage(opts.database ?? join37(project.stateDir, "ui.db"), project.id);
42840
44346
  resources.storage = storage;
42841
44347
  const bus = new Bus();
42842
44348
  const transcriptChange = new Emitter();
@@ -42911,7 +44417,8 @@ function createContextUnchecked(opts, resources) {
42911
44417
  };
42912
44418
  const claudeBackend = createClaudeBackend({ logDir: defaultLogDir(project), claudeBin: opts.claudeBin });
42913
44419
  const codexBackend = createCodexBackend({});
42914
- const backendFor = (kind) => kind === "codex" ? codexBackend : claudeBackend;
44420
+ const acpBackend = createAcpBackend({ stateDir: project.stateDir });
44421
+ const backendFor = (kind) => kind === "codex" ? codexBackend : kind === "acp" ? acpBackend : claudeBackend;
42915
44422
  const codexAppServer = codexAppServerBridgeEnabled() ? createCodexAppServerBridge({
42916
44423
  projectId: project.id,
42917
44424
  projectDir: project.dir,
@@ -42963,6 +44470,19 @@ function createContextUnchecked(opts, resources) {
42963
44470
  }
42964
44471
  void codexAppServer?.warmUp();
42965
44472
  opts.startup?.afterPhase?.("Codex app-server bridge");
44473
+ const acpBridge = createAcpBridge({
44474
+ projectId: project.id,
44475
+ stateDir: project.stateDir,
44476
+ frizzMcp: resolveFrizzMcp(frizzMcpTarget),
44477
+ interactions: storage.interactions,
44478
+ env: process.env,
44479
+ customAgents: () => getSettings(storage, home).acpAgents,
44480
+ onStatusChange: () => board?.refresh()
44481
+ });
44482
+ resources.acpBridge = acpBridge;
44483
+ contextUnsubscribers.push(storage.subscribeSessionLifecycle((event) => {
44484
+ acpBridge.releaseSession(event.previous.slug, event.previous.session_id, event.type === "replaced" ? "session-replaced" : "session-deleted");
44485
+ }));
42966
44486
  const claudeRuntimeIngest = claudeBrokerBridgeEnabled() ? createClaudeRuntimeIngest({ nudge: () => tailer.nudge?.() }) : void 0;
42967
44487
  resources.claudeRuntimeIngest = claudeRuntimeIngest;
42968
44488
  const claudeBroker = claudeBrokerBridgeEnabled() ? createClaudeAgentBrokerBridge({
@@ -43074,6 +44594,7 @@ function createContextUnchecked(opts, resources) {
43074
44594
  claudeBin: opts.claudeBin,
43075
44595
  backendFor,
43076
44596
  codexAppServer,
44597
+ acpBridge,
43077
44598
  claudeBroker,
43078
44599
  // Auth preflight (claude-auth plan, Slice A): Claude reads its local credential and confirms only
43079
44600
  // a positive signed-out against its CLI (readClaudePreflightAuth — the comment there records why
@@ -43113,6 +44634,21 @@ ${wakeDeliveryToken(deliveryId)}`;
43113
44634
  }
43114
44635
  return deliverCodexWake({ bridge, storage, cwd: project.dir, row, slug, deliveryMessage, deliveryId });
43115
44636
  }
44637
+ if (row?.backend === "acp") {
44638
+ return acpBridge.followUp({
44639
+ threadSlug: slug,
44640
+ sessionId: row.session_id,
44641
+ cwd: project.dir,
44642
+ agentId: row.acp_agent ?? "",
44643
+ modelId: acpModelIdFromModel(row.model),
44644
+ acpSessionId: row.agent_session_id,
44645
+ text: deliveryMessage,
44646
+ deliveryId
44647
+ }).then((r) => {
44648
+ if (r.acpSessionId !== row.agent_session_id) storage.setAgentSession(slug, r.acpSessionId);
44649
+ if (row.exited === 1) storage.setExitedIfCurrent(slug, row.session_id, row.runtime_generation ?? 0, false);
44650
+ });
44651
+ }
43116
44652
  if (row?.backend === "claude" && row.claude_runtime === "broker") {
43117
44653
  if (!claudeBroker) {
43118
44654
  process.stderr.write(`[frizz] claude-broker wake for ${slug} dropped: the session broker is unavailable
@@ -43178,6 +44714,7 @@ ${wakeDeliveryToken(deliveryId)}`;
43178
44714
  storage,
43179
44715
  interactions: storage.interactions,
43180
44716
  codexAppServer,
44717
+ acpBridge,
43181
44718
  claudeBroker,
43182
44719
  board,
43183
44720
  tailer,
@@ -43219,6 +44756,8 @@ var init_context = __esm({
43219
44756
  init_resume();
43220
44757
  init_claude();
43221
44758
  init_codex();
44759
+ init_acp_transcript();
44760
+ init_acp_bridge();
43222
44761
  init_auth_status();
43223
44762
  init_login_utility();
43224
44763
  init_usage_limit();
@@ -45895,8 +47434,8 @@ var init_server = __esm({
45895
47434
  });
45896
47435
 
45897
47436
  // packages/server/src/repair.ts
45898
- import { existsSync as existsSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "node:fs";
45899
- import { basename as basename7, dirname as dirname15, resolve as resolve8 } from "node:path";
47437
+ import { existsSync as existsSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync11 } from "node:fs";
47438
+ import { basename as basename7, dirname as dirname16, resolve as resolve8 } from "node:path";
45900
47439
  function quoteValue(v) {
45901
47440
  if (/^[\w./#:+-]+$/.test(v)) return v;
45902
47441
  return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -45908,10 +47447,10 @@ function deriveTitle(body, slug) {
45908
47447
  function repairThreadFile(frizzDir, file) {
45909
47448
  const root = resolve8(frizzDir);
45910
47449
  const abs = resolve8(root, file);
45911
- if (dirname15(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
47450
+ if (dirname16(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
45912
47451
  if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
45913
- if (!existsSync12(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
45914
- const content = readFileSync22(abs, "utf8");
47452
+ if (!existsSync13(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
47453
+ const content = readFileSync23(abs, "utf8");
45915
47454
  const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
45916
47455
  if (firstLine2?.trim() === "---") {
45917
47456
  throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
@@ -46104,7 +47643,7 @@ var init_repo_files = __esm({
46104
47643
  });
46105
47644
 
46106
47645
  // cc-worker/hooks/bash-background.mjs
46107
- import { readFileSync as readFileSync23, realpathSync as realpathSync10 } from "node:fs";
47646
+ import { readFileSync as readFileSync24, realpathSync as realpathSync10 } from "node:fs";
46108
47647
  import { basename as basename8 } from "node:path";
46109
47648
  import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
46110
47649
  function emit(obj) {
@@ -46124,8 +47663,8 @@ function withoutHeredocBodies(command) {
46124
47663
  return " ".repeat(line.length);
46125
47664
  }
46126
47665
  for (const match2 of line.matchAll(/<<-?\s*(?:'([^']+)'|"([^"]+)"|([A-Za-z_][A-Za-z0-9_-]*))/g)) {
46127
- const delimiter2 = match2[1] ?? match2[2] ?? match2[3];
46128
- if (delimiter2) pending.push(delimiter2);
47666
+ const delimiter3 = match2[1] ?? match2[2] ?? match2[3];
47667
+ if (delimiter3) pending.push(delimiter3);
46129
47668
  }
46130
47669
  active = pending.shift();
46131
47670
  return line;
@@ -46259,7 +47798,7 @@ var init_bash_background = __esm({
46259
47798
  if (isDirectHookExecution(process.argv[1], import.meta.url)) {
46260
47799
  try {
46261
47800
  const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
46262
- emit(evaluateBashBackgroundHook(JSON.parse(readFileSync23(0, "utf8")), env));
47801
+ emit(evaluateBashBackgroundHook(JSON.parse(readFileSync24(0, "utf8")), env));
46263
47802
  } catch {
46264
47803
  emit({});
46265
47804
  }
@@ -46268,10 +47807,10 @@ var init_bash_background = __esm({
46268
47807
  });
46269
47808
 
46270
47809
  // packages/server/src/transcript.ts
46271
- import { closeSync as closeSync13, existsSync as existsSync13, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync24, readSync as readSync7, renameSync as renameSync9, statSync as statSync16, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
47810
+ import { closeSync as closeSync13, existsSync as existsSync14, fstatSync as fstatSync3, mkdirSync as mkdirSync16, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync25, readSync as readSync7, renameSync as renameSync9, statSync as statSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
46272
47811
  import { createHash as createHash13 } from "node:crypto";
46273
- import { StringDecoder as StringDecoder4 } from "node:string_decoder";
46274
- import { join as join36 } from "node:path";
47812
+ import { StringDecoder as StringDecoder5 } from "node:string_decoder";
47813
+ import { join as join38 } from "node:path";
46275
47814
  import { homedir as homedir16 } from "node:os";
46276
47815
  function formatTokens(n) {
46277
47816
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
@@ -46851,14 +48390,14 @@ function pruneScreenshotCache() {
46851
48390
  if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
46852
48391
  const byMtime = entries2.map((n) => {
46853
48392
  try {
46854
- return { n, m: statSync16(join36(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
48393
+ return { n, m: statSync17(join38(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
46855
48394
  } catch {
46856
48395
  return { n, m: 0 };
46857
48396
  }
46858
48397
  }).sort((a, b) => b.m - a.m);
46859
48398
  for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
46860
48399
  try {
46861
- unlinkSync5(join36(SCREENSHOT_CACHE_DIR, n));
48400
+ unlinkSync5(join38(SCREENSHOT_CACHE_DIR, n));
46862
48401
  } catch {
46863
48402
  }
46864
48403
  }
@@ -46888,14 +48427,14 @@ function persistBase64Image(mediaType, data, idKey) {
46888
48427
  const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
46889
48428
  if (!ext || !data) return void 0;
46890
48429
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
46891
- const path3 = join36(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
48430
+ const path3 = join38(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
46892
48431
  try {
46893
- if (existsSync13(path3)) return path3;
48432
+ if (existsSync14(path3)) return path3;
46894
48433
  if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
46895
48434
  const buf = Buffer.from(data, "base64");
46896
48435
  if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
46897
- mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
46898
- const tmp = join36(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
48436
+ mkdirSync16(SCREENSHOT_CACHE_DIR, { recursive: true });
48437
+ const tmp = join38(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
46899
48438
  writeFileSync12(tmp, buf);
46900
48439
  renameSync9(tmp, path3);
46901
48440
  pruneScreenshotCache();
@@ -46910,14 +48449,14 @@ function persistSentFile(srcPath, idKey) {
46910
48449
  const outExt = ext === "jpeg" ? "jpg" : ext;
46911
48450
  try {
46912
48451
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
46913
- const dest = join36(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
46914
- if (existsSync13(dest)) return dest;
46915
- const size = statSync16(srcPath).size;
48452
+ const dest = join38(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
48453
+ if (existsSync14(dest)) return dest;
48454
+ const size = statSync17(srcPath).size;
46916
48455
  if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
46917
- const buf = readFileSync24(srcPath);
48456
+ const buf = readFileSync25(srcPath);
46918
48457
  if (!looksLikeImage(buf, outExt)) return void 0;
46919
- mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
46920
- const tmp = join36(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
48458
+ mkdirSync16(SCREENSHOT_CACHE_DIR, { recursive: true });
48459
+ const tmp = join38(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
46921
48460
  writeFileSync12(tmp, buf);
46922
48461
  renameSync9(tmp, dest);
46923
48462
  pruneScreenshotCache();
@@ -46935,7 +48474,7 @@ function viewImageCall(path3, idKey) {
46935
48474
  ...image ? { outputImage: image } : {}
46936
48475
  };
46937
48476
  }
46938
- function toolResultText3(content) {
48477
+ function toolResultText4(content) {
46939
48478
  if (typeof content === "string") return content.trim() || null;
46940
48479
  if (Array.isArray(content)) {
46941
48480
  const joined = content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
@@ -46966,7 +48505,7 @@ function attachToolResults(rec, pending, backgroundShells, backgroundTaskIds, ch
46966
48505
  const ackAgentId = typeof rec.toolUseResult?.agentId === "string" ? rec.toolUseResult.agentId.trim() : "";
46967
48506
  const dispatchId = entry.calls.find((c) => typeof c.agentId === "string" && c.agentId)?.agentId;
46968
48507
  if (ackAgentId && dispatchId) childDispatchIds.set(ackAgentId, dispatchId);
46969
- const text2 = toolResultText3(b.content);
48508
+ const text2 = toolResultText4(b.content);
46970
48509
  if (text2 && entry.name === "TaskList") {
46971
48510
  const todos = parseTodoListResult(text2);
46972
48511
  if (todos) for (const call of entry.calls) call.todos = todos;
@@ -47319,7 +48858,7 @@ function retainedFoldEntry(path3, identityPrefix, fileId, size) {
47319
48858
  }
47320
48859
  const hit = entry !== void 0;
47321
48860
  if (!entry) {
47322
- entry = { identityPrefix, fold: createTranscriptFold(identityPrefix), bytesRead: 0, decoder: new StringDecoder4("utf8"), fileId };
48861
+ entry = { identityPrefix, fold: createTranscriptFold(identityPrefix), bytesRead: 0, decoder: new StringDecoder5("utf8"), fileId };
47323
48862
  } else {
47324
48863
  transcriptCache.delete(path3);
47325
48864
  }
@@ -47355,7 +48894,7 @@ function readTranscript(project, sessionId) {
47355
48894
  }
47356
48895
  function verifyIncrementalParse(path3, identityPrefix, incremental) {
47357
48896
  try {
47358
- const fresh = parseTranscript(readFileSync24(path3, "utf8"), identityPrefix);
48897
+ const fresh = parseTranscript(readFileSync25(path3, "utf8"), identityPrefix);
47359
48898
  const a = JSON.stringify(incremental);
47360
48899
  const b = JSON.stringify(fresh);
47361
48900
  if (a !== b) {
@@ -47375,16 +48914,16 @@ function verifyIncrementalParse(path3, identityPrefix, incremental) {
47375
48914
  }
47376
48915
  }
47377
48916
  function logDirOf(project) {
47378
- return join36(homedir16(), ".claude", "projects", project.cwdSlug);
48917
+ return join38(homedir16(), ".claude", "projects", project.cwdSlug);
47379
48918
  }
47380
48919
  function resolveTranscriptPath(project, sessionId) {
47381
- const path3 = join36(logDirOf(project), `${sessionId}.jsonl`);
48920
+ const path3 = join38(logDirOf(project), `${sessionId}.jsonl`);
47382
48921
  try {
47383
- if (statSync16(path3).size > 0) return path3;
48922
+ if (statSync17(path3).size > 0) return path3;
47384
48923
  } catch {
47385
48924
  }
47386
48925
  const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
47387
- return stranded ? join36(stranded, `${sessionId}.jsonl`) : path3;
48926
+ return stranded ? join38(stranded, `${sessionId}.jsonl`) : path3;
47388
48927
  }
47389
48928
  function projectCodexTranscript(raw2, identityPrefix = "codex") {
47390
48929
  const out = [];
@@ -47759,7 +49298,7 @@ function codexDirectToolCall(name, obj, callId) {
47759
49298
  detail: strField(obj.task_name) ?? "sub-agent",
47760
49299
  subagentType: codexAgentCell(obj),
47761
49300
  agentId: callId,
47762
- input: compactFields(obj, ["agent_type", "fork_context", "fork_turns", "service_tier"])
49301
+ prompt: typeof obj.message === "string" && obj.message.trim() && strField(obj.message) !== ENCRYPTED_PAYLOAD ? capAgentPrompt(obj.message) : void 0
47763
49302
  };
47764
49303
  case "send_message":
47765
49304
  return codexPeerMessageCall("Send message", target, obj);
@@ -47871,14 +49410,6 @@ function codexAgentCell(obj) {
47871
49410
  const effort = strField(obj.reasoning_effort);
47872
49411
  return model && effort ? `${model}/${effort}` : model ?? effort;
47873
49412
  }
47874
- function compactFields(obj, keys) {
47875
- const projected = {};
47876
- for (const key of keys) {
47877
- const value = obj[key];
47878
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") projected[key] = value;
47879
- }
47880
- return Object.keys(projected).length ? renderToolInput(projected) : void 0;
47881
- }
47882
49413
  function formatCompactDuration(ms) {
47883
49414
  if (ms < 1e3) return `${Math.round(ms)}ms`;
47884
49415
  if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
@@ -48434,9 +49965,9 @@ function defaultCodexBackend() {
48434
49965
  function sourceForThread(project, storage, slug, backendFor) {
48435
49966
  const row = storage.getSession(slug);
48436
49967
  if (row) {
48437
- const backend = row.backend === "codex" ? "codex" : "claude";
48438
- const nativeId = backend === "codex" ? row.agent_session_id ?? row.session_id : row.transcript_id ?? row.session_id;
48439
- const path3 = backend === "codex" ? (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(nativeId) : resolveTranscriptPath(project, nativeId);
49968
+ const backend = row.backend === "codex" ? "codex" : row.backend === "acp" ? "acp" : "claude";
49969
+ const nativeId = backend === "codex" ? row.agent_session_id ?? row.session_id : backend === "acp" ? row.session_id : row.transcript_id ?? row.session_id;
49970
+ const path3 = backend === "codex" ? (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(nativeId) : backend === "acp" ? backendFor?.("acp")?.transcriptPath(nativeId) : resolveTranscriptPath(project, nativeId);
48440
49971
  if (!path3) return void 0;
48441
49972
  return {
48442
49973
  slug,
@@ -48449,7 +49980,7 @@ function sourceForThread(project, storage, slug, backendFor) {
48449
49980
  }
48450
49981
  if (!FOREIGN_SESSION_ID_RE.test(slug)) return void 0;
48451
49982
  const claudePath = resolveTranscriptPath(project, slug);
48452
- if (existsSync13(claudePath)) {
49983
+ if (existsSync14(claudePath)) {
48453
49984
  return { slug, sessionId: slug, nativeId: slug, backend: "claude", runtimeGeneration: 0, path: claudePath };
48454
49985
  }
48455
49986
  const rollout = (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(slug);
@@ -48543,6 +50074,7 @@ function fixedSnapshot(source) {
48543
50074
  function projectSnapshot(snapshot) {
48544
50075
  const prefix = `${snapshot.backend}:${snapshot.nativeId}`;
48545
50076
  if (snapshot.backend === "codex") return retireStaleQueuedBubbles(projectCodexTranscript(snapshot.raw, prefix));
50077
+ if (snapshot.backend === "acp") return retireStaleQueuedBubbles(projectAcpTranscript(snapshot.raw, prefix));
48546
50078
  const { entry } = retainedFoldEntry(snapshot.path, prefix, snapshot.fileKey, snapshot.size);
48547
50079
  ingestBounded(entry, snapshot.size, (from, length) => snapshot.readRange(from, length));
48548
50080
  return retireStaleQueuedBubbles([...entry.fold.allMessages()]);
@@ -48599,7 +50131,7 @@ function decodeTranscriptCursor(cursor) {
48599
50131
  }
48600
50132
  const p = value;
48601
50133
  const validText2 = (s, max) => typeof s === "string" && s.length > 0 && s.length <= max && !/[\0\r\n]/.test(s);
48602
- if (!p || p.v !== 1 || !validText2(p.slug, 256) || !validText2(p.sessionId, 256) || !validText2(p.nativeId, 256) || p.backend !== "claude" && p.backend !== "codex" || !Number.isSafeInteger(p.runtimeGeneration) || (p.runtimeGeneration ?? -1) < 0 || !validText2(p.fileKey, 256) || !Number.isSafeInteger(p.snapshotBytes) || (p.snapshotBytes ?? -1) < 0 || !validText2(p.prefixDigest, 128) || !validText2(p.anchorSourceId, 768)) throw new Error("invalid transcript cursor");
50134
+ if (!p || p.v !== 1 || !validText2(p.slug, 256) || !validText2(p.sessionId, 256) || !validText2(p.nativeId, 256) || p.backend !== "claude" && p.backend !== "codex" && p.backend !== "acp" || !Number.isSafeInteger(p.runtimeGeneration) || (p.runtimeGeneration ?? -1) < 0 || !validText2(p.fileKey, 256) || !Number.isSafeInteger(p.snapshotBytes) || (p.snapshotBytes ?? -1) < 0 || !validText2(p.prefixDigest, 128) || !validText2(p.anchorSourceId, 768)) throw new Error("invalid transcript cursor");
48603
50135
  return p;
48604
50136
  }
48605
50137
  function messageBytes(message) {
@@ -48715,7 +50247,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
48715
50247
  }
48716
50248
  function readCodexTranscriptFile(absPath, nativeId = absPath) {
48717
50249
  try {
48718
- return parseCodexTranscript(readFileSync24(absPath, "utf8"), `codex:${nativeId}`);
50250
+ return parseCodexTranscript(readFileSync25(absPath, "utf8"), `codex:${nativeId}`);
48719
50251
  } catch {
48720
50252
  return [];
48721
50253
  }
@@ -48723,6 +50255,11 @@ function readCodexTranscriptFile(absPath, nativeId = absPath) {
48723
50255
  function readThreadTranscript(project, storage, slug, backendFor) {
48724
50256
  const row = storage.getSession(slug);
48725
50257
  if (row) {
50258
+ if (row.backend === "acp") {
50259
+ const path3 = backendFor?.("acp")?.transcriptPath(row.session_id);
50260
+ const acpLedger = parseDeliveryLedger(row.delivery_ledger);
50261
+ return projectDeliveryLedger(path3 ? readAcpTranscriptFile(path3, row.session_id) : [], acpLedger);
50262
+ }
48726
50263
  if (row.backend === "codex") {
48727
50264
  const backend = backendFor?.("codex") ?? defaultCodexBackend();
48728
50265
  const nativeId = row.agent_session_id ?? row.session_id;
@@ -48813,7 +50350,7 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
48813
50350
  function readTranscriptFile(absPath) {
48814
50351
  try {
48815
50352
  const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
48816
- return parseTranscript(readFileSync24(absPath, "utf8"), `claude-file:${pathKey}`);
50353
+ return parseTranscript(readFileSync25(absPath, "utf8"), `claude-file:${pathKey}`);
48817
50354
  } catch {
48818
50355
  return [];
48819
50356
  }
@@ -48829,6 +50366,7 @@ var init_transcript = __esm({
48829
50366
  init_delivery_marker();
48830
50367
  init_completion_relay();
48831
50368
  init_codex();
50369
+ init_acp_transcript();
48832
50370
  init_discover();
48833
50371
  init_tailer();
48834
50372
  init_credential_redaction();
@@ -48889,7 +50427,7 @@ var init_transcript = __esm({
48889
50427
  });
48890
50428
 
48891
50429
  // packages/server/src/open-external.ts
48892
- import { spawn as spawn6 } from "node:child_process";
50430
+ import { spawn as spawn7 } from "node:child_process";
48893
50431
  function validateExternalUrl(raw2) {
48894
50432
  let parsed;
48895
50433
  try {
@@ -48911,7 +50449,7 @@ async function openExternalUrl(raw2, options = {}) {
48911
50449
  const v = validateExternalUrl(raw2);
48912
50450
  if (!v.ok) throw new Error(v.reason);
48913
50451
  const spec = externalUrlOpenCommand(v.url, options.platform);
48914
- const child = (options.spawn ?? spawn6)(spec.command, [...spec.args], { detached: true, stdio: "ignore", windowsHide: true });
50452
+ const child = (options.spawn ?? spawn7)(spec.command, [...spec.args], { detached: true, stdio: "ignore", windowsHide: true });
48915
50453
  try {
48916
50454
  await awaitOpenerStart(child, spec.command);
48917
50455
  return { opened: true };
@@ -49210,7 +50748,7 @@ var init_github_hovercard = __esm({
49210
50748
  });
49211
50749
 
49212
50750
  // packages/server/src/backend/account-actions.ts
49213
- import { execFile as execFile9 } from "node:child_process";
50751
+ import { execFile as execFile10 } from "node:child_process";
49214
50752
  import { promisify as promisify8 } from "node:util";
49215
50753
  function liveThreadsForBackend(threads, backend) {
49216
50754
  let count = 0;
@@ -49223,6 +50761,7 @@ function liveThreadsForBackend(threads, backend) {
49223
50761
  return count;
49224
50762
  }
49225
50763
  async function runProviderLogout(opts) {
50764
+ if (opts.backend === "acp") return { status: "failed", detail: "Log out with the agent's own CLI; Frizz holds no credential for an ACP agent.", auth: "unknown" };
49226
50765
  if (opts.liveThreads > 0) {
49227
50766
  return { status: "blocked", activeThreads: opts.liveThreads, auth: (await readAuthSnapshot({ claudeBin: opts.claudeBin }))[opts.backend] };
49228
50767
  }
@@ -49245,7 +50784,7 @@ var init_account_actions = __esm({
49245
50784
  "packages/server/src/backend/account-actions.ts"() {
49246
50785
  "use strict";
49247
50786
  init_auth_status();
49248
- execFileAsync6 = promisify8(execFile9);
50787
+ execFileAsync6 = promisify8(execFile10);
49249
50788
  LIVE_RUNTIMES = /* @__PURE__ */ new Set(["spawning", "running", "perm-prompt"]);
49250
50789
  }
49251
50790
  });
@@ -49378,12 +50917,12 @@ var init_background_shell_output = __esm({
49378
50917
  });
49379
50918
 
49380
50919
  // packages/server/src/image-header.ts
49381
- import { closeSync as closeSync15, openSync as openSync16, readSync as readSync9, statSync as statSync17 } from "node:fs";
50920
+ import { closeSync as closeSync15, openSync as openSync16, readSync as readSync9, statSync as statSync18 } from "node:fs";
49382
50921
  import { extname } from "node:path";
49383
50922
  function readHead3(path3, budget) {
49384
50923
  let fd;
49385
50924
  try {
49386
- const size = statSync17(path3).size;
50925
+ const size = statSync18(path3).size;
49387
50926
  if (size === 0) return void 0;
49388
50927
  const buffer = Buffer.alloc(Math.min(size, budget));
49389
50928
  fd = openSync16(path3, "r");
@@ -49520,7 +51059,7 @@ var init_image_header = __esm({
49520
51059
  });
49521
51060
 
49522
51061
  // packages/server/src/directory-picker.ts
49523
- import { execFile as execFile10 } from "node:child_process";
51062
+ import { execFile as execFile11 } from "node:child_process";
49524
51063
  import { promisify as promisify9 } from "node:util";
49525
51064
  async function pickImageFile(startIn, prompt = "Choose an image for this project", platform3 = process.platform) {
49526
51065
  if (platform3 === "darwin") {
@@ -49602,20 +51141,22 @@ var run2, PICKER_TIMEOUT_MS, OSASCRIPT;
49602
51141
  var init_directory_picker = __esm({
49603
51142
  "packages/server/src/directory-picker.ts"() {
49604
51143
  "use strict";
49605
- run2 = promisify9(execFile10);
51144
+ run2 = promisify9(execFile11);
49606
51145
  PICKER_TIMEOUT_MS = 5 * 6e4;
49607
51146
  OSASCRIPT = (prompt) => `POSIX path of (choose folder with prompt ${JSON.stringify(prompt)})`;
49608
51147
  }
49609
51148
  });
49610
51149
 
49611
51150
  // packages/server/src/router.ts
49612
- import { readFileSync as readFileSync25, statSync as statSync18 } from "node:fs";
49613
- import { join as join37, resolve as resolve9 } from "node:path";
51151
+ import { readFileSync as readFileSync26, statSync as statSync19 } from "node:fs";
51152
+ import { join as join39, resolve as resolve9 } from "node:path";
49614
51153
  import { randomUUID as randomUUID16 } from "node:crypto";
49615
- import { basename as basename9, dirname as dirname16 } from "node:path";
49616
- import { existsSync as existsSync14, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
51154
+ import { basename as basename9, dirname as dirname17 } from "node:path";
51155
+ import { existsSync as existsSync15, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
49617
51156
  import { homedir as homedir17 } from "node:os";
49618
51157
  function validateGithubDispatchProfile(input) {
51158
+ if (input.backend === "acp") return;
51159
+ if (input.effort === void 0) throw new Error(`Unsupported ${input.backend} model/effort pair: ${input.model} / (no effort)`);
49619
51160
  validateThreadProfile(input.backend, input.model, input.effort);
49620
51161
  }
49621
51162
  function githubDispatcherRequest(input, item) {
@@ -49638,7 +51179,11 @@ function isAppServerCodexRow(row) {
49638
51179
  function appServerCodexTurnLive(codex, row) {
49639
51180
  return codex?.turnLiveness(row.slug, row.session_id)?.bridgeTurn === true;
49640
51181
  }
49641
- async function stopThreadRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker) {
51182
+ async function stopThreadRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
51183
+ if (row.backend === "acp") {
51184
+ if (!acp?.turnLiveness(row.slug, row.session_id)?.turnActive) return "absent";
51185
+ return (await acp.interruptTurn(row.slug, row.session_id)).interrupted ? "stopped" : "absent";
51186
+ }
49642
51187
  if (isAppServerCodexRow(row)) {
49643
51188
  if (!appServerCodexTurnLive(codex, row)) return "absent";
49644
51189
  if (!codex) throw new Error("The Codex app-server is unavailable; nothing was stopped");
@@ -49679,9 +51224,9 @@ function stopRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator)
49679
51224
  }
49680
51225
  return "stopped";
49681
51226
  }
49682
- async function stopRuntimeBySlug(storage, slug, runtime = cachedLivenessTerminator, codex, claudeBroker) {
51227
+ async function stopRuntimeBySlug(storage, slug, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
49683
51228
  const row = storage.getSession(slug);
49684
- if (row) return { outcome: await stopThreadRuntime(storage, row, runtime, codex, claudeBroker), row };
51229
+ if (row) return { outcome: await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp), row };
49685
51230
  if (storage.getAdoptionClaim(slug)) throw new Error("An adoption attempt is in progress; nothing was stopped");
49686
51231
  throw new Error("No registered runtime identity is available; nothing was stopped");
49687
51232
  }
@@ -49713,18 +51258,18 @@ function cutOffHold(telemetry) {
49713
51258
  if (telemetry?.turn !== "in-flight") return void 0;
49714
51259
  return { turnInFlight: true, cutOff: true, unobservable: false, subAgents: [], subAgentCount: 0, bgShells: [], bgShellCount: 0 };
49715
51260
  }
49716
- async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker) {
51261
+ async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker, acp) {
49717
51262
  const binding = adoptionRuntimeBinding(storage, row);
49718
51263
  if (binding.kind === "conflict") {
49719
51264
  throw new Error("This thread has a competing adoption attempt; nothing was changed");
49720
51265
  }
49721
51266
  const appServerCodex = isAppServerCodexRow(row);
49722
51267
  const brokerClaude = isBrokerClaudeRow(row);
49723
- const live = brokerClaude ? claudeBroker?.isDaemonAlive(row.session_id) ?? false : appServerCodex ? appServerCodexTurnLive(codex, row) : false;
51268
+ const live = brokerClaude ? claudeBroker?.isDaemonAlive(row.session_id) ?? false : appServerCodex ? appServerCodexTurnLive(codex, row) : row.backend === "acp" ? acp?.turnLiveness(row.slug, row.session_id)?.turnActive ?? false : false;
49724
51269
  const hold = terminateLive ? void 0 : live ? completionConfirmationHold(telemetry) : cutOffHold(telemetry);
49725
51270
  if (hold) return { needsConfirmation: true, hold };
49726
51271
  if (live) {
49727
- await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
51272
+ await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp);
49728
51273
  if (!appServerCodex && !brokerClaude && binding.kind === "unbound" && runtime.isLive(row.slug)) {
49729
51274
  throw new Error("The session could not be confirmed stopped; it was not marked done");
49730
51275
  }
@@ -49735,7 +51280,7 @@ async function completeRegisteredThread(storage, row, terminateLive, runtime = c
49735
51280
  }
49736
51281
  return { needsConfirmation: false };
49737
51282
  }
49738
- async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker) {
51283
+ async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
49739
51284
  const binding = adoptionRuntimeBinding(storage, row);
49740
51285
  if (binding.kind === "conflict") {
49741
51286
  throw new Error("This thread changed while it was being dismissed; nothing was removed");
@@ -49745,7 +51290,7 @@ async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLive
49745
51290
  runtimeGeneration: row.runtime_generation ?? 0,
49746
51291
  adoptionAttemptToken: binding.kind === "bound" ? binding.claim.attempt_token : null
49747
51292
  };
49748
- await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
51293
+ await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp);
49749
51294
  const forgotten = storage.forgetSessionIfCurrent(row.slug, expected);
49750
51295
  if (!forgotten) {
49751
51296
  throw new Error("This thread resumed or was replaced while it was being dismissed; the new worker was preserved");
@@ -49777,11 +51322,11 @@ function projectCard(entry, stale) {
49777
51322
  function addProjectAtPath(input, home = homedir17()) {
49778
51323
  const typed = input.trim();
49779
51324
  if (!typed) throw new Error("Enter a folder path.");
49780
- const expanded = typed === "~" || typed.startsWith("~/") ? join37(home, typed.slice(1)) : typed;
51325
+ const expanded = typed === "~" || typed.startsWith("~/") ? join39(home, typed.slice(1)) : typed;
49781
51326
  const absolute = resolve9(expanded);
49782
51327
  let stats;
49783
51328
  try {
49784
- stats = statSync18(absolute);
51329
+ stats = statSync19(absolute);
49785
51330
  } catch {
49786
51331
  throw new Error(`No folder at ${absolute}`);
49787
51332
  }
@@ -49806,7 +51351,7 @@ function storeProjectIcon(id, name, bytes) {
49806
51351
  if (!entry) throw new Error("No such project.");
49807
51352
  if (bytes.length === 0) throw new Error("That file is empty.");
49808
51353
  const path3 = customIconPath(id, `.${extension2}`);
49809
- mkdirSync16(dirname16(path3), { recursive: true });
51354
+ mkdirSync17(dirname17(path3), { recursive: true });
49810
51355
  writeFileSync13(path3, bytes);
49811
51356
  if (!imageDimensions(path3)) {
49812
51357
  rmSync10(path3, { force: true });
@@ -49823,7 +51368,7 @@ function storeProjectIcon(id, name, bytes) {
49823
51368
  function setProjectIconFromFile(id, file) {
49824
51369
  let bytes;
49825
51370
  try {
49826
- bytes = readFileSync25(file);
51371
+ bytes = readFileSync26(file);
49827
51372
  } catch {
49828
51373
  throw new Error("That file could not be read.");
49829
51374
  }
@@ -49856,7 +51401,7 @@ function mergeSubAgentSteers(messages, steers) {
49856
51401
  return merged;
49857
51402
  }
49858
51403
  function createRouter(ctx) {
49859
- const frizzDir = join37(ctx.project.dir, ".frizz");
51404
+ const frizzDir = join39(ctx.project.dir, ".frizz");
49860
51405
  const openRoots = openableFileRoots(ctx.project);
49861
51406
  function isAutoTitledSession(slug) {
49862
51407
  return ctx.storage.getSession(slug)?.title_auto === 1;
@@ -50483,6 +52028,28 @@ function createRouter(ctx) {
50483
52028
  ${gapNote}` : input.message;
50484
52029
  if (row) reopenArchivedThreadForFollowUp(ctx, row);
50485
52030
  if (row) wakeParkedThreadForFollowUp(ctx, row);
52031
+ if (row?.backend === "acp") {
52032
+ const bridge = ctx.acpBridge;
52033
+ if (!bridge) throw new Error("The ACP bridge is unavailable; cannot deliver this follow-up");
52034
+ const result = await bridge.followUp({
52035
+ threadSlug: input.slug,
52036
+ sessionId: row.session_id,
52037
+ cwd: ctx.project.dir,
52038
+ agentId: row.acp_agent ?? "",
52039
+ modelId: acpModelIdFromModel(row.model),
52040
+ acpSessionId: row.agent_session_id,
52041
+ text: messageForWorker,
52042
+ ...input.deliveryId ? { deliveryId: input.deliveryId } : {}
52043
+ });
52044
+ if (result.acpSessionId !== row.agent_session_id) ctx.storage.setAgentSession(input.slug, result.acpSessionId);
52045
+ if (row.exited === 1) ctx.storage.setExitedIfCurrent(input.slug, row.session_id, row.runtime_generation ?? 0, false);
52046
+ if (input.deliveryId) {
52047
+ appendDelivery(ctx.storage, input.slug, { id: input.deliveryId, text: input.message, state: result.state === "queued" ? "enqueued" : "delivered" });
52048
+ ctx.transcriptChange.emit([input.slug]);
52049
+ }
52050
+ ctx.board.refresh();
52051
+ return;
52052
+ }
50486
52053
  if (row?.backend === "codex") {
50487
52054
  const bridge = ctx.codexAppServer;
50488
52055
  if (!bridge) throw new Error("Codex app-server is unavailable; cannot deliver this follow-up");
@@ -50668,6 +52235,7 @@ ${gapNote}` : input.message;
50668
52235
  const thread = (await ctx.board.snapshot()).threads.find((t) => t.id === input.slug);
50669
52236
  if (!thread || thread.foreign || thread.kind !== "session") throw new Error(`thread ${input.slug} is not editable`);
50670
52237
  const permRow = ctx.storage.getSession(input.slug);
52238
+ if (permRow?.backend === "acp") throw new Error("An ACP agent enforces its own permissions; Frizz has no mode to set on it");
50671
52239
  if (permRow?.backend === "codex") {
50672
52240
  ctx.storage.setPermissionMode(input.slug, input.permissionMode);
50673
52241
  ctx.board.refresh();
@@ -50713,7 +52281,9 @@ ${gapNote}` : input.message;
50713
52281
  const row = ctx.storage.getSession(input.slug);
50714
52282
  if (!row) throw new Error(`thread ${input.slug} has no session to ask for skills`);
50715
52283
  let skills;
50716
- if (row.backend === "codex") {
52284
+ if (row.backend === "acp") {
52285
+ skills = [];
52286
+ } else if (row.backend === "codex") {
50717
52287
  if (!isAppServerCodexRow(row) || !ctx.codexAppServer) {
50718
52288
  throw new Error("This Codex thread has no app-server session to ask for skills");
50719
52289
  }
@@ -50734,6 +52304,7 @@ ${gapNote}` : input.message;
50734
52304
  const thread = (await ctx.board.snapshot()).threads.find((candidate) => candidate.id === input.slug);
50735
52305
  if (!thread || thread.foreign || thread.kind !== "session") throw new Error(`thread ${input.slug} is not editable`);
50736
52306
  const profRow = ctx.storage.getSession(input.slug);
52307
+ if (profRow?.backend === "acp") throw new Error("An ACP agent chooses its own model; Frizz offers no profile for it yet");
50737
52308
  if (profRow?.backend === "codex") {
50738
52309
  ctx.storage.setProfile(input.slug, input.model, input.effort);
50739
52310
  ctx.board.refresh();
@@ -50806,7 +52377,8 @@ ${gapNote}` : input.message;
50806
52377
  cachedLivenessTerminator,
50807
52378
  ctx.tailer.get(input.slug),
50808
52379
  ctx.codexAppServer,
50809
- ctx.claudeBroker
52380
+ ctx.claudeBroker,
52381
+ ctx.acpBridge
50810
52382
  );
50811
52383
  if (!result.needsConfirmation) ctx.board.refresh();
50812
52384
  return result;
@@ -51400,7 +52972,7 @@ Say which way you went and why in your write-up, so the human can course-correct
51400
52972
  if (t && t.runtime !== "exited") {
51401
52973
  throw new Error("only a stalled or exited session can be dismissed \u2014 archive a live one instead");
51402
52974
  }
51403
- await stopAndForgetRegisteredRuntime(ctx.storage, row, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
52975
+ await stopAndForgetRegisteredRuntime(ctx.storage, row, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
51404
52976
  ctx.tailer.forget(input.slug);
51405
52977
  ctx.board.refresh();
51406
52978
  }
@@ -51518,7 +53090,7 @@ Say which way you went and why in your write-up, so the human can course-correct
51518
53090
  handler: async ({ input }) => {
51519
53091
  assertLegacyMutationAllowed(input.slug);
51520
53092
  if (input.status === "dismissed") {
51521
- const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
53093
+ const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
51522
53094
  if (stopped.row && !ctx.storage.setExitedIfCurrent(
51523
53095
  stopped.row.slug,
51524
53096
  stopped.row.session_id,
@@ -51622,7 +53194,7 @@ Say which way you went and why in your write-up, so the human can course-correct
51622
53194
  killAgent: mutation({
51623
53195
  input: SlugInput,
51624
53196
  handler: async ({ input }) => {
51625
- const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
53197
+ const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
51626
53198
  if (stopped.row && !ctx.storage.setExitedIfCurrent(
51627
53199
  stopped.row.slug,
51628
53200
  stopped.row.session_id,
@@ -51641,6 +53213,20 @@ Say which way you went and why in your write-up, so the human can course-correct
51641
53213
  output: external_exports.array(CodexModel),
51642
53214
  handler: async () => readCodexModels()
51643
53215
  }),
53216
+ // The ACP agents Frizz knows how to launch, with `available` for the ones on this machine's PATH.
53217
+ // The composer lists the available ones as `acp:<id>` models (plans/acp-backend.md, decision 9).
53218
+ acpAgents: query({
53219
+ output: external_exports.array(AcpAgent),
53220
+ handler: async () => listAcpAgents(ctx.getSettings().acpAgents).map((a) => ({ id: a.id, label: a.label, command: a.command, available: a.bin !== void 0 }))
53221
+ }),
53222
+ // The models one ACP agent advertises — read by opening a throwaway session in the project dir
53223
+ // (cached in the bridge), so it is a separate query the composer asks only once a model picker
53224
+ // needs it, never on every board load.
53225
+ acpAgentModels: query({
53226
+ input: AcpAgentModelsInput,
53227
+ output: AcpAgentModels,
53228
+ handler: async ({ input }) => ctx.acpBridge ? ctx.acpBridge.agentModels(input.agentId, ctx.project.dir, { refresh: input.refresh === true }) : { agentId: input.agentId, models: [], error: "The ACP bridge is unavailable", probedAt: (/* @__PURE__ */ new Date()).toISOString() }
53229
+ }),
51644
53230
  // Provider subscription quota (5h + weekly rate-limit windows) for the sidebar status bar. Codex
51645
53231
  // reads live from the app-server's `account/rateLimits/read`, falling back to the rollout JSONL
51646
53232
  // frizz already tails; Claude delegates to Claude Code's own non-interactive `/usage` command.
@@ -51690,7 +53276,7 @@ Say which way you went and why in your write-up, so the human can course-correct
51690
53276
  const { state, backend } = ctx.loginUtility.status(input.attemptId);
51691
53277
  const auth = await readAuthSnapshot({ claudeBin: ctx.claudeBin });
51692
53278
  if (state === "exited") ctx.loginUtility.cancel(input.attemptId);
51693
- return { state, auth: auth[backend ?? "claude"] };
53279
+ return { state, auth: backend === "acp" ? "unknown" : auth[backend ?? "claude"] };
51694
53280
  }
51695
53281
  }),
51696
53282
  accountLoginCancel: mutation({
@@ -51859,7 +53445,7 @@ Say which way you went and why in your write-up, so the human can course-correct
51859
53445
  }
51860
53446
  const updated = clearProjectIcon(input.id);
51861
53447
  if (!updated) throw new Error("No such project.");
51862
- return projectCard(updated, !existsSync14(updated.path));
53448
+ return projectCard(updated, !existsSync15(updated.path));
51863
53449
  }
51864
53450
  }),
51865
53451
  /**
@@ -51928,6 +53514,16 @@ Say which way you went and why in your write-up, so the human can course-correct
51928
53514
  output: Settings,
51929
53515
  handler: async ({ input }) => ctx.setSettings(input)
51930
53516
  }),
53517
+ // Context controls live in the new-thread picker. Patch only the chosen field against the
53518
+ // latest settings, synchronously, rather than replacing unrelated settings from a stale drawer.
53519
+ contextWindowSet: mutation({
53520
+ input: external_exports.object({ backend: external_exports.enum(["claude", "codex"]), tokens: external_exports.number().int().positive().nullable() }),
53521
+ output: Settings,
53522
+ handler: async ({ input }) => ctx.setSettings({
53523
+ ...ctx.getSettings(),
53524
+ [input.backend === "claude" ? "autoCompactWindow" : "codexContextWindow"]: input.tokens ?? void 0
53525
+ })
53526
+ }),
51931
53527
  // Clear the stored settings blob so defaults (incl. the shipped default preamble) apply again.
51932
53528
  settingsReset: mutation({
51933
53529
  input: external_exports.object({}),
@@ -52032,6 +53628,7 @@ var init_router5 = __esm({
52032
53628
  init_server();
52033
53629
  init_src();
52034
53630
  init_context();
53631
+ init_acp_agents();
52035
53632
  init_storage();
52036
53633
  init_usage_limit();
52037
53634
  init_board();
@@ -52219,7 +53816,7 @@ var init_local_origin = __esm({
52219
53816
  });
52220
53817
 
52221
53818
  // packages/server/src/compression.ts
52222
- import { brotliCompressSync, gzipSync, constants as constants2 } from "node:zlib";
53819
+ import { brotliCompressSync, gzipSync, constants as constants3 } from "node:zlib";
52223
53820
  function negotiateEncoding(acceptEncoding) {
52224
53821
  if (!acceptEncoding) return null;
52225
53822
  const offers = /* @__PURE__ */ new Map();
@@ -52242,9 +53839,9 @@ function negotiateEncoding(acceptEncoding) {
52242
53839
  function compress(body, encoding) {
52243
53840
  const out = encoding === "br" ? brotliCompressSync(body, {
52244
53841
  params: {
52245
- [constants2.BROTLI_PARAM_QUALITY]: 4,
53842
+ [constants3.BROTLI_PARAM_QUALITY]: 4,
52246
53843
  // Telling brotli the input size lets it size its window instead of guessing.
52247
- [constants2.BROTLI_PARAM_SIZE_HINT]: body.byteLength
53844
+ [constants3.BROTLI_PARAM_SIZE_HINT]: body.byteLength
52248
53845
  }
52249
53846
  }) : gzipSync(body, { level: 6 });
52250
53847
  return Uint8Array.from(out);
@@ -52265,10 +53862,10 @@ var init_compression = __esm({
52265
53862
  });
52266
53863
 
52267
53864
  // packages/server/src/local-image.ts
52268
- import { readFileSync as readFileSync26, realpathSync as realpathSync11, statSync as statSync19 } from "node:fs";
52269
- import { extname as extname2, isAbsolute as isAbsolute6 } from "node:path";
53865
+ import { readFileSync as readFileSync27, realpathSync as realpathSync11, statSync as statSync20 } from "node:fs";
53866
+ import { extname as extname2, isAbsolute as isAbsolute7 } from "node:path";
52270
53867
  function resolveLocalImage(rawPath) {
52271
- if (!rawPath || !isAbsolute6(rawPath)) return { status: 400 };
53868
+ if (!rawPath || !isAbsolute7(rawPath)) return { status: 400 };
52272
53869
  const contentType = IMAGE_CONTENT_TYPE[extname2(rawPath).toLowerCase()];
52273
53870
  if (!contentType) return { status: 400 };
52274
53871
  let real;
@@ -52278,8 +53875,8 @@ function resolveLocalImage(rawPath) {
52278
53875
  return { status: 404 };
52279
53876
  }
52280
53877
  try {
52281
- if (!statSync19(real).isFile()) return { status: 404 };
52282
- return { status: 200, contentType, body: readFileSync26(real) };
53878
+ if (!statSync20(real).isFile()) return { status: 404 };
53879
+ return { status: 200, contentType, body: readFileSync27(real) };
52283
53880
  } catch {
52284
53881
  return { status: 404 };
52285
53882
  }
@@ -52299,9 +53896,9 @@ var init_local_image = __esm({
52299
53896
  });
52300
53897
 
52301
53898
  // packages/server/src/project-icon.ts
52302
- import { readFileSync as readFileSync27, readdirSync as readdirSync10, realpathSync as realpathSync12, statSync as statSync20 } from "node:fs";
53899
+ import { readFileSync as readFileSync28, readdirSync as readdirSync10, realpathSync as realpathSync12, statSync as statSync21 } from "node:fs";
52303
53900
  import { homedir as homedir18 } from "node:os";
52304
- import { basename as basename10, dirname as dirname17, extname as extname3, join as join38, relative as relative2, sep as sep3 } from "node:path";
53901
+ import { basename as basename10, dirname as dirname18, extname as extname3, join as join40, relative as relative2, sep as sep3 } from "node:path";
52305
53902
  function normalizeStem(stem) {
52306
53903
  const sized = stem.toLowerCase().replace(/[@-]\d+(\.\d+)?x(\d+)?$/u, "").replace(/[-_]\d{2,4}$/u, "");
52307
53904
  const normalized = sized.replace(VARIANT_SUFFIX, "");
@@ -52319,7 +53916,7 @@ function sizeScore(dimensions) {
52319
53916
  return Math.max(0, Math.min(40, Math.round(Math.log2(edge / 24) * 8)));
52320
53917
  }
52321
53918
  function under(root, relativeDirectory) {
52322
- return relativeDirectory ? join38(root, relativeDirectory) : root;
53919
+ return relativeDirectory ? join40(root, relativeDirectory) : root;
52323
53920
  }
52324
53921
  function listFiles(directory) {
52325
53922
  try {
@@ -52341,19 +53938,19 @@ function iconDirectories(root) {
52341
53938
  for (const asset of ASSET_DIRECTORIES) directories.push(under(root, asset));
52342
53939
  for (const host of HOST_DIRECTORIES) {
52343
53940
  if (!children2.has(host)) continue;
52344
- for (const asset of ASSET_DIRECTORIES) directories.push(under(join38(root, host), asset));
53941
+ for (const asset of ASSET_DIRECTORIES) directories.push(under(join40(root, host), asset));
52345
53942
  }
52346
53943
  for (const extra of EXTRA_DIRECTORIES) directories.push(under(root, extra));
52347
53944
  for (const container of WORKSPACE_CONTAINERS) {
52348
53945
  if (!children2.has(container)) continue;
52349
53946
  let members;
52350
53947
  try {
52351
- members = readdirSync10(join38(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
53948
+ members = readdirSync10(join40(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
52352
53949
  } catch {
52353
53950
  continue;
52354
53951
  }
52355
53952
  for (const member of members) {
52356
- for (const asset of ASSET_DIRECTORIES) directories.push(under(join38(root, container, member), asset));
53953
+ for (const asset of ASSET_DIRECTORIES) directories.push(under(join40(root, container, member), asset));
52357
53954
  }
52358
53955
  }
52359
53956
  return [...new Set(directories)];
@@ -52361,13 +53958,13 @@ function iconDirectories(root) {
52361
53958
  function manifestIcons(manifestPath) {
52362
53959
  let parsed;
52363
53960
  try {
52364
- parsed = JSON.parse(readFileSync27(manifestPath, "utf8"));
53961
+ parsed = JSON.parse(readFileSync28(manifestPath, "utf8"));
52365
53962
  } catch {
52366
53963
  return [];
52367
53964
  }
52368
53965
  if (!Array.isArray(parsed?.icons)) return [];
52369
- const directory = dirname17(manifestPath);
52370
- 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) => join38(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
53966
+ const directory = dirname18(manifestPath);
53967
+ 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) => join40(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
52371
53968
  }
52372
53969
  function projectIconCandidates(root) {
52373
53970
  const projectStem = normalizeStem(basename10(root)).normalized;
@@ -52386,10 +53983,10 @@ function projectIconCandidates(root) {
52386
53983
  for (const name of listFiles(directory)) {
52387
53984
  const extension2 = extname3(name).toLowerCase();
52388
53985
  if (MANIFEST_NAMES.includes(name.toLowerCase())) {
52389
- for (const icon of manifestIcons(join38(directory, name))) {
53986
+ for (const icon of manifestIcons(join40(directory, name))) {
52390
53987
  if (MEASURABLE_IMAGE_EXTENSIONS.has(extname3(icon).toLowerCase())) {
52391
53988
  fromManifest.add(icon);
52392
- files.push({ path: icon, directory: dirname17(icon) });
53989
+ files.push({ path: icon, directory: dirname18(icon) });
52393
53990
  }
52394
53991
  }
52395
53992
  continue;
@@ -52397,7 +53994,7 @@ function projectIconCandidates(root) {
52397
53994
  if (!MEASURABLE_IMAGE_EXTENSIONS.has(extension2)) continue;
52398
53995
  const stem = name.slice(0, name.length - extension2.length);
52399
53996
  if (normalizeStem(stem).normalized === "favicon") iconHomes.add(directory);
52400
- files.push({ path: join38(directory, name), directory });
53997
+ files.push({ path: join40(directory, name), directory });
52401
53998
  if (files.length >= MAX_CANDIDATES) break;
52402
53999
  }
52403
54000
  }
@@ -52449,7 +54046,7 @@ function resolveProjectIconResponse(id, options = {}) {
52449
54046
  let real;
52450
54047
  try {
52451
54048
  real = realpathSync12(path3);
52452
- if (!statSync20(real).isFile()) return { status: 404 };
54049
+ if (!statSync21(real).isFile()) return { status: 404 };
52453
54050
  } catch {
52454
54051
  return { status: 404 };
52455
54052
  }
@@ -52462,7 +54059,7 @@ function resolveProjectIconResponse(id, options = {}) {
52462
54059
  });
52463
54060
  if (!roots.some((root) => real === root || real.startsWith(root + sep3))) return { status: 404 };
52464
54061
  try {
52465
- return { status: 200, contentType, body: readFileSync27(real) };
54062
+ return { status: 200, contentType, body: readFileSync28(real) };
52466
54063
  } catch {
52467
54064
  return { status: 404 };
52468
54065
  }
@@ -52574,8 +54171,8 @@ var init_project_icon = __esm({
52574
54171
  });
52575
54172
 
52576
54173
  // packages/server/src/local-visualization.ts
52577
- import { readdirSync as readdirSync11, readFileSync as readFileSync28, realpathSync as realpathSync13, statSync as statSync21 } from "node:fs";
52578
- import { join as join39, sep as sep4 } from "node:path";
54174
+ import { readdirSync as readdirSync11, readFileSync as readFileSync29, realpathSync as realpathSync13, statSync as statSync22 } from "node:fs";
54175
+ import { join as join41, sep as sep4 } from "node:path";
52579
54176
  function children(path3, pattern) {
52580
54177
  try {
52581
54178
  return readdirSync11(path3, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
@@ -52587,7 +54184,7 @@ function isUnder2(path3, root) {
52587
54184
  return path3 === root || path3.startsWith(root.endsWith(sep4) ? root : root + sep4);
52588
54185
  }
52589
54186
  function resolveFragment(projectDir, sessionId, file) {
52590
- const base = join39(projectDir, ".codex", "visualizations");
54187
+ const base = join41(projectDir, ".codex", "visualizations");
52591
54188
  let projectReal;
52592
54189
  let baseReal;
52593
54190
  try {
@@ -52598,15 +54195,15 @@ function resolveFragment(projectDir, sessionId, file) {
52598
54195
  }
52599
54196
  if (!isUnder2(baseReal, projectReal)) return null;
52600
54197
  for (const year of children(base, YEAR_PART)) {
52601
- const yearDir = join39(base, year);
54198
+ const yearDir = join41(base, year);
52602
54199
  for (const month of children(yearDir, DATE_PART)) {
52603
- const monthDir = join39(yearDir, month);
54200
+ const monthDir = join41(yearDir, month);
52604
54201
  for (const day of children(monthDir, DATE_PART)) {
52605
- const sessionRoot = join39(baseReal, year, month, day, sessionId);
52606
- const candidate = join39(monthDir, day, sessionId, file);
54202
+ const sessionRoot = join41(baseReal, year, month, day, sessionId);
54203
+ const candidate = join41(monthDir, day, sessionId, file);
52607
54204
  try {
52608
54205
  const real = realpathSync13(candidate);
52609
- if (isUnder2(real, sessionRoot) && statSync21(real).isFile()) return real;
54206
+ if (isUnder2(real, sessionRoot) && statSync22(real).isFile()) return real;
52610
54207
  } catch {
52611
54208
  }
52612
54209
  }
@@ -52687,9 +54284,9 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
52687
54284
  if (!path3) return { status: 404 };
52688
54285
  let fragment;
52689
54286
  try {
52690
- const size = statSync21(path3).size;
54287
+ const size = statSync22(path3).size;
52691
54288
  if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
52692
- fragment = readFileSync28(path3, "utf8");
54289
+ fragment = readFileSync29(path3, "utf8");
52693
54290
  } catch {
52694
54291
  return { status: 404 };
52695
54292
  }
@@ -52731,8 +54328,8 @@ var init_local_visualization = __esm({
52731
54328
  });
52732
54329
 
52733
54330
  // packages/server/src/app.ts
52734
- import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync14 } from "node:fs";
52735
- import { join as join40 } from "node:path";
54331
+ import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync14 } from "node:fs";
54332
+ import { join as join42 } from "node:path";
52736
54333
  import { randomUUID as randomUUID17 } from "node:crypto";
52737
54334
  function createApp(ctx, options = {}) {
52738
54335
  const app = new Hono2();
@@ -52836,10 +54433,10 @@ function createApp(ctx, options = {}) {
52836
54433
  if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
52837
54434
  const ext = `.${attachmentExtension(name)}`;
52838
54435
  const buf = Buffer.from(body.data, "base64");
52839
- const dir = join40(ctx.project.stateDir, "attachments");
52840
- mkdirSync17(dir, { recursive: true });
54436
+ const dir = join42(ctx.project.stateDir, "attachments");
54437
+ mkdirSync18(dir, { recursive: true });
52841
54438
  const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
52842
- const path3 = join40(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
54439
+ const path3 = join42(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
52843
54440
  writeFileSync14(path3, buf);
52844
54441
  return c.json({ path: path3 });
52845
54442
  });
@@ -54151,11 +55748,11 @@ var init_app_socket = __esm({
54151
55748
  });
54152
55749
 
54153
55750
  // packages/server/src/local-file-watch.ts
54154
- import { statSync as statSync22, watch } from "node:fs";
54155
- import { basename as basename11, dirname as dirname18 } from "node:path";
55751
+ import { statSync as statSync23, watch } from "node:fs";
55752
+ import { basename as basename11, dirname as dirname19 } from "node:path";
54156
55753
  function stampOf(path3) {
54157
55754
  try {
54158
- const s = statSync22(path3, { bigint: true });
55755
+ const s = statSync23(path3, { bigint: true });
54159
55756
  return `${s.ino}:${s.size}:${s.mtimeNs}`;
54160
55757
  } catch {
54161
55758
  return "gone";
@@ -54175,7 +55772,7 @@ function watchLocalFile(path3, onChange) {
54175
55772
  let entry = entries.get(path3);
54176
55773
  if (!entry) {
54177
55774
  const name = basename11(path3);
54178
- const watcher2 = watch(dirname18(path3), { persistent: false }, (_event, changed) => {
55775
+ const watcher2 = watch(dirname19(path3), { persistent: false }, (_event, changed) => {
54179
55776
  if (changed !== null && changed !== void 0 && changed.toString() !== name) return;
54180
55777
  const current = entries.get(path3);
54181
55778
  if (!current) return;
@@ -54211,10 +55808,10 @@ var init_local_file_watch = __esm({
54211
55808
  });
54212
55809
 
54213
55810
  // packages/server/src/boot-progress.ts
54214
- import { mkdirSync as mkdirSync18, readFileSync as readFileSync29, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
54215
- import { basename as basename12, dirname as dirname19, join as join41 } from "node:path";
55811
+ import { mkdirSync as mkdirSync19, readFileSync as readFileSync30, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
55812
+ import { basename as basename12, dirname as dirname20, join as join43 } from "node:path";
54216
55813
  function bootProgressPath(stateDir) {
54217
- return join41(stateDir, BOOT_PROGRESS_NAME);
55814
+ return join43(stateDir, BOOT_PROGRESS_NAME);
54218
55815
  }
54219
55816
  function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
54220
55817
  if (!stateDir) {
@@ -54234,8 +55831,8 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
54234
55831
  lastAt = now;
54235
55832
  step++;
54236
55833
  try {
54237
- mkdirSync18(dirname19(path3), { recursive: true, mode: 448 });
54238
- const temp = join41(dirname19(path3), `.${basename12(path3)}.${process.pid}.tmp`);
55834
+ mkdirSync19(dirname20(path3), { recursive: true, mode: 448 });
55835
+ const temp = join43(dirname20(path3), `.${basename12(path3)}.${process.pid}.tmp`);
54239
55836
  writeFileSync15(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
54240
55837
  `, "utf8");
54241
55838
  renameSync10(temp, path3);
@@ -54251,22 +55848,23 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
54251
55848
  }
54252
55849
  });
54253
55850
  }
54254
- var BOOT_PROGRESS_NAME;
55851
+ var BOOT_PROGRESS_NAME, BOOT_HARD_TIMEOUT_MS;
54255
55852
  var init_boot_progress = __esm({
54256
55853
  "packages/server/src/boot-progress.ts"() {
54257
55854
  "use strict";
54258
55855
  BOOT_PROGRESS_NAME = "boot.progress";
55856
+ BOOT_HARD_TIMEOUT_MS = 10 * 6e4;
54259
55857
  }
54260
55858
  });
54261
55859
 
54262
55860
  // packages/server/src/runtime-lease.ts
54263
- import { existsSync as existsSync15, mkdirSync as mkdirSync19, readdirSync as readdirSync12, readFileSync as readFileSync30, unlinkSync as unlinkSync6, writeFileSync as writeFileSync16 } from "node:fs";
54264
- import { dirname as dirname20, join as join42 } from "node:path";
55861
+ import { existsSync as existsSync16, mkdirSync as mkdirSync20, readdirSync as readdirSync12, readFileSync as readFileSync31, unlinkSync as unlinkSync6, writeFileSync as writeFileSync16 } from "node:fs";
55862
+ import { dirname as dirname21, join as join44 } from "node:path";
54265
55863
  function runtimeVersionDir(bin) {
54266
- let dir = dirname20(bin);
55864
+ let dir = dirname21(bin);
54267
55865
  for (let step = 0; step < MAX_ASCENT; step++) {
54268
- if (existsSync15(join42(dir, PROVISIONED_MARKER))) return dir;
54269
- const parent = dirname20(dir);
55866
+ if (existsSync16(join44(dir, PROVISIONED_MARKER))) return dir;
55867
+ const parent = dirname21(dir);
54270
55868
  if (parent === dir) return void 0;
54271
55869
  dir = parent;
54272
55870
  }
@@ -54284,9 +55882,9 @@ function leaseRuntime(bin, role) {
54284
55882
  const versionDir = runtimeVersionDir(bin);
54285
55883
  if (!versionDir) return () => {
54286
55884
  };
54287
- const path3 = join42(versionDir, LEASES_DIR, `${process.pid}.json`);
55885
+ const path3 = join44(versionDir, LEASES_DIR, `${process.pid}.json`);
54288
55886
  try {
54289
- mkdirSync19(dirname20(path3), { recursive: true });
55887
+ mkdirSync20(dirname21(path3), { recursive: true });
54290
55888
  const lease = { pid: process.pid, role, bin, at: (/* @__PURE__ */ new Date()).toISOString() };
54291
55889
  writeFileSync16(path3, JSON.stringify(lease));
54292
55890
  } catch {
@@ -54301,7 +55899,7 @@ function leaseRuntime(bin, role) {
54301
55899
  };
54302
55900
  }
54303
55901
  function liveRuntimeLeases(versionDir) {
54304
- const dir = join42(versionDir, LEASES_DIR);
55902
+ const dir = join44(versionDir, LEASES_DIR);
54305
55903
  let names;
54306
55904
  try {
54307
55905
  names = readdirSync12(dir);
@@ -54311,10 +55909,10 @@ function liveRuntimeLeases(versionDir) {
54311
55909
  const live = [];
54312
55910
  for (const name of names) {
54313
55911
  if (!name.endsWith(".json")) continue;
54314
- const path3 = join42(dir, name);
55912
+ const path3 = join44(dir, name);
54315
55913
  let lease;
54316
55914
  try {
54317
- lease = JSON.parse(readFileSync30(path3, "utf8"));
55915
+ lease = JSON.parse(readFileSync31(path3, "utf8"));
54318
55916
  } catch {
54319
55917
  continue;
54320
55918
  }
@@ -54345,25 +55943,25 @@ import { createHash as createHash15 } from "node:crypto";
54345
55943
  import {
54346
55944
  chmodSync as chmodSync2,
54347
55945
  closeSync as closeSync16,
54348
- existsSync as existsSync16,
54349
- mkdirSync as mkdirSync20,
55946
+ existsSync as existsSync17,
55947
+ mkdirSync as mkdirSync21,
54350
55948
  openSync as openSync17,
54351
- readFileSync as readFileSync31,
55949
+ readFileSync as readFileSync32,
54352
55950
  readdirSync as readdirSync13,
54353
55951
  renameSync as renameSync11,
54354
55952
  rmSync as rmSync12,
54355
- statSync as statSync23,
55953
+ statSync as statSync24,
54356
55954
  writeFileSync as writeFileSync17,
54357
55955
  writeSync as writeSync2
54358
55956
  } from "node:fs";
54359
- import { dirname as dirname21, isAbsolute as isAbsolute7, join as join43, normalize, relative as relative3, sep as sep5 } from "node:path";
55957
+ import { dirname as dirname22, isAbsolute as isAbsolute8, join as join45, normalize, relative as relative3, sep as sep5 } from "node:path";
54360
55958
  import { Readable, Transform, Writable as Writable3 } from "node:stream";
54361
55959
  import { pipeline } from "node:stream/promises";
54362
55960
  import { createGunzip } from "node:zlib";
54363
55961
  function runtimesRoot(env = process.env) {
54364
55962
  const override = env[RUNTIMES_DIR_ENV];
54365
- if (override && isAbsolute7(override)) return override;
54366
- return join43(frizzRoots().cache, "runtimes");
55963
+ if (override && isAbsolute8(override)) return override;
55964
+ return join45(frizzRoots().cache, "runtimes");
54367
55965
  }
54368
55966
  function isMusl() {
54369
55967
  if (process.platform !== "linux") return false;
@@ -54380,16 +55978,16 @@ function runtimeCoordinates(backend, platform3 = process.platform, arch = proces
54380
55978
  return { pkg: "@openai/codex", packageVersion: `${CODEX_VERSION}-${platform3}-${arch}`, label: CODEX_VERSION, binary: `codex${exe}` };
54381
55979
  }
54382
55980
  function provisionedBinary(backend, root, label) {
54383
- const dir = join43(root, backend, label);
55981
+ const dir = join45(root, backend, label);
54384
55982
  let marker;
54385
55983
  try {
54386
- marker = JSON.parse(readFileSync31(join43(dir, MARKER), "utf8"));
55984
+ marker = JSON.parse(readFileSync32(join45(dir, MARKER), "utf8"));
54387
55985
  } catch {
54388
55986
  return void 0;
54389
55987
  }
54390
55988
  if (typeof marker.binary !== "string" || marker.label !== label) return void 0;
54391
- const bin = join43(dir, marker.binary);
54392
- return existsSync16(bin) ? bin : void 0;
55989
+ const bin = join45(dir, marker.binary);
55990
+ return existsSync17(bin) ? bin : void 0;
54393
55991
  }
54394
55992
  function octal(buffer, offset, length) {
54395
55993
  const text2 = buffer.toString("latin1", offset, offset + length).replace(/\0.*$/su, "").trim();
@@ -54402,18 +56000,18 @@ function safeEntryPath(dest, name) {
54402
56000
  const parts = name.split("/").filter((part) => part.length > 0);
54403
56001
  if (parts.length < 2) return void 0;
54404
56002
  const rel = normalize(parts.slice(1).join(sep5));
54405
- if (isAbsolute7(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return void 0;
54406
- const full = join43(dest, rel);
56003
+ if (isAbsolute8(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return void 0;
56004
+ const full = join45(dest, rel);
54407
56005
  const back = relative3(dest, full);
54408
- if (back.startsWith("..") || isAbsolute7(back)) return void 0;
56006
+ if (back.startsWith("..") || isAbsolute8(back)) return void 0;
54409
56007
  return full;
54410
56008
  }
54411
56009
  function locateBinary(dir, backend, binary) {
54412
56010
  if (backend === "claude") {
54413
- const direct = join43(dir, binary);
54414
- return existsSync16(direct) ? direct : void 0;
56011
+ const direct = join45(dir, binary);
56012
+ return existsSync17(direct) ? direct : void 0;
54415
56013
  }
54416
- const vendor = join43(dir, "vendor");
56014
+ const vendor = join45(dir, "vendor");
54417
56015
  let triples;
54418
56016
  try {
54419
56017
  triples = readdirSync13(vendor);
@@ -54421,8 +56019,8 @@ function locateBinary(dir, backend, binary) {
54421
56019
  return void 0;
54422
56020
  }
54423
56021
  for (const triple of triples) {
54424
- const candidate = join43(vendor, triple, "bin", binary);
54425
- if (existsSync16(candidate)) return candidate;
56022
+ const candidate = join45(vendor, triple, "bin", binary);
56023
+ if (existsSync17(candidate)) return candidate;
54426
56024
  }
54427
56025
  return void 0;
54428
56026
  }
@@ -54437,12 +56035,12 @@ async function provisionRuntime(backend, options) {
54437
56035
  const registry = options.registry ?? REGISTRY;
54438
56036
  const progress = options.onProgress ?? (() => {
54439
56037
  });
54440
- const backendDir = join43(root, backend);
54441
- const finalDir = join43(backendDir, coordinates.label);
54442
- const partial = join43(backendDir, `.partial-${coordinates.label}-${process.pid}`);
54443
- mkdirSync20(backendDir, { recursive: true });
56038
+ const backendDir = join45(root, backend);
56039
+ const finalDir = join45(backendDir, coordinates.label);
56040
+ const partial = join45(backendDir, `.partial-${coordinates.label}-${process.pid}`);
56041
+ mkdirSync21(backendDir, { recursive: true });
54444
56042
  rmSync12(partial, { recursive: true, force: true });
54445
- mkdirSync20(partial);
56043
+ mkdirSync21(partial);
54446
56044
  try {
54447
56045
  const manifestUrl = `${registry}/${coordinates.pkg.replace("/", "%2F")}/${coordinates.packageVersion}`;
54448
56046
  progress(`resolving ${coordinates.pkg}@${coordinates.packageVersion}`);
@@ -54486,7 +56084,7 @@ async function provisionRuntime(backend, options) {
54486
56084
  binary: relative3(partial, bin),
54487
56085
  at: (/* @__PURE__ */ new Date()).toISOString()
54488
56086
  };
54489
- writeFileSync17(join43(partial, MARKER), `${JSON.stringify(marker, null, 2)}
56087
+ writeFileSync17(join45(partial, MARKER), `${JSON.stringify(marker, null, 2)}
54490
56088
  `, "utf8");
54491
56089
  const rename2 = options.rename ?? renameSync11;
54492
56090
  try {
@@ -54495,7 +56093,7 @@ async function provisionRuntime(backend, options) {
54495
56093
  rename2(partial, finalDir);
54496
56094
  break;
54497
56095
  } catch (err) {
54498
- if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") || existsSync16(finalDir)) throw err;
56096
+ if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") || existsSync17(finalDir)) throw err;
54499
56097
  await new Promise((resolve12) => setTimeout(resolve12, options.retryDelayMs ?? RENAME_RETRY_DELAY_MS));
54500
56098
  }
54501
56099
  }
@@ -54506,7 +56104,7 @@ async function provisionRuntime(backend, options) {
54506
56104
  return { bin: theirs, label: coordinates.label, fetched: false };
54507
56105
  }
54508
56106
  progress("ready");
54509
- return { bin: join43(finalDir, marker.binary), label: coordinates.label, fetched: true };
56107
+ return { bin: join45(finalDir, marker.binary), label: coordinates.label, fetched: true };
54510
56108
  } catch (err) {
54511
56109
  rmSync12(partial, { recursive: true, force: true });
54512
56110
  throw err;
@@ -54514,7 +56112,7 @@ async function provisionRuntime(backend, options) {
54514
56112
  }
54515
56113
  function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
54516
56114
  const rm2 = deps.rm ?? defaultSweepRm;
54517
- const backendDir = join43(root, backend);
56115
+ const backendDir = join45(root, backend);
54518
56116
  let entries2;
54519
56117
  try {
54520
56118
  entries2 = readdirSync13(backendDir);
@@ -54524,10 +56122,10 @@ function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
54524
56122
  const sweep = { removed: [], kept: [] };
54525
56123
  for (const entry of entries2) {
54526
56124
  if (entry === keepLabel) continue;
54527
- const full = join43(backendDir, entry);
56125
+ const full = join45(backendDir, entry);
54528
56126
  if (entry.startsWith(".partial-")) {
54529
56127
  try {
54530
- if (now - statSync23(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
56128
+ if (now - statSync24(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
54531
56129
  } catch {
54532
56130
  continue;
54533
56131
  }
@@ -54617,8 +56215,8 @@ var init_runtimes = __esm({
54617
56215
  init_codex_app_server();
54618
56216
  init_frizz_paths();
54619
56217
  init_runtime_lease();
54620
- CLAUDE_AGENT_SDK_VERSION = "0.3.271";
54621
- CLAUDE_CODE_VERSION = "2.1.271";
56218
+ CLAUDE_AGENT_SDK_VERSION = "0.3.273";
56219
+ CLAUDE_CODE_VERSION = "2.1.273";
54622
56220
  CODEX_VERSION = CODEX_APP_SERVER_SUPPORTED_VERSION;
54623
56221
  RUNTIMES_MODE_ENV = "FRIZZ_RUNTIMES";
54624
56222
  RUNTIMES_DIR_ENV = "FRIZZ_RUNTIMES_DIR";
@@ -54707,7 +56305,7 @@ var init_runtimes = __esm({
54707
56305
  return;
54708
56306
  case "5": {
54709
56307
  const dir = safeEntryPath(this.dest, name);
54710
- if (dir) mkdirSync20(dir, { recursive: true });
56308
+ if (dir) mkdirSync21(dir, { recursive: true });
54711
56309
  return;
54712
56310
  }
54713
56311
  case "0":
@@ -54715,7 +56313,7 @@ var init_runtimes = __esm({
54715
56313
  case "7": {
54716
56314
  const file = safeEntryPath(this.dest, name);
54717
56315
  if (!file) return;
54718
- mkdirSync20(dirname21(file), { recursive: true });
56316
+ mkdirSync21(dirname22(file), { recursive: true });
54719
56317
  this.fd = openSync17(file, "w", octal(header, 100, 8) & 511 || 420);
54720
56318
  this.written.push(file);
54721
56319
  return;
@@ -55292,18 +56890,18 @@ var init_legacy_project_db = __esm({
55292
56890
  });
55293
56891
 
55294
56892
  // packages/server/src/frizz-db.ts
55295
- import { existsSync as existsSync17, mkdirSync as mkdirSync21, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync24 } from "node:fs";
55296
- import { basename as basename13, dirname as dirname22, join as join44 } from "node:path";
56893
+ import { existsSync as existsSync18, mkdirSync as mkdirSync22, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync25 } from "node:fs";
56894
+ import { basename as basename13, dirname as dirname23, join as join46 } from "node:path";
55297
56895
  function frizzDatabaseLocation(options) {
55298
56896
  if (options.stateDir !== void 0) {
55299
- const projectsDir = dirname22(options.stateDir);
56897
+ const projectsDir = dirname23(options.stateDir);
55300
56898
  if (basename13(projectsDir) === "projects") {
55301
- return { path: options.path ?? join44(dirname22(projectsDir), "ui.db"), projectsDir };
56899
+ return { path: options.path ?? join46(dirname23(projectsDir), "ui.db"), projectsDir };
55302
56900
  }
55303
- return { path: options.path ?? join44(options.stateDir, "ui.db"), projectsDir: void 0 };
56901
+ return { path: options.path ?? join46(options.stateDir, "ui.db"), projectsDir: void 0 };
55304
56902
  }
55305
56903
  const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
55306
- return { path: options.path ?? join44(data, "ui.db"), projectsDir: join44(data, "projects") };
56904
+ return { path: options.path ?? join46(data, "ui.db"), projectsDir: join46(data, "projects") };
55307
56905
  }
55308
56906
  function ensureFrizzSchema(db) {
55309
56907
  db.exec(`
@@ -55322,7 +56920,7 @@ function ensureFrizzSchema(db) {
55322
56920
  }
55323
56921
  function openFrizzDatabase(options = {}) {
55324
56922
  const { path: path3, projectsDir } = frizzDatabaseLocation(options);
55325
- mkdirSync21(dirname22(path3), { recursive: true });
56923
+ mkdirSync22(dirname23(path3), { recursive: true });
55326
56924
  const db = new sqlite_default(path3);
55327
56925
  db.pragma("busy_timeout = 5000");
55328
56926
  db.pragma("journal_mode = WAL");
@@ -55334,10 +56932,10 @@ function openFrizzDatabase(options = {}) {
55334
56932
  );
55335
56933
  for (const projectId of retiredLegacyProjectIds(projectsDir)) {
55336
56934
  if (alreadyImported.has(projectId)) continue;
55337
- const dir = join44(projectsDir, projectId);
56935
+ const dir = join46(projectsDir, projectId);
55338
56936
  try {
55339
- renameSync12(join44(dir, "ui.db.imported"), join44(dir, "ui.db"));
55340
- db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join44(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
56937
+ renameSync12(join46(dir, "ui.db.imported"), join46(dir, "ui.db"));
56938
+ db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join46(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
55341
56939
  alreadyImported.add(projectId);
55342
56940
  log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
55343
56941
  } catch (error) {
@@ -55347,7 +56945,7 @@ function openFrizzDatabase(options = {}) {
55347
56945
  }
55348
56946
  for (const projectId of legacyProjectIds(projectsDir)) {
55349
56947
  if (alreadyImported.has(projectId)) continue;
55350
- const legacyPath = join44(projectsDir, projectId, "ui.db");
56948
+ const legacyPath = join46(projectsDir, projectId, "ui.db");
55351
56949
  try {
55352
56950
  const rows = importLegacyProject(db, projectId, legacyPath);
55353
56951
  imported.push({ projectId, rows });
@@ -55377,10 +56975,10 @@ function openFrizzDatabase(options = {}) {
55377
56975
  };
55378
56976
  }
55379
56977
  function projectIdsWithFile(projectsDir, file) {
55380
- if (!existsSync17(projectsDir)) return [];
56978
+ if (!existsSync18(projectsDir)) return [];
55381
56979
  return readdirSync14(projectsDir).filter((name) => {
55382
56980
  try {
55383
- return statSync24(join44(projectsDir, name, file)).isFile();
56981
+ return statSync25(join46(projectsDir, name, file)).isFile();
55384
56982
  } catch {
55385
56983
  return false;
55386
56984
  }
@@ -55390,7 +56988,7 @@ function legacyProjectIds(projectsDir) {
55390
56988
  return projectIdsWithFile(projectsDir, "ui.db");
55391
56989
  }
55392
56990
  function retiredLegacyProjectIds(projectsDir) {
55393
- return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync17(join44(projectsDir, id, "ui.db")));
56991
+ return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync18(join46(projectsDir, id, "ui.db")));
55394
56992
  }
55395
56993
  function tableColumns(db, schema, table) {
55396
56994
  return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
@@ -55546,7 +57144,7 @@ var init_tenant_prime = __esm({
55546
57144
  // packages/server/src/project-teardown.ts
55547
57145
  import { rmSync as rmSync13 } from "node:fs";
55548
57146
  import { homedir as homedir19 } from "node:os";
55549
- import { dirname as dirname23, resolve as resolve10 } from "node:path";
57147
+ import { dirname as dirname24, resolve as resolve10 } from "node:path";
55550
57148
  async function stopProjectWorkers(ctx) {
55551
57149
  let stopped = 0;
55552
57150
  for (const row of ctx.storage.allSessions()) {
@@ -55566,7 +57164,7 @@ function deleteProjectState(projectId, home = homedir19()) {
55566
57164
  if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
55567
57165
  throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
55568
57166
  }
55569
- if (dirname23(resolve10(target)) !== resolve10(dirname23(projectStateDir("x", home)))) {
57167
+ if (dirname24(resolve10(target)) !== resolve10(dirname24(projectStateDir("x", home)))) {
55570
57168
  throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
55571
57169
  }
55572
57170
  rmSync13(target, { recursive: true, force: true });
@@ -55595,11 +57193,11 @@ __export(index_exports, {
55595
57193
  unknownProjectPage: () => unknownProjectPage
55596
57194
  });
55597
57195
  import { createServer } from "node:http";
55598
- import { readFileSync as readFileSync32, existsSync as existsSync18, statSync as statSync25 } from "node:fs";
55599
- import { dirname as dirname24, join as join45, resolve as resolve11, extname as extname4, posix as posix2, sep as sep6 } from "node:path";
57196
+ import { readFileSync as readFileSync33, existsSync as existsSync19, statSync as statSync26 } from "node:fs";
57197
+ import { dirname as dirname25, join as join47, resolve as resolve11, extname as extname4, posix as posix2, sep as sep6 } from "node:path";
55600
57198
  function readServerAddressHolder(path3) {
55601
57199
  try {
55602
- const value = JSON.parse(readFileSync32(path3, "utf8"));
57200
+ const value = JSON.parse(readFileSync33(path3, "utf8"));
55603
57201
  if (typeof value?.pid !== "number") return void 0;
55604
57202
  return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
55605
57203
  } catch {
@@ -55724,12 +57322,12 @@ function staticIsFresh(req, etag, mtimeMs) {
55724
57322
  }
55725
57323
  function serveStatic(distDir, req, res) {
55726
57324
  const rel = posix2.normalize((req.url ?? "/").split("?")[0].replace(/\\/g, "/")).replace(/^(\.\.[/\\])+/, "");
55727
- let file = join45(distDir, rel === "/" ? "index.html" : rel);
55728
- if (!file.startsWith(distDir + sep6)) file = join45(distDir, "index.html");
55729
- if (!existsSync18(file)) file = join45(distDir, "index.html");
57325
+ let file = join47(distDir, rel === "/" ? "index.html" : rel);
57326
+ if (!file.startsWith(distDir + sep6)) file = join47(distDir, "index.html");
57327
+ if (!existsSync19(file)) file = join47(distDir, "index.html");
55730
57328
  try {
55731
- const stats = statSync25(file);
55732
- const cacheControl = file.startsWith(join45(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
57329
+ const stats = statSync26(file);
57330
+ const cacheControl = file.startsWith(join47(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
55733
57331
  const etag = `"${stats.size.toString(16)}-${Math.trunc(stats.mtimeMs).toString(16)}"`;
55734
57332
  const headers = {
55735
57333
  "content-type": MIME[extname4(file)] ?? "application/octet-stream",
@@ -55745,7 +57343,7 @@ function serveStatic(distDir, req, res) {
55745
57343
  res.end();
55746
57344
  return;
55747
57345
  }
55748
- let body = readFileSync32(file);
57346
+ let body = readFileSync33(file);
55749
57347
  const accept = req.headers["accept-encoding"];
55750
57348
  const encoding = negotiateEncoding(Array.isArray(accept) ? accept.join(",") : accept);
55751
57349
  if (encoding && shouldCompress(new Headers({ "content-type": headers["content-type"] }), body.byteLength)) {
@@ -56007,7 +57605,7 @@ async function startServer(opts = {}) {
56007
57605
  if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
56008
57606
  if (statusPath && statusIdentity) {
56009
57607
  try {
56010
- runtime.removeStatus(serverAddressPathForStateDir(dirname24(statusPath)), statusIdentity);
57608
+ runtime.removeStatus(serverAddressPathForStateDir(dirname25(statusPath)), statusIdentity);
56011
57609
  } catch {
56012
57610
  }
56013
57611
  }
@@ -56145,7 +57743,7 @@ async function startServer(opts = {}) {
56145
57743
  }
56146
57744
  statusPath = serverLockPathFor(ctx.project);
56147
57745
  const webRoot = resolve11(import.meta.dirname, "..", "..", "web");
56148
- const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join45(webRoot, "dist");
57746
+ const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join47(webRoot, "dist");
56149
57747
  startupPhase = "Vite";
56150
57748
  if (opts.dev) {
56151
57749
  try {
@@ -56221,7 +57819,7 @@ async function startServer(opts = {}) {
56221
57819
  if (vite) {
56222
57820
  vite.middlewares(req, res, () => {
56223
57821
  try {
56224
- const html = readFileSync32(join45(webRoot, "index.html"), "utf8");
57822
+ const html = readFileSync33(join47(webRoot, "index.html"), "utf8");
56225
57823
  void vite.transformIndexHtml(url, html).then((out) => {
56226
57824
  res.writeHead(200, { "content-type": "text/html" });
56227
57825
  res.end(out);
@@ -56233,7 +57831,7 @@ async function startServer(opts = {}) {
56233
57831
  });
56234
57832
  return;
56235
57833
  }
56236
- if (existsSync18(distDir)) {
57834
+ if (existsSync19(distDir)) {
56237
57835
  serveStatic(distDir, req, res);
56238
57836
  return;
56239
57837
  }
@@ -56431,7 +58029,7 @@ var init_index = __esm({
56431
58029
  }
56432
58030
  };
56433
58031
  isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
56434
- serverLockPathFor = (project) => join45(project.stateDir, "server.lock");
58032
+ serverLockPathFor = (project) => join47(project.stateDir, "server.lock");
56435
58033
  MIME = {
56436
58034
  ".html": "text/html",
56437
58035
  ".js": "text/javascript",
@@ -56454,8 +58052,8 @@ init_project();
56454
58052
  init_project_launch();
56455
58053
  init_shutdown();
56456
58054
  init_logging();
56457
- import { existsSync as existsSync19 } from "node:fs";
56458
- import { join as join46 } from "node:path";
58055
+ import { existsSync as existsSync20 } from "node:fs";
58056
+ import { join as join48 } from "node:path";
56459
58057
 
56460
58058
  // packages/server/src/native-helper.ts
56461
58059
  import { chmodSync, statSync as statSync4 } from "node:fs";
@@ -56532,9 +58130,9 @@ try {
56532
58130
  ["FRIZZ_SCRIPTS_DIR", process.env.FRIZZ_SCRIPTS_DIR, "index.mjs"],
56533
58131
  ["FRIZZ_WORKER_PLUGIN_DIR", process.env.FRIZZ_WORKER_PLUGIN_DIR, ".claude-plugin/plugin.json"]
56534
58132
  ];
56535
- if (!existsSync19(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
58133
+ if (!existsSync20(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
56536
58134
  for (const [name, directory, requiredFile] of required) {
56537
- if (!directory || !existsSync19(join46(directory, requiredFile)))
58135
+ if (!directory || !existsSync20(join48(directory, requiredFile)))
56538
58136
  throw new Error(`stable artifact launch is missing verified ${name}`);
56539
58137
  }
56540
58138
  }