frizz 0.12.10 → 0.12.12
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.
- package/dist/claude-agent-broker.js +56 -17
- package/dist/codex-app-server-daemon.js +57 -11
- package/dist/dev-child.js +1207 -517
- package/dist/frizz.js +591 -77
- package/package.json +1 -1
- package/runtime/cc-worker/bin/frizz-mcp.mjs +76 -18
- package/runtime/cc-worker/hooks/bash-background.d.mts +1 -1
- package/runtime/cc-worker/hooks/bash-background.mjs +17 -6
- package/runtime/cc-worker/hooks/deny-ask.mjs +5 -4
- package/runtime/cc-worker/hooks/deny-plan.mjs +3 -3
- package/runtime/cc-worker/hooks/session-seed.mjs +1 -1
- package/runtime/cc-worker/skills/gh/SKILL.md +3 -3
- package/web-dist/assets/{TerminalPane-BLyRh8S1.js → TerminalPane-CTuT1746.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-B0n1m57h.js → abnfDiagram-VRR7QNED-C9lXEgoU.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-cPNCo9oK.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-CW78Jo4Q.js → architectureDiagram-ZJ3FMSHR-BmJyQxby.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-DasDnytT.js → blockDiagram-677ZJIJ3-DYTepy7i.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-B7cCJxqg.js → c4Diagram-LMCZKHZV-CNaJlw58.js} +1 -1
- package/web-dist/assets/channel-D5dgm6BA.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-BBBSn1aq.js → chunk-32BRIVSS-uAMm81q2.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-B5lATDKz.js → chunk-52WLFC77-DjszZCE7.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-B86e9JiF.js → chunk-C7G6YPKG-BuHoup14.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-CpKL4dGX.js → chunk-EX3LRPZG-Bg9UEQvR.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-Dp3AvQlT.js → chunk-FWX5IMBZ-CHnQfTLw.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-i9yVPrLo.js → chunk-HOUHSVGY-ByjeQLq2.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX--Y7uMkOf.js → chunk-ICXQ74PX-DeM9b_DJ.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-DyMHpsmW.js → chunk-MOJQB5TN-DxLERy2q.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-1mtDKLBA.js → chunk-OGEWGWER-sBPjsAxh.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-CFgSrqyc.js → chunk-PUDLZKDR-CarEdLd9.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-BPhNENQ8.js → chunk-Q4XR5HBZ-BfXpvkfr.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-CMrXD-em.js → chunk-V7JOEXUC-B3FaJqr_.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-B6lbggln.js → chunk-VAUOI2AC-DHsDLPEL.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-B9qypAjb.js → chunk-VR4S4FIN-ObNNjO-z.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-CI9tkIhS.js → chunk-WYO6CB5R-JZxgPfBf.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-DFJPi6M6.js → chunk-ZGVPDNZ5-rLSAye5U.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-Bj_Hjv7y.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-Bj_Hjv7y.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-g28vb2Hf.js → cynefin-VYW2F7L2-B0-Xy-xW.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-bBtOt0pp.js → cynefinDiagram-TSTJHNR4-Dd3QCTMp.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-DuIcY2-t.js → dagre-VKFMJZFB-c_sDjRhh.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-CsYoD3ve.js → diagram-FQU43EPY-DlrOA7Q2.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-H3NxrIKS.js → diagram-G47NLZAW-DNwGdtwN.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-DIojGMfZ.js → diagram-NH7WQ7WH-CPNunIAd.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-D0L444Xe.js → diagram-OA4YK3LP-BZVJWnon.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-C3PEfMxp.js → diagram-WEI45ONY-B8UEevkd.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-Dknugppg.js → ebnfDiagram-CCIWWBDH-BGCxDQf1.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-Ds5u_SEw.js → erDiagram-Q63AITRT-CKnUuelS.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-DvqeSNur.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-DT_LFo10.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-KFFdgLKc.js → ganttDiagram-NO4QXBWP-2-k1ZZhN.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-B62DXBoO.js → gitGraph-TEB2WS4Q-Ci3lt5M_.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-BPtTY3Ld.js → gitGraphDiagram-IHSO6WYX-B_AQwuvk.js} +1 -1
- package/web-dist/assets/index-CSVjp00Q.css +1 -0
- package/web-dist/assets/index-D5VGTEZg.js +319 -0
- package/web-dist/assets/{info-DKCQHKI2-Ct-TkGHr.js → info-DKCQHKI2-m3mT757b.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-j0jFbDdJ.js → infoDiagram-FWYZ7A6U-CQw6f3Hj.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-DgfNQn9k.js → ishikawaDiagram-FXEZZL3T-C-7JgXGg.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-BfykOv3I.js → journeyDiagram-5HDEW3XC-DhPxGm-x.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-BvhHHrMj.js → kanban-definition-HUTT4EX6-pYAdpAN-.js} +1 -1
- package/web-dist/assets/{line-DZ-APW6S.js → line-DtHJLrdk.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-BWisef3-.js → mermaid-parser.core-CiDWeD1W.js} +3 -3
- package/web-dist/assets/{mermaid.core-DnlDGPhM.js → mermaid.core-BIRnljFs.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-BoTGWSEy.js → mindmap-definition-LN4V7U3C-AwCgXi_v.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-DaifVkPE.js → packet-7NZHBO7P-CnOMRKg1.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-DpLfBJwm.js → pegDiagram-2B236MQR-KwEiKY6p.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-BvB0Jxcm.js → pie-RZYD4A2V-BXVw5n_F.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-CSdBcMSK.js → pieDiagram-ENE6RG2P-BKeGzq7n.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-i7k-hoMS.js → quadrantDiagram-ABIIQ3AL-_B_KnoiC.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-DpnvOCzG.js → radar-I7S5WNFK-BDV-2CJh.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-Bj4Lhzob.js → railroad-3IZDKUUU-CJLCawbl.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-BLgEAPud.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-Czt3Iqq1.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-HPyLt5Zu.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-3HJeC9YI.js → railroadDiagram-RFXS5EU6-BVV6ZmA7.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-CeBCsZVw.js → requirementDiagram-TGXJPOKE-D3JoXNiN.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-BPhonqDa.js → sankeyDiagram-HTMAVEWB-D7r0TcuX.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-C5nocNwQ.js → sequenceDiagram-DBY2YBRQ-B35koHCj.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-CzAZsCPb.js → stateDiagram-2N3HPSRC-DrFIFrPb.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-D57MsDPe.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-DRn8Vs4e.js → swimlanes-5IMT3BWC-D14gjDvr.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-DC2nRhL8.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-Bc99eShR.js → timeline-definition-FHXFAJF6-DYuBi6LS.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ--Hn_52w1.js → treeView-QDETBFTQ-DWCgtuaU.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-BCI_8eXP.js → treemap-6X3UGDF4-QuJpQ-gt.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-BR-5GJF6.js → vennDiagram-L72KCM5P-BgtM82oJ.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-4HuUekM-.js → wardley-OPB4EBWU-CWh_aioC.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-BI9w8zpX.js → wardleyDiagram-EHGQE667-BNWSkBva.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-BZ72575j.js → xychartDiagram-FW5EYKEG-Bc6i6hg5.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/architecture-TIHT7OUA-BOgxi8qb.js +0 -1
- package/web-dist/assets/channel-cwFXt8Vr.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-BLP_ClYo.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-BLP_ClYo.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-DgF-cIkx.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-CB3C8FCr.js +0 -1
- package/web-dist/assets/index-B-zUKqGI.js +0 -319
- package/web-dist/assets/index-BIGkwpMf.css +0 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-scsBBZv_.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-z2-UIe31.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-Dm1rBPEF.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-DEGF_nZS.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-CBLKdauI.js +0 -8
package/dist/dev-child.js
CHANGED
|
@@ -13456,8 +13456,8 @@ function parkExpiredWakeMessage(status) {
|
|
|
13456
13456
|
...status,
|
|
13457
13457
|
"",
|
|
13458
13458
|
"Re-park if they are genuinely still going \u2014 there is no limit on that, and a long job is not a",
|
|
13459
|
-
"failure. If something is finished, read its result. If nothing is left, end in ```done or
|
|
13460
|
-
"
|
|
13459
|
+
"failure. If something is finished, read its result. If nothing is left, end in ```done or register",
|
|
13460
|
+
"a question with `mcp__frizz__ask`."
|
|
13461
13461
|
].join("\n");
|
|
13462
13462
|
}
|
|
13463
13463
|
function parkFinishedWakeMessage(status, several) {
|
|
@@ -13469,7 +13469,7 @@ function parkFinishedWakeMessage(status, several) {
|
|
|
13469
13469
|
"READ ITS OUTPUT AND CARRY ON. This is not a broken fence and there is nothing to fix: the wait",
|
|
13470
13470
|
"you declared simply ended. Do NOT relaunch the same work \u2014 its result is already on disk.",
|
|
13471
13471
|
"",
|
|
13472
|
-
"Then park on whatever comes next, or end in ```done or a
|
|
13472
|
+
"Then park on whatever comes next, or end in ```done or a registered question (`mcp__frizz__ask`) if nothing is left."
|
|
13473
13473
|
].join("\n");
|
|
13474
13474
|
}
|
|
13475
13475
|
function prWatchExpiredWakeMessage(ref) {
|
|
@@ -13483,6 +13483,12 @@ function ownWatchExpiredWakeMessage(kind, target) {
|
|
|
13483
13483
|
|
|
13484
13484
|
If you are still waiting on it, register it again with \`mcp__frizz__watch\` and a fresh \`for:\`. If you are not, and it was the only thing you were waiting on, end in a proper terminal state instead of parking on it again.`;
|
|
13485
13485
|
}
|
|
13486
|
+
function questionFencesLive(spawnedAt) {
|
|
13487
|
+
if (spawnedAt === void 0 || spawnedAt === null) return true;
|
|
13488
|
+
const at = typeof spawnedAt === "number" ? spawnedAt : Date.parse(spawnedAt);
|
|
13489
|
+
if (!Number.isFinite(at)) return true;
|
|
13490
|
+
return at < Date.parse(QUESTION_FENCE_RETIRED_AT);
|
|
13491
|
+
}
|
|
13486
13492
|
function askedQuestionDepth(q) {
|
|
13487
13493
|
let deepest = 1;
|
|
13488
13494
|
for (const option of q.options ?? []) {
|
|
@@ -13679,7 +13685,7 @@ function parseGithubWakeSteer(text2) {
|
|
|
13679
13685
|
function frizzRoute(path3) {
|
|
13680
13686
|
return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
|
|
13681
13687
|
}
|
|
13682
|
-
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, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, 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, 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, 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;
|
|
13688
|
+
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, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, 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;
|
|
13683
13689
|
var init_src = __esm({
|
|
13684
13690
|
"packages/shared/src/index.ts"() {
|
|
13685
13691
|
"use strict";
|
|
@@ -13803,7 +13809,14 @@ var init_src = __esm({
|
|
|
13803
13809
|
slug: external_exports.string(),
|
|
13804
13810
|
displayName: external_exports.string(),
|
|
13805
13811
|
defaultEffort: external_exports.string(),
|
|
13806
|
-
efforts: external_exports.array(external_exports.string())
|
|
13812
|
+
efforts: external_exports.array(external_exports.string()),
|
|
13813
|
+
// The model's stock context window and the largest one it accepts, in tokens, straight from the
|
|
13814
|
+
// cache's `context_window` / `max_context_window` (272K / 872K on GPT-5.6, 128K / 128K on Spark).
|
|
13815
|
+
// The Settings "Context window" presets are built from these so the drawer names the numbers codex
|
|
13816
|
+
// will actually run at (maintainer 2026-09-11: "reflect the actual numbers"). Optional: a cache from
|
|
13817
|
+
// before the fields existed, or the degraded fallback, simply carries neither.
|
|
13818
|
+
contextWindow: external_exports.number().int().positive().optional(),
|
|
13819
|
+
maxContextWindow: external_exports.number().int().positive().optional()
|
|
13807
13820
|
});
|
|
13808
13821
|
ThreadProfileOption = external_exports.object({
|
|
13809
13822
|
model: external_exports.string().min(1),
|
|
@@ -13990,7 +14003,7 @@ var init_src = __esm({
|
|
|
13990
14003
|
RETIRED_AWAITING_REPLACEMENT = {
|
|
13991
14004
|
"watch": "`shells: [<the id your runtime gave you>]` (or `agents: [<id>]`) \u2014 the same id, in the current sequence",
|
|
13992
14005
|
"pr-watch": "register the PR with `mcp__frizz__watch_pr`, then name it `prs: [owner/repo#123]`",
|
|
13993
|
-
"human": "there is no human gate any more \u2014 if you need a person,
|
|
14006
|
+
"human": "there is no human gate any more \u2014 if you need a person, register a question with `mcp__frizz__ask` instead of parking",
|
|
13994
14007
|
"ci": "CI is not a wait of its own: register the PR with `mcp__frizz__watch_pr` and you are woken when its checks settle",
|
|
13995
14008
|
"session": "there is no cross-session wait \u2014 name the sub-agent you dispatched with `agents: [<id>]`",
|
|
13996
14009
|
// THE 2026-08-24 CUTOVER. The frontmatter is YAML now, and YAML has no repeated keys — so the four
|
|
@@ -14050,7 +14063,7 @@ var init_src = __esm({
|
|
|
14050
14063
|
INTERRUPT_MARKERS = ["[Request interrupted by user]", "[Request interrupted by user for tool use]"];
|
|
14051
14064
|
ALLDONE_SENTINEL = "ALLDONE";
|
|
14052
14065
|
OPT_OUT_NOTE = "To stop these, sign off with a ```done fence \u2014 but ONLY when the work is genuinely finished: it files this thread away, and nothing but new work from the human reopens it.";
|
|
14053
|
-
OVER_QUESTION_NOTE = "Your
|
|
14066
|
+
OVER_QUESTION_NOTE = "Your registered question is still unanswered, and a Goal armed at rest means the operator is not waiting to answer it: decide it yourself, say in one line which way you went and what would reverse it, and carry on. Do NOT re-ask it.";
|
|
14054
14067
|
TIMER_PROMPT_MAX = SNOOZE_PROMPT_MAX;
|
|
14055
14068
|
TIMER_MAX_DELAY_SECONDS = 30 * 24 * 60 * 60;
|
|
14056
14069
|
TIMER_MAX_ARMED = 64;
|
|
@@ -14082,14 +14095,16 @@ var init_src = __esm({
|
|
|
14082
14095
|
"the write-up.",
|
|
14083
14096
|
"",
|
|
14084
14097
|
"**DECIDE RATHER THAN ASK.** Stop only for a decision that is genuinely the human's AND that blocks you",
|
|
14085
|
-
"right now:
|
|
14098
|
+
"right now: register that one with `mcp__frizz__ask`. Every other open choice INSIDE the task \u2014 a name, a",
|
|
14086
14099
|
"default, a reversible design call \u2014 is yours to make: decide it, say in one line which way you went and",
|
|
14087
14100
|
"what would reverse it, and carry on. A choice that would ENLARGE the task is not one of those: an",
|
|
14088
14101
|
"unanswered question is not permission to go build the answer.",
|
|
14089
14102
|
"",
|
|
14090
|
-
"Otherwise,
|
|
14103
|
+
"Otherwise, sign off \u2014 a registration, or a fence at the END of your next message:",
|
|
14091
14104
|
"",
|
|
14092
|
-
"-
|
|
14105
|
+
"- `mcp__frizz__ask` \u2014 you need the human. NOT a fence: the ```question fence is retired, and a fence",
|
|
14106
|
+
" with a question in its body is plain prose. Register it (options with one-line trade-offs, the",
|
|
14107
|
+
" recommended one first), then rest normally \u2014 an open registered question is the sign-off.",
|
|
14093
14108
|
"- `` ```done `` \u2014 genuinely FINISHED. A DISMISSAL: the card is filed away and nobody looks again, so",
|
|
14094
14109
|
" if anything is still owed, it is not done. Body: 1-3 sentences, then bullets, each opening with a",
|
|
14095
14110
|
" **bolded verb phrase**.",
|
|
@@ -14304,6 +14319,7 @@ var init_src = __esm({
|
|
|
14304
14319
|
watches: external_exports.array(OwnWatchView)
|
|
14305
14320
|
}).strict();
|
|
14306
14321
|
OWN_WATCH_MAX_ARMED = 24;
|
|
14322
|
+
QUESTION_FENCE_RETIRED_AT = "2026-09-11T17:30:00Z";
|
|
14307
14323
|
AskQuestionKind = external_exports.enum(["question", "multi"]);
|
|
14308
14324
|
ASK_MAX_DEPTH = 3;
|
|
14309
14325
|
AskedOptionSchema = external_exports.lazy(() => external_exports.object({
|
|
@@ -14444,6 +14460,20 @@ var init_src = __esm({
|
|
|
14444
14460
|
retrying: external_exports.boolean().optional(),
|
|
14445
14461
|
at: external_exports.string().optional()
|
|
14446
14462
|
});
|
|
14463
|
+
ThreadLinkView = external_exports.object({
|
|
14464
|
+
id: external_exports.string(),
|
|
14465
|
+
kind: external_exports.enum(["link", "file"]),
|
|
14466
|
+
label: external_exports.string(),
|
|
14467
|
+
target: external_exports.string()
|
|
14468
|
+
}).strict();
|
|
14469
|
+
UpsertOwnLinkInput = external_exports.object({
|
|
14470
|
+
slug: ThreadSlug,
|
|
14471
|
+
label: external_exports.string().trim().min(1).max(240).regex(/^[^\x00-\x1f\x7f]+$/),
|
|
14472
|
+
target: external_exports.string().trim().min(1).max(8192).regex(/^[^\x00-\x1f\x7f]+$/)
|
|
14473
|
+
}).strict();
|
|
14474
|
+
UpsertOwnLinkResult = external_exports.object({ link: ThreadLinkView }).strict();
|
|
14475
|
+
DropOwnLinkInput = external_exports.object({ slug: ThreadSlug, id: external_exports.string().min(1) }).strict();
|
|
14476
|
+
DropOwnLinkResult = external_exports.object({ dropped: external_exports.boolean() }).strict();
|
|
14447
14477
|
ThreadView = external_exports.object({
|
|
14448
14478
|
id: ThreadSlug,
|
|
14449
14479
|
// slug; filename is <slug>.md
|
|
@@ -14512,6 +14542,8 @@ var init_src = __esm({
|
|
|
14512
14542
|
// Live background SHELLS the worker launched (tailer-derived). Same default-[] discipline. Rendered
|
|
14513
14543
|
// in the anchored background-ops strip alongside sub-agents; ids make current rows drillable.
|
|
14514
14544
|
bgShells: external_exports.array(BgShellView).default([]),
|
|
14545
|
+
// Optional for snapshots from a server that predates link registration.
|
|
14546
|
+
links: external_exports.array(ThreadLinkView).optional(),
|
|
14515
14547
|
// The thread's ARMED WATCHERS — registry-derived, not folded from the transcript, which is what makes
|
|
14516
14548
|
// them survive the worker saying one more sentence. Same default-[] discipline as the two above.
|
|
14517
14549
|
//
|
|
@@ -14830,6 +14862,15 @@ var init_src = __esm({
|
|
|
14830
14862
|
// writes were 51% of a day's spend while the entries were invalidated every 15–30 minutes anyway
|
|
14831
14863
|
// (see claudePromptCacheEnv). Optional so an old blob parses; defaultSettings pins "auto".
|
|
14832
14864
|
promptCacheTtl: external_exports.enum(["auto", "5m", "1h"]).optional(),
|
|
14865
|
+
// The context window a NEW Codex thread runs with, in tokens. Codex's own default is the model's
|
|
14866
|
+
// stock window (272K on GPT-5.6), but the catalogue also carries a larger `max_context_window` (872K
|
|
14867
|
+
// on GPT-5.6-sol/terra/luna and GPT-6-astra, measured 2026-09-11 in ~/.codex/models_cache.json) that
|
|
14868
|
+
// a thread only reaches when asked. Reaches codex as the `model_context_window` config override on
|
|
14869
|
+
// `thread/start` and every cold `thread/resume` (see codexContextWindowConfig); codex clamps it to
|
|
14870
|
+
// the model's maximum, so a value above it is harmless, and it moves the auto-compact threshold with
|
|
14871
|
+
// it (codex compacts at 90% of the resolved window). Unset ⇒ nothing is sent and the model's stock
|
|
14872
|
+
// window applies. Optional so an old blob parses; defaultSettings leaves it unset.
|
|
14873
|
+
codexContextWindow: external_exports.number().int().positive().optional(),
|
|
14833
14874
|
// The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
|
|
14834
14875
|
// unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
|
|
14835
14876
|
// the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
|
|
@@ -15049,6 +15090,7 @@ var init_src = __esm({
|
|
|
15049
15090
|
}).strict();
|
|
15050
15091
|
OwnThreadActivityResult = external_exports.object({
|
|
15051
15092
|
activity: external_exports.array(ThreadActivityItem),
|
|
15093
|
+
links: external_exports.array(ThreadLinkView).optional(),
|
|
15052
15094
|
/** Every question still owed an answer. NOT a `ThreadActivityItem` and deliberately its own list: a
|
|
15053
15095
|
* question is not running work, it waits on a PERSON, and there is no `questions:` key in the awaiting
|
|
15054
15096
|
* fence for it to be written into. Until 2026-08-28 a worker could read its open questions back only
|
|
@@ -17788,7 +17830,7 @@ function rpcRequestIdJson(value) {
|
|
|
17788
17830
|
}
|
|
17789
17831
|
function canonicalJson(value) {
|
|
17790
17832
|
const visiting = /* @__PURE__ */ new WeakSet();
|
|
17791
|
-
const
|
|
17833
|
+
const normalize2 = (candidate) => {
|
|
17792
17834
|
if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean") return candidate;
|
|
17793
17835
|
if (typeof candidate === "number") {
|
|
17794
17836
|
if (!Number.isFinite(candidate)) throw new InteractionStoreError("invalid-response", "provider JSON contains a non-finite number");
|
|
@@ -17800,14 +17842,14 @@ function canonicalJson(value) {
|
|
|
17800
17842
|
if (visiting.has(candidate)) throw new InteractionStoreError("invalid-response", "provider JSON contains a cycle");
|
|
17801
17843
|
visiting.add(candidate);
|
|
17802
17844
|
try {
|
|
17803
|
-
if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null :
|
|
17845
|
+
if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null : normalize2(item));
|
|
17804
17846
|
const object = candidate;
|
|
17805
|
-
return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key,
|
|
17847
|
+
return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key, normalize2(object[key])]));
|
|
17806
17848
|
} finally {
|
|
17807
17849
|
visiting.delete(candidate);
|
|
17808
17850
|
}
|
|
17809
17851
|
};
|
|
17810
|
-
return JSON.stringify(
|
|
17852
|
+
return JSON.stringify(normalize2(value));
|
|
17811
17853
|
}
|
|
17812
17854
|
function boundedProviderJson(value) {
|
|
17813
17855
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -19385,6 +19427,20 @@ function createStorage(source, projectId) {
|
|
|
19385
19427
|
`);
|
|
19386
19428
|
const prWatchCursorStmt = scope.prepare("UPDATE pr_watch SET cursor = ? WHERE project_id = @project_id AND id = ? AND state = 'armed'");
|
|
19387
19429
|
const delPrWatches = scope.prepare("DELETE FROM pr_watch WHERE project_id = @project_id AND thread_slug = ?");
|
|
19430
|
+
const upsertThreadLinkStmt = scope.prepare(`
|
|
19431
|
+
INSERT INTO thread_link (project_id, id, thread_slug, kind, label, target, created_at)
|
|
19432
|
+
VALUES (@project_id, @id, @slug, @kind, @label, @target, @createdAtMs)
|
|
19433
|
+
ON CONFLICT (project_id, thread_slug, label) DO UPDATE SET kind = excluded.kind, target = excluded.target
|
|
19434
|
+
RETURNING *
|
|
19435
|
+
`);
|
|
19436
|
+
const threadLinksStmt = scope.prepare(
|
|
19437
|
+
"SELECT * FROM thread_link WHERE project_id = @project_id ORDER BY created_at, rowid"
|
|
19438
|
+
);
|
|
19439
|
+
const threadLinksBySlugStmt = scope.prepare(
|
|
19440
|
+
"SELECT * FROM thread_link WHERE project_id = @project_id AND thread_slug = ? ORDER BY created_at, rowid"
|
|
19441
|
+
);
|
|
19442
|
+
const dropThreadLinkStmt = scope.prepare("DELETE FROM thread_link WHERE project_id = @project_id AND thread_slug = ? AND id = ?");
|
|
19443
|
+
const delThreadLinks = scope.prepare("DELETE FROM thread_link WHERE project_id = @project_id AND thread_slug = ?");
|
|
19388
19444
|
const armThreadWatchStmt = scope.prepare(`
|
|
19389
19445
|
INSERT INTO thread_watch (project_id, id, thread_slug, kind, target, state, created_at, expires_at, settled_at)
|
|
19390
19446
|
VALUES (@project_id, @id, @slug, @kind, @target, 'armed', @createdAtMs, @expiresAtMs, NULL)
|
|
@@ -19524,6 +19580,7 @@ function createStorage(source, projectId) {
|
|
|
19524
19580
|
delThreadTimers.run(existing.slug);
|
|
19525
19581
|
delPrWatches.run(existing.slug);
|
|
19526
19582
|
delThreadWatches.run(existing.slug);
|
|
19583
|
+
delThreadLinks.run(existing.slug);
|
|
19527
19584
|
delThreadQuestions.run(existing.slug);
|
|
19528
19585
|
delThreadDone.run(existing.slug);
|
|
19529
19586
|
delSubAgentSteers.run(existing.slug);
|
|
@@ -19975,6 +20032,10 @@ function createStorage(source, projectId) {
|
|
|
19975
20032
|
dropPrWatch: (slug, id, settledAtMs) => dropPrWatchStmt.run(settledAtMs, id, slug).changes === 1,
|
|
19976
20033
|
settlePrWatch: (id, settledAtMs) => settlePrWatchStmt.run(settledAtMs, id).changes === 1,
|
|
19977
20034
|
setPrWatchCursor: (id, cursor) => prWatchCursorStmt.run(cursor, id).changes === 1,
|
|
20035
|
+
upsertThreadLink: (link) => upsertThreadLinkStmt.get(link),
|
|
20036
|
+
listThreadLinks: (slug) => threadLinksBySlugStmt.all(slug),
|
|
20037
|
+
threadLinksBySlug: () => groupBySlug(threadLinksStmt.all()),
|
|
20038
|
+
dropThreadLink: (slug, id) => dropThreadLinkStmt.run(slug, id).changes === 1,
|
|
19978
20039
|
// IDEMPOTENT BY (thread, kind, target), which is what the partial unique index enforces. A worker
|
|
19979
20040
|
// woken by an expiry re-registers the same wait, and a worker that simply calls twice must not end
|
|
19980
20041
|
// up with two rows to drop — so an existing armed row is RETURNED rather than replaced. Replacing
|
|
@@ -20436,6 +20497,17 @@ var init_storage = __esm({
|
|
|
20436
20497
|
ON pr_watch(project_id, state);
|
|
20437
20498
|
CREATE INDEX IF NOT EXISTS pr_watch_slug
|
|
20438
20499
|
ON pr_watch(project_id, thread_slug, state, created_at);
|
|
20500
|
+
-- Saved destinations have no liveness or expiry. The label is a stable slot within one thread.
|
|
20501
|
+
CREATE TABLE IF NOT EXISTS thread_link (
|
|
20502
|
+
id TEXT PRIMARY KEY,
|
|
20503
|
+
project_id TEXT NOT NULL,
|
|
20504
|
+
thread_slug TEXT NOT NULL,
|
|
20505
|
+
kind TEXT NOT NULL CHECK (kind IN ('link', 'file')),
|
|
20506
|
+
label TEXT NOT NULL,
|
|
20507
|
+
target TEXT NOT NULL,
|
|
20508
|
+
created_at INTEGER NOT NULL,
|
|
20509
|
+
UNIQUE (project_id, thread_slug, label)
|
|
20510
|
+
);
|
|
20439
20511
|
-- A worker's registered WATCHES on its own running work (2026-08-26). See
|
|
20440
20512
|
-- plans/rest-by-registration.md: a wait stops being a line the worker re-writes at every rest and
|
|
20441
20513
|
-- becomes a row it creates once, which the human sees in the queue and which wakes the thread itself.
|
|
@@ -20549,7 +20621,8 @@ var init_storage = __esm({
|
|
|
20549
20621
|
"thread_watch",
|
|
20550
20622
|
"thread_question",
|
|
20551
20623
|
"thread_done",
|
|
20552
|
-
"subagent_steer"
|
|
20624
|
+
"subagent_steer",
|
|
20625
|
+
"thread_link"
|
|
20553
20626
|
];
|
|
20554
20627
|
}
|
|
20555
20628
|
});
|
|
@@ -20694,6 +20767,9 @@ var init_settings = __esm({
|
|
|
20694
20767
|
// "auto" = the CLI's own tier (1h on a subscription). See the schema and claudePromptCacheEnv for
|
|
20695
20768
|
// when "5m" is the cheaper choice.
|
|
20696
20769
|
promptCacheTtl: "auto",
|
|
20770
|
+
// Unset = codex's own window for the model (272K on GPT-5.6). The schema and codexContextWindowConfig
|
|
20771
|
+
// explain the 872K ceiling a larger value is clamped to.
|
|
20772
|
+
codexContextWindow: void 0,
|
|
20697
20773
|
// Hidden until asked for — see the schema for why.
|
|
20698
20774
|
projectRail: false
|
|
20699
20775
|
});
|
|
@@ -21350,7 +21426,23 @@ function toCodexModel(raw2) {
|
|
|
21350
21426
|
const def = typeof m.default_reasoning_level === "string" ? m.default_reasoning_level : "";
|
|
21351
21427
|
const defaultEffort = def && efforts.includes(def) ? def : efforts[0];
|
|
21352
21428
|
const priority = typeof m.priority === "number" && Number.isFinite(m.priority) ? m.priority : Number.MAX_SAFE_INTEGER;
|
|
21353
|
-
|
|
21429
|
+
const window = (key) => {
|
|
21430
|
+
const v = m[key];
|
|
21431
|
+
return typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
|
|
21432
|
+
};
|
|
21433
|
+
const contextWindow = window("context_window");
|
|
21434
|
+
const maxContextWindow = window("max_context_window");
|
|
21435
|
+
return {
|
|
21436
|
+
model: {
|
|
21437
|
+
slug,
|
|
21438
|
+
displayName,
|
|
21439
|
+
defaultEffort,
|
|
21440
|
+
efforts,
|
|
21441
|
+
...contextWindow === void 0 ? {} : { contextWindow },
|
|
21442
|
+
...maxContextWindow === void 0 ? {} : { maxContextWindow }
|
|
21443
|
+
},
|
|
21444
|
+
priority
|
|
21445
|
+
};
|
|
21354
21446
|
}
|
|
21355
21447
|
function parseCodexModelsCache(raw2) {
|
|
21356
21448
|
let doc;
|
|
@@ -22098,6 +22190,35 @@ var init_detached_daemons = __esm({
|
|
|
22098
22190
|
}
|
|
22099
22191
|
});
|
|
22100
22192
|
|
|
22193
|
+
// packages/server/src/backend/daemon-tree.ts
|
|
22194
|
+
import { spawnSync } from "node:child_process";
|
|
22195
|
+
function endDaemonTree(pid, signal, deps = {}) {
|
|
22196
|
+
const kill = deps.kill ?? defaultKill;
|
|
22197
|
+
if ((deps.platform ?? process.platform) !== "win32") {
|
|
22198
|
+
try {
|
|
22199
|
+
kill(pid, signal);
|
|
22200
|
+
} catch {
|
|
22201
|
+
}
|
|
22202
|
+
return;
|
|
22203
|
+
}
|
|
22204
|
+
const killed = (deps.spawnSync ?? spawnSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
22205
|
+
if (killed.error || killed.status !== 0) {
|
|
22206
|
+
try {
|
|
22207
|
+
kill(pid, signal);
|
|
22208
|
+
} catch {
|
|
22209
|
+
}
|
|
22210
|
+
}
|
|
22211
|
+
}
|
|
22212
|
+
var defaultKill;
|
|
22213
|
+
var init_daemon_tree = __esm({
|
|
22214
|
+
"packages/server/src/backend/daemon-tree.ts"() {
|
|
22215
|
+
"use strict";
|
|
22216
|
+
defaultKill = (pid, signal) => {
|
|
22217
|
+
process.kill(pid, signal);
|
|
22218
|
+
};
|
|
22219
|
+
}
|
|
22220
|
+
});
|
|
22221
|
+
|
|
22101
22222
|
// packages/server/src/backend/ipc-path.ts
|
|
22102
22223
|
import { join as join15 } from "node:path";
|
|
22103
22224
|
function frizzIpcPath(name) {
|
|
@@ -22260,6 +22381,7 @@ function forkBroker(options) {
|
|
|
22260
22381
|
const socketPath = claudeBrokerSocketPath(options.stateDir, options.sessionId);
|
|
22261
22382
|
const recordPath2 = claudeBrokerRecordPath(options.stateDir, options.sessionId);
|
|
22262
22383
|
mkdirSync9(dirname8(recordPath2), { recursive: true });
|
|
22384
|
+
const generation = randomUUID8();
|
|
22263
22385
|
const config = {
|
|
22264
22386
|
socketPath,
|
|
22265
22387
|
cwd: options.cwd,
|
|
@@ -22268,7 +22390,7 @@ function forkBroker(options) {
|
|
|
22268
22390
|
permissionMode: options.permissionMode,
|
|
22269
22391
|
env: options.env,
|
|
22270
22392
|
recordPath: recordPath2,
|
|
22271
|
-
generation
|
|
22393
|
+
generation,
|
|
22272
22394
|
// The daemon writes its own death forensics here. Same dir as the record, so a session's socket,
|
|
22273
22395
|
// record and diagnostics stay together and a project teardown removes all three.
|
|
22274
22396
|
diagnosticLogPath: claudeBrokerDiagnosticLogPath(options.stateDir, options.sessionId),
|
|
@@ -22286,18 +22408,52 @@ function forkBroker(options) {
|
|
|
22286
22408
|
cwd: options.cwd,
|
|
22287
22409
|
env: { ...process.env, FRIZZ_CLAUDE_BROKER: JSON.stringify(config) },
|
|
22288
22410
|
detached: true,
|
|
22289
|
-
stdio: "ignore"
|
|
22411
|
+
stdio: "ignore",
|
|
22412
|
+
// A detached process owns no console on Windows; keep the daemon from ever being handed a
|
|
22413
|
+
// visible one (Windows audit 2026-09-11, finding 4 — the Codex daemon's spawn tells the story).
|
|
22414
|
+
windowsHide: true
|
|
22290
22415
|
});
|
|
22291
22416
|
child.unref();
|
|
22292
22417
|
const deadline = Date.now() + (options.timeoutMs ?? 3e4);
|
|
22418
|
+
const recordedCause = () => readClaudeBrokerExit(options.stateDir, options.sessionId, generation);
|
|
22293
22419
|
return new Promise((resolve12, reject) => {
|
|
22420
|
+
let settled = false;
|
|
22421
|
+
let timer;
|
|
22422
|
+
const settle = (outcome) => {
|
|
22423
|
+
if (settled) return;
|
|
22424
|
+
settled = true;
|
|
22425
|
+
if (timer) clearTimeout(timer);
|
|
22426
|
+
outcome();
|
|
22427
|
+
};
|
|
22294
22428
|
const poll = () => {
|
|
22295
22429
|
const record = readBrokerRecord(recordPath2);
|
|
22296
|
-
if (record && pidAlive(record.daemonPid)) return resolve12(record);
|
|
22297
|
-
if (Date.now() > deadline)
|
|
22298
|
-
|
|
22430
|
+
if (record && pidAlive(record.daemonPid)) return settle(() => resolve12(record));
|
|
22431
|
+
if (Date.now() > deadline) {
|
|
22432
|
+
const exit = recordedCause();
|
|
22433
|
+
return settle(() => reject(new Error(
|
|
22434
|
+
`Claude broker for session ${options.sessionId} did not become ready${exit ? `: ${describeClaudeBrokerExit(exit)}` : ""}`
|
|
22435
|
+
)));
|
|
22436
|
+
}
|
|
22437
|
+
timer = setTimeout(poll, 50);
|
|
22299
22438
|
};
|
|
22300
|
-
child.once("error", reject);
|
|
22439
|
+
child.once("error", (error) => settle(() => reject(error)));
|
|
22440
|
+
child.once("exit", (code, signal) => {
|
|
22441
|
+
if (settled) return;
|
|
22442
|
+
const how = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
|
|
22443
|
+
const until = Date.now() + 500;
|
|
22444
|
+
const read = () => {
|
|
22445
|
+
if (settled) return;
|
|
22446
|
+
const exit = recordedCause();
|
|
22447
|
+
if (!exit && Date.now() < until) {
|
|
22448
|
+
setTimeout(read, 25);
|
|
22449
|
+
return;
|
|
22450
|
+
}
|
|
22451
|
+
settle(() => reject(new Error(
|
|
22452
|
+
`Claude broker for session ${options.sessionId} exited before it became ready (${how}): ${describeClaudeBrokerExit(exit)}`
|
|
22453
|
+
)));
|
|
22454
|
+
};
|
|
22455
|
+
read();
|
|
22456
|
+
});
|
|
22301
22457
|
poll();
|
|
22302
22458
|
});
|
|
22303
22459
|
}
|
|
@@ -22306,7 +22462,7 @@ async function adoptOrForkBroker(options) {
|
|
|
22306
22462
|
if (existing) return { record: existing, reattached: true };
|
|
22307
22463
|
return { record: await forkBroker(options), reattached: false };
|
|
22308
22464
|
}
|
|
22309
|
-
function killBroker(stateDir, sessionId, retireReason) {
|
|
22465
|
+
function killBroker(stateDir, sessionId, retireReason, deps = {}) {
|
|
22310
22466
|
const recordPath2 = claudeBrokerRecordPath(stateDir, sessionId);
|
|
22311
22467
|
const record = liveBrokerRecord(recordPath2);
|
|
22312
22468
|
if (retireReason) {
|
|
@@ -22314,10 +22470,7 @@ function killBroker(stateDir, sessionId, retireReason) {
|
|
|
22314
22470
|
} else takeBrokerRetirement(stateDir, sessionId);
|
|
22315
22471
|
if (record) {
|
|
22316
22472
|
rememberBrokerIdentity(recordPath2, record);
|
|
22317
|
-
|
|
22318
|
-
process.kill(record.daemonPid, "SIGTERM");
|
|
22319
|
-
} catch {
|
|
22320
|
-
}
|
|
22473
|
+
endDaemonTree(record.daemonPid, "SIGTERM", deps);
|
|
22321
22474
|
}
|
|
22322
22475
|
try {
|
|
22323
22476
|
unlinkSync(recordPath2);
|
|
@@ -22331,6 +22484,7 @@ var init_claude_broker_host = __esm({
|
|
|
22331
22484
|
"use strict";
|
|
22332
22485
|
init_detached_daemons();
|
|
22333
22486
|
init_claude_broker_diagnostics();
|
|
22487
|
+
init_daemon_tree();
|
|
22334
22488
|
init_ipc_path();
|
|
22335
22489
|
WINDOWS_SHIM_TARGET = /^\s*"%dp0%\\(.+?)"/mu;
|
|
22336
22490
|
}
|
|
@@ -23286,10 +23440,30 @@ var init_tail_cache = __esm({
|
|
|
23286
23440
|
import { statSync as statSync8, openSync as openSync10, readSync as readSync4, closeSync as closeSync9, readdirSync as readdirSync5, realpathSync as realpathSync5, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, readFileSync as readFileSync12, existsSync as existsSync5 } from "node:fs";
|
|
23287
23441
|
import { execFile as execFile2 } from "node:child_process";
|
|
23288
23442
|
import { promisify as promisify2 } from "node:util";
|
|
23289
|
-
import { basename as basename4, join as join18 } from "node:path";
|
|
23443
|
+
import { basename as basename4, join as join18, win32 } from "node:path";
|
|
23290
23444
|
import { homedir as homedir7 } from "node:os";
|
|
23291
|
-
async function probeShellsAlive(outputFiles) {
|
|
23445
|
+
async function probeShellsAlive(outputFiles, opts = {}) {
|
|
23446
|
+
const platform3 = opts.platform ?? process.platform;
|
|
23447
|
+
const exec = opts.exec ?? execFileAsync;
|
|
23292
23448
|
const verdicts = /* @__PURE__ */ new Map();
|
|
23449
|
+
if (platform3 === "win32") {
|
|
23450
|
+
const present = outputFiles.filter((file) => {
|
|
23451
|
+
if (existsSync5(file)) return true;
|
|
23452
|
+
verdicts.set(file, void 0);
|
|
23453
|
+
return false;
|
|
23454
|
+
});
|
|
23455
|
+
if (present.length === 0) return verdicts;
|
|
23456
|
+
const [shell, args] = windowsShellHolderCommand(present, opts.env ?? process.env);
|
|
23457
|
+
let report;
|
|
23458
|
+
try {
|
|
23459
|
+
report = (await exec(shell, args, { encoding: "utf8", timeout: 8e3, windowsHide: true })).stdout;
|
|
23460
|
+
} catch {
|
|
23461
|
+
for (const file of present) verdicts.set(file, void 0);
|
|
23462
|
+
return verdicts;
|
|
23463
|
+
}
|
|
23464
|
+
for (const [file, alive] of parseWindowsShellHolderReport(String(report), present)) verdicts.set(file, alive);
|
|
23465
|
+
return verdicts;
|
|
23466
|
+
}
|
|
23293
23467
|
const live = [];
|
|
23294
23468
|
for (const file of outputFiles) {
|
|
23295
23469
|
if (!existsSync5(file)) {
|
|
@@ -23305,7 +23479,7 @@ async function probeShellsAlive(outputFiles) {
|
|
|
23305
23479
|
if (live.length === 0) return verdicts;
|
|
23306
23480
|
let stdout = "";
|
|
23307
23481
|
try {
|
|
23308
|
-
stdout = (await
|
|
23482
|
+
stdout = (await exec("lsof", ["-F", "pn", "--", ...live.map((f) => f.real)], { encoding: "utf8", timeout: 8e3 })).stdout;
|
|
23309
23483
|
} catch (err) {
|
|
23310
23484
|
const e = err;
|
|
23311
23485
|
const ran = typeof e.code === "number" && !e.killed;
|
|
@@ -23320,6 +23494,25 @@ async function probeShellsAlive(outputFiles) {
|
|
|
23320
23494
|
for (const f of live) verdicts.set(f.requested, held.has(f.real));
|
|
23321
23495
|
return verdicts;
|
|
23322
23496
|
}
|
|
23497
|
+
function windowsShellHolderCommand(paths, env = process.env) {
|
|
23498
|
+
const shell = win32.join(env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
23499
|
+
const list = paths.map((p) => `'${p.replace(/'/g, "''")}'`).join(",");
|
|
23500
|
+
const script = `foreach($p in @(${list})){try{$f=[System.IO.File]::Open($p,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None);$f.Close();'free'}catch [System.IO.IOException]{if((($_.Exception.HResult) -band 0xFFFF) -in 32,33){'held'}else{'unknown'}}catch{'unknown'}}`;
|
|
23501
|
+
return [shell, ["-NoProfile", "-NonInteractive", "-NoLogo", "-Command", script]];
|
|
23502
|
+
}
|
|
23503
|
+
function parseWindowsShellHolderReport(report, paths) {
|
|
23504
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
23505
|
+
const lines = report.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
23506
|
+
if (lines.length !== paths.length) {
|
|
23507
|
+
for (const file of paths) verdicts.set(file, void 0);
|
|
23508
|
+
return verdicts;
|
|
23509
|
+
}
|
|
23510
|
+
paths.forEach((file, i) => {
|
|
23511
|
+
const word = lines[i];
|
|
23512
|
+
verdicts.set(file, word === "held" ? true : word === "free" ? false : void 0);
|
|
23513
|
+
});
|
|
23514
|
+
return verdicts;
|
|
23515
|
+
}
|
|
23323
23516
|
function activityMinute(at) {
|
|
23324
23517
|
if (!at) return "";
|
|
23325
23518
|
const ms = Date.parse(at);
|
|
@@ -25403,18 +25596,20 @@ ${detail2}
|
|
|
25403
25596
|
}, delay2);
|
|
25404
25597
|
nudgeTimer.unref?.();
|
|
25405
25598
|
}
|
|
25406
|
-
function
|
|
25599
|
+
function currentRowFor(state) {
|
|
25407
25600
|
const current = deps.storage.getSession(state.slug);
|
|
25408
|
-
return
|
|
25409
|
-
|
|
25410
|
-
|
|
25601
|
+
return current && current.session_id === state.sessionId && (current.runtime_generation ?? 0) === state.runtimeGeneration ? current : void 0;
|
|
25602
|
+
}
|
|
25603
|
+
function registeredStateIsCurrent(state) {
|
|
25604
|
+
return currentRowFor(state) !== void 0;
|
|
25411
25605
|
}
|
|
25412
25606
|
return {
|
|
25413
25607
|
get(slug) {
|
|
25414
25608
|
const registered = states.get(slug);
|
|
25415
|
-
const
|
|
25609
|
+
const row = registered ? currentRowFor(registered) : void 0;
|
|
25610
|
+
const s = registered ? row ? registered : void 0 : foreignStates.get(slug);
|
|
25416
25611
|
if (!s) return void 0;
|
|
25417
|
-
const pendingQuestion = s.lastAssistantHasQuestion;
|
|
25612
|
+
const pendingQuestion = s.lastAssistantHasQuestion && questionFencesLive(row?.spawned_at);
|
|
25418
25613
|
const nowMs = now();
|
|
25419
25614
|
return { turn: s.turn, permPrompt: s.permPrompt, permPolicy: s.permPolicy, permDenies: s.permDenies, model: s.model, effort: s.effort, profileAt: s.profileAt, profileRevision: s.profileRevision, permissionMode: s.permissionMode, permissionModeAt: s.permissionModeAt, permissionModeRevision: s.permissionModeRevision, lastActivityAt: s.lastActivityAt, lastAssistantAt: s.lastAssistantAt, lastAssistant: s.lastAssistant, aiTitle: s.aiTitle, customTitle: s.customTitle, customTitleRevision: s.customTitleRevision, subAgents: subAgentViews(s, nowMs), droppedReports: [...s.queuedReports.values()], bgShells: [...bgShellViews(s), ...codexBgShellViews(s)], retiredShells: retiredShellViews(s), pendingAsk: s.pendingAsk, pendingQuestion, lastAssistantAllDone: s.lastAssistantAllDone, lastUserAt: s.lastUserAt, lastUserText: s.lastUserText, firstUserText: s.firstUserText, lastFence: s.lastFence, noTranscript: s.noTranscript, authFault: s.authFault, apiFault: s.apiFault, providerError: s.providerError, limitFault: s.limitFault, contextTokens: s.contextTokens, contextWindow: s.contextWindow, lastCompactionAt: s.lastCompactionAt };
|
|
25420
25615
|
},
|
|
@@ -25979,14 +26174,15 @@ function parseToolArguments(args) {
|
|
|
25979
26174
|
}
|
|
25980
26175
|
function imageField(output) {
|
|
25981
26176
|
if (!Array.isArray(output)) return {};
|
|
26177
|
+
const images = [];
|
|
25982
26178
|
for (const part of output) {
|
|
25983
26179
|
if (!part || typeof part !== "object") continue;
|
|
25984
26180
|
const p = part;
|
|
25985
26181
|
if (p.type !== "input_image" && p.type !== "output_image" && p.type !== "image") continue;
|
|
25986
26182
|
const url = typeof p.image_url === "string" ? p.image_url : typeof p.url === "string" ? p.url : void 0;
|
|
25987
|
-
if (url?.startsWith("data:image/"))
|
|
26183
|
+
if (url?.startsWith("data:image/")) images.push(url);
|
|
25988
26184
|
}
|
|
25989
|
-
return {};
|
|
26185
|
+
return images.length ? { image: images[0], images } : {};
|
|
25990
26186
|
}
|
|
25991
26187
|
function codexItemText(content) {
|
|
25992
26188
|
if (!Array.isArray(content)) return "";
|
|
@@ -26255,6 +26451,101 @@ var init_codex = __esm({
|
|
|
26255
26451
|
}
|
|
26256
26452
|
});
|
|
26257
26453
|
|
|
26454
|
+
// packages/server/src/backend/codex-executable.ts
|
|
26455
|
+
import { accessSync as accessSync2, constants as fsConstants2, readFileSync as readFileSync14 } from "node:fs";
|
|
26456
|
+
import { posix, win32 as win322 } from "node:path";
|
|
26457
|
+
function defaultAccess(path3, mode) {
|
|
26458
|
+
try {
|
|
26459
|
+
accessSync2(path3, mode === "executable" ? fsConstants2.X_OK : fsConstants2.F_OK);
|
|
26460
|
+
return true;
|
|
26461
|
+
} catch {
|
|
26462
|
+
return false;
|
|
26463
|
+
}
|
|
26464
|
+
}
|
|
26465
|
+
function defaultReadFile(path3) {
|
|
26466
|
+
try {
|
|
26467
|
+
return readFileSync14(path3, "utf8");
|
|
26468
|
+
} catch {
|
|
26469
|
+
return void 0;
|
|
26470
|
+
}
|
|
26471
|
+
}
|
|
26472
|
+
function searchPath2(env) {
|
|
26473
|
+
const direct = env.PATH ?? env.Path ?? env.path;
|
|
26474
|
+
if (direct !== void 0) return direct;
|
|
26475
|
+
for (const [key, value] of Object.entries(env)) if (key.toLowerCase() === "path") return value ?? "";
|
|
26476
|
+
return "";
|
|
26477
|
+
}
|
|
26478
|
+
function vendoredNativeBeside(launcherJs, arch, access) {
|
|
26479
|
+
const triple = WINDOWS_TRIPLE[arch];
|
|
26480
|
+
if (!triple) return void 0;
|
|
26481
|
+
const { dirname: dirname24, join: join46 } = win322;
|
|
26482
|
+
const pkgRoot = dirname24(dirname24(launcherJs));
|
|
26483
|
+
const platformPkg = `codex-win32-${arch}`;
|
|
26484
|
+
const vendorRoots = [
|
|
26485
|
+
join46(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
|
|
26486
|
+
// nested under the package
|
|
26487
|
+
join46(dirname24(pkgRoot), platformPkg, "vendor"),
|
|
26488
|
+
// hoisted beside it: <prefix>\node_modules\@openai\<pkg>
|
|
26489
|
+
join46(pkgRoot, "vendor")
|
|
26490
|
+
// the pre-platform-package layout, binaries inside @openai/codex itself
|
|
26491
|
+
];
|
|
26492
|
+
for (const root of vendorRoots) {
|
|
26493
|
+
const exe = join46(root, triple, "bin", "codex.exe");
|
|
26494
|
+
if (access(exe, "exists")) return exe;
|
|
26495
|
+
}
|
|
26496
|
+
return void 0;
|
|
26497
|
+
}
|
|
26498
|
+
function resolveCodexExecutable(bin, deps = {}) {
|
|
26499
|
+
const env = deps.env ?? process.env;
|
|
26500
|
+
const platform3 = deps.platform ?? process.platform;
|
|
26501
|
+
const arch = deps.arch ?? process.arch;
|
|
26502
|
+
const execPath = deps.execPath ?? process.execPath;
|
|
26503
|
+
const access = deps.access ?? defaultAccess;
|
|
26504
|
+
const readFile2 = deps.readFile ?? defaultReadFile;
|
|
26505
|
+
const windows = platform3 === "win32";
|
|
26506
|
+
const path3 = windows ? win322 : posix;
|
|
26507
|
+
const candidate = bin && bin.length > 0 ? bin : "codex";
|
|
26508
|
+
if (path3.isAbsolute(candidate) || /[\\/]/u.test(candidate)) return { file: candidate, args: [] };
|
|
26509
|
+
for (const dir of searchPath2(env).split(path3.delimiter)) {
|
|
26510
|
+
if (!dir) continue;
|
|
26511
|
+
if (!windows) {
|
|
26512
|
+
const full2 = path3.join(dir, candidate);
|
|
26513
|
+
if (access(full2, "executable")) return { file: full2, args: [] };
|
|
26514
|
+
continue;
|
|
26515
|
+
}
|
|
26516
|
+
const exe = path3.join(dir, `${candidate}.exe`);
|
|
26517
|
+
if (access(exe, "exists")) return { file: exe, args: [] };
|
|
26518
|
+
const body = readFile2(path3.join(dir, `${candidate}.cmd`));
|
|
26519
|
+
const target = body === void 0 ? void 0 : WINDOWS_SHIM_TARGET2.exec(body)?.[1];
|
|
26520
|
+
if (!target) continue;
|
|
26521
|
+
const full = path3.join(dir, target);
|
|
26522
|
+
if (!access(full, "exists")) continue;
|
|
26523
|
+
if (!/\.(?:js|mjs|cjs)$/iu.test(full)) return { file: full, args: [] };
|
|
26524
|
+
const native = vendoredNativeBeside(full, arch, access);
|
|
26525
|
+
if (native) return { file: native, args: [] };
|
|
26526
|
+
return { file: execPath, args: [full] };
|
|
26527
|
+
}
|
|
26528
|
+
throw new CodexExecutableNotFoundError(candidate);
|
|
26529
|
+
}
|
|
26530
|
+
var CodexExecutableNotFoundError, WINDOWS_SHIM_TARGET2, WINDOWS_TRIPLE;
|
|
26531
|
+
var init_codex_executable = __esm({
|
|
26532
|
+
"packages/server/src/backend/codex-executable.ts"() {
|
|
26533
|
+
"use strict";
|
|
26534
|
+
CodexExecutableNotFoundError = class extends Error {
|
|
26535
|
+
code = "ENOENT";
|
|
26536
|
+
constructor(candidate) {
|
|
26537
|
+
super(`could not resolve '${candidate}' to an executable on PATH`);
|
|
26538
|
+
this.name = "CodexExecutableNotFoundError";
|
|
26539
|
+
}
|
|
26540
|
+
};
|
|
26541
|
+
WINDOWS_SHIM_TARGET2 = /"%dp0%\\([^"]+)"\s+%\*/u;
|
|
26542
|
+
WINDOWS_TRIPLE = {
|
|
26543
|
+
x64: "x86_64-pc-windows-msvc",
|
|
26544
|
+
arm64: "aarch64-pc-windows-msvc"
|
|
26545
|
+
};
|
|
26546
|
+
}
|
|
26547
|
+
});
|
|
26548
|
+
|
|
26258
26549
|
// packages/server/src/backend/worker-env.ts
|
|
26259
26550
|
function isFrizzInternalEnvKey(key) {
|
|
26260
26551
|
return key.startsWith(FRIZZ_INTERNAL_PREFIX);
|
|
@@ -27623,14 +27914,14 @@ var require_receiver = __commonJS({
|
|
|
27623
27914
|
* @return {(Error|RangeError)} The error
|
|
27624
27915
|
* @private
|
|
27625
27916
|
*/
|
|
27626
|
-
createError(ErrorCtor, message, prefix, statusCode,
|
|
27917
|
+
createError(ErrorCtor, message, prefix, statusCode, errorCode5) {
|
|
27627
27918
|
this._loop = false;
|
|
27628
27919
|
this._errored = true;
|
|
27629
27920
|
const err = new ErrorCtor(
|
|
27630
27921
|
prefix ? `Invalid WebSocket frame: ${message}` : message
|
|
27631
27922
|
);
|
|
27632
27923
|
Error.captureStackTrace(err, this.createError);
|
|
27633
|
-
err.code =
|
|
27924
|
+
err.code = errorCode5;
|
|
27634
27925
|
err[kStatusCode] = statusCode;
|
|
27635
27926
|
return err;
|
|
27636
27927
|
}
|
|
@@ -29996,6 +30287,11 @@ function claudeCompactionEnv(settings) {
|
|
|
29996
30287
|
if (window === void 0 || !Number.isInteger(window) || window <= 0) return {};
|
|
29997
30288
|
return { [CLAUDE_AUTO_COMPACT_WINDOW_ENV]: String(window) };
|
|
29998
30289
|
}
|
|
30290
|
+
function codexContextWindowConfig(settings) {
|
|
30291
|
+
const window = settings?.codexContextWindow;
|
|
30292
|
+
if (window === void 0 || !Number.isInteger(window) || window <= 0) return {};
|
|
30293
|
+
return { [CODEX_CONTEXT_WINDOW_KEY]: window };
|
|
30294
|
+
}
|
|
29999
30295
|
function claudePromptCacheEnv(settings) {
|
|
30000
30296
|
const ttl = settings?.promptCacheTtl;
|
|
30001
30297
|
if (ttl !== "5m" && ttl !== "1h") return {};
|
|
@@ -30007,7 +30303,7 @@ function claudeCompactionWindowOf(env) {
|
|
|
30007
30303
|
const window = Number(raw2);
|
|
30008
30304
|
return Number.isInteger(window) && window > 0 ? window : void 0;
|
|
30009
30305
|
}
|
|
30010
|
-
var FRIZZ_MCP, WORKER_MAX_WEB_SEARCHES, WORKER_MAX_SUBAGENTS, WORKER_MAX_CONCURRENT_SUBAGENTS, CLAUDE_WORKER_ENV, CLAUDE_AUTO_COMPACT_WINDOW_ENV, CLAUDE_PROMPT_CACHE_TTL_ENV, CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV, WORKER_DISALLOWED_TOOLS;
|
|
30306
|
+
var FRIZZ_MCP, WORKER_MAX_WEB_SEARCHES, WORKER_MAX_SUBAGENTS, WORKER_MAX_CONCURRENT_SUBAGENTS, CLAUDE_WORKER_ENV, CLAUDE_AUTO_COMPACT_WINDOW_ENV, CODEX_CONTEXT_WINDOW_KEY, CLAUDE_PROMPT_CACHE_TTL_ENV, CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV, WORKER_DISALLOWED_TOOLS;
|
|
30011
30307
|
var init_types2 = __esm({
|
|
30012
30308
|
"packages/server/src/backend/types.ts"() {
|
|
30013
30309
|
"use strict";
|
|
@@ -30025,6 +30321,7 @@ var init_types2 = __esm({
|
|
|
30025
30321
|
BASH_MAX_TIMEOUT_MS: String(24 * 60 * 60 * 1e3)
|
|
30026
30322
|
};
|
|
30027
30323
|
CLAUDE_AUTO_COMPACT_WINDOW_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW";
|
|
30324
|
+
CODEX_CONTEXT_WINDOW_KEY = "model_context_window";
|
|
30028
30325
|
CLAUDE_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_PROMPT_CACHE_TTL";
|
|
30029
30326
|
CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL";
|
|
30030
30327
|
WORKER_DISALLOWED_TOOLS = ["AskUserQuestion"];
|
|
@@ -30088,7 +30385,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
30088
30385
|
import { connect } from "node:net";
|
|
30089
30386
|
import { Agent as HttpAgent } from "node:http";
|
|
30090
30387
|
import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
|
|
30091
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync11, readFileSync as
|
|
30388
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync11, readFileSync as readFileSync15, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
30092
30389
|
import { join as join20 } from "node:path";
|
|
30093
30390
|
import { PassThrough, Writable } from "node:stream";
|
|
30094
30391
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -30112,7 +30409,7 @@ function pidAlive2(pid) {
|
|
|
30112
30409
|
}
|
|
30113
30410
|
function readNativeRecord(stateDir, projectId) {
|
|
30114
30411
|
try {
|
|
30115
|
-
const value = JSON.parse(
|
|
30412
|
+
const value = JSON.parse(readFileSync15(nativeRecordPath(stateDir, projectId), "utf8"));
|
|
30116
30413
|
if (typeof value.listenerPid !== "number" || typeof value.socketPath !== "string" || typeof value.generation !== "string") return null;
|
|
30117
30414
|
return {
|
|
30118
30415
|
projectId,
|
|
@@ -30282,7 +30579,10 @@ async function startListener(options) {
|
|
|
30282
30579
|
cwd: options.cwd,
|
|
30283
30580
|
env: options.env,
|
|
30284
30581
|
detached: true,
|
|
30285
|
-
stdio: "ignore"
|
|
30582
|
+
stdio: "ignore",
|
|
30583
|
+
// No console window for codex.exe on Windows (Windows audit 2026-09-11, finding 4; the story is on
|
|
30584
|
+
// the daemon's spawn in codex-app-server-daemon.ts).
|
|
30585
|
+
windowsHide: true
|
|
30286
30586
|
});
|
|
30287
30587
|
child.unref();
|
|
30288
30588
|
const record = {
|
|
@@ -30354,7 +30654,7 @@ var init_codex_app_server_native = __esm({
|
|
|
30354
30654
|
};
|
|
30355
30655
|
}
|
|
30356
30656
|
log.error("codex", `codex app-server native listener unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a frizz restart`);
|
|
30357
|
-
const child = spawn2(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
30657
|
+
const child = spawn2(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
30358
30658
|
return { process: child, generation: randomUUID9(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
|
|
30359
30659
|
}
|
|
30360
30660
|
};
|
|
@@ -30362,10 +30662,10 @@ var init_codex_app_server_native = __esm({
|
|
|
30362
30662
|
});
|
|
30363
30663
|
|
|
30364
30664
|
// packages/server/src/backend/codex-app-server-host.ts
|
|
30365
|
-
import { spawn as spawn3
|
|
30665
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
30366
30666
|
import { createConnection } from "node:net";
|
|
30367
30667
|
import { createHash as createHash8, randomUUID as randomUUID10 } from "node:crypto";
|
|
30368
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync12, readFileSync as
|
|
30668
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync12, readFileSync as readFileSync16, unlinkSync as unlinkSync3 } from "node:fs";
|
|
30369
30669
|
import { join as join21 } from "node:path";
|
|
30370
30670
|
import { PassThrough as PassThrough2, Writable as Writable2 } from "node:stream";
|
|
30371
30671
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
@@ -30389,7 +30689,7 @@ function pidAlive3(pid) {
|
|
|
30389
30689
|
}
|
|
30390
30690
|
function readDaemonRecord(stateDir, projectId) {
|
|
30391
30691
|
try {
|
|
30392
|
-
const value = JSON.parse(
|
|
30692
|
+
const value = JSON.parse(readFileSync16(recordPath(stateDir, projectId), "utf8"));
|
|
30393
30693
|
if (typeof value.daemonPid !== "number" || typeof value.socketPath !== "string" || typeof value.generation !== "string") return null;
|
|
30394
30694
|
return {
|
|
30395
30695
|
projectId,
|
|
@@ -30406,7 +30706,7 @@ function readDaemonRecord(stateDir, projectId) {
|
|
|
30406
30706
|
}
|
|
30407
30707
|
function readDaemonExitBreadcrumb(stateDir, projectId) {
|
|
30408
30708
|
try {
|
|
30409
|
-
const value = JSON.parse(
|
|
30709
|
+
const value = JSON.parse(readFileSync16(`${recordPath(stateDir, projectId)}.exit`, "utf8"));
|
|
30410
30710
|
if (typeof value.generation !== "string" || typeof value.reason !== "string") return null;
|
|
30411
30711
|
return {
|
|
30412
30712
|
generation: value.generation,
|
|
@@ -30429,22 +30729,6 @@ function liveDaemonRecord(stateDir, projectId) {
|
|
|
30429
30729
|
}
|
|
30430
30730
|
return null;
|
|
30431
30731
|
}
|
|
30432
|
-
function endDaemonTree(pid, signal) {
|
|
30433
|
-
if (process.platform !== "win32") {
|
|
30434
|
-
try {
|
|
30435
|
-
process.kill(pid, signal);
|
|
30436
|
-
} catch {
|
|
30437
|
-
}
|
|
30438
|
-
return;
|
|
30439
|
-
}
|
|
30440
|
-
const killed = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
30441
|
-
if (killed.error || killed.status !== 0) {
|
|
30442
|
-
try {
|
|
30443
|
-
process.kill(pid, signal);
|
|
30444
|
-
} catch {
|
|
30445
|
-
}
|
|
30446
|
-
}
|
|
30447
|
-
}
|
|
30448
30732
|
function killCodexAppServerDaemon(stateDir, projectId) {
|
|
30449
30733
|
const record = liveDaemonRecord(stateDir, projectId);
|
|
30450
30734
|
if (!record) return;
|
|
@@ -30505,7 +30789,11 @@ function forkDaemon(options) {
|
|
|
30505
30789
|
// the payload and is applied by the daemon, keeping the audited env allowlist authoritative.
|
|
30506
30790
|
env: { ...process.env, FRIZZ_CODEX_APP_SERVER_DAEMON: payload },
|
|
30507
30791
|
detached: true,
|
|
30508
|
-
stdio: "ignore"
|
|
30792
|
+
stdio: "ignore",
|
|
30793
|
+
// Belt to the daemon's own braces: a detached process owns no console on Windows, so anything
|
|
30794
|
+
// console-subsystem it starts without CREATE_NO_WINDOW gets a fresh visible one (Windows audit
|
|
30795
|
+
// 2026-09-11, finding 4). The daemon hides its app-server; this keeps the daemon itself hidden.
|
|
30796
|
+
windowsHide: true
|
|
30509
30797
|
});
|
|
30510
30798
|
child.unref();
|
|
30511
30799
|
const deadline = Date.now() + (options.timeoutMs ?? 3e4);
|
|
@@ -30610,7 +30898,11 @@ function inProcessCodexAppServer(options) {
|
|
|
30610
30898
|
const child = spawn3(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), {
|
|
30611
30899
|
cwd: options.cwd,
|
|
30612
30900
|
env: options.env,
|
|
30613
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
30901
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
30902
|
+
// No console window for codex.exe (Windows audit 2026-09-11, finding 4). The server child has a
|
|
30903
|
+
// console of its own so this site was not the visible-window bug, but a codex.exe that inherits
|
|
30904
|
+
// it dies with it, and the shape test in daemon-tree.test.ts holds every provider spawn to it.
|
|
30905
|
+
windowsHide: true
|
|
30614
30906
|
});
|
|
30615
30907
|
const kill = child.kill.bind(child);
|
|
30616
30908
|
child.kill = ((signal) => {
|
|
@@ -30637,6 +30929,7 @@ var init_codex_app_server_host = __esm({
|
|
|
30637
30929
|
"packages/server/src/backend/codex-app-server-host.ts"() {
|
|
30638
30930
|
"use strict";
|
|
30639
30931
|
init_detached_daemons();
|
|
30932
|
+
init_daemon_tree();
|
|
30640
30933
|
init_codex_app_server_native();
|
|
30641
30934
|
init_ipc_path();
|
|
30642
30935
|
init_codex_mcp();
|
|
@@ -31084,7 +31377,7 @@ var init_claude_quota = __esm({
|
|
|
31084
31377
|
// packages/server/src/backend/auth-status.ts
|
|
31085
31378
|
import { join as join23 } from "node:path";
|
|
31086
31379
|
import { homedir as homedir10, platform as platform2 } from "node:os";
|
|
31087
|
-
import { readFileSync as
|
|
31380
|
+
import { readFileSync as readFileSync17, statSync as statSync10 } from "node:fs";
|
|
31088
31381
|
import { execFile as execFile4 } from "node:child_process";
|
|
31089
31382
|
import { promisify as promisify4 } from "node:util";
|
|
31090
31383
|
function claudeConfigDir2() {
|
|
@@ -31094,7 +31387,7 @@ function claudeConfigDir2() {
|
|
|
31094
31387
|
function claudeFileState(configDir) {
|
|
31095
31388
|
let raw2;
|
|
31096
31389
|
try {
|
|
31097
|
-
raw2 =
|
|
31390
|
+
raw2 = readFileSync17(join23(configDir, ".credentials.json"), "utf8");
|
|
31098
31391
|
} catch (err) {
|
|
31099
31392
|
return err.code === "ENOENT" ? "absent" : "error";
|
|
31100
31393
|
}
|
|
@@ -31127,7 +31420,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
|
|
|
31127
31420
|
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
|
|
31128
31421
|
let raw2;
|
|
31129
31422
|
try {
|
|
31130
|
-
raw2 =
|
|
31423
|
+
raw2 = readFileSync17(join23(codexHome, "auth.json"), "utf8");
|
|
31131
31424
|
} catch (err) {
|
|
31132
31425
|
return err.code === "ENOENT" ? "signed-out" : "unknown";
|
|
31133
31426
|
}
|
|
@@ -31147,7 +31440,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
|
|
|
31147
31440
|
function readCodexAccountId(codexHome = defaultCodexHome()) {
|
|
31148
31441
|
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return void 0;
|
|
31149
31442
|
try {
|
|
31150
|
-
const doc = JSON.parse(
|
|
31443
|
+
const doc = JSON.parse(readFileSync17(join23(codexHome, "auth.json"), "utf8"));
|
|
31151
31444
|
if (!doc || typeof doc !== "object") return void 0;
|
|
31152
31445
|
const tokens = doc.tokens;
|
|
31153
31446
|
if (!tokens || typeof tokens !== "object") return void 0;
|
|
@@ -31157,9 +31450,10 @@ function readCodexAccountId(codexHome = defaultCodexHome()) {
|
|
|
31157
31450
|
return void 0;
|
|
31158
31451
|
}
|
|
31159
31452
|
}
|
|
31160
|
-
async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
|
|
31453
|
+
async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3, resolve12 = resolveCodexExecutable) {
|
|
31161
31454
|
try {
|
|
31162
|
-
|
|
31455
|
+
const codex = resolve12(codexBin);
|
|
31456
|
+
await exec(codex.file, [...codex.args, "--version"], { timeout: 5e3 });
|
|
31163
31457
|
return "present";
|
|
31164
31458
|
} catch (err) {
|
|
31165
31459
|
return err.code === "ENOENT" ? "missing" : "unknown";
|
|
@@ -31218,7 +31512,7 @@ function readClaudeAccountEmail(path3 = claudeAccountFile()) {
|
|
|
31218
31512
|
if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
|
|
31219
31513
|
let email;
|
|
31220
31514
|
try {
|
|
31221
|
-
const doc = JSON.parse(
|
|
31515
|
+
const doc = JSON.parse(readFileSync17(path3, "utf8"));
|
|
31222
31516
|
email = asEmail(doc?.oauthAccount?.emailAddress);
|
|
31223
31517
|
} catch {
|
|
31224
31518
|
email = void 0;
|
|
@@ -31229,7 +31523,7 @@ function readClaudeAccountEmail(path3 = claudeAccountFile()) {
|
|
|
31229
31523
|
function readCodexAccountEmail(codexHome = defaultCodexHome()) {
|
|
31230
31524
|
let doc;
|
|
31231
31525
|
try {
|
|
31232
|
-
doc = JSON.parse(
|
|
31526
|
+
doc = JSON.parse(readFileSync17(join23(codexHome, "auth.json"), "utf8"));
|
|
31233
31527
|
} catch {
|
|
31234
31528
|
return void 0;
|
|
31235
31529
|
}
|
|
@@ -31284,6 +31578,7 @@ var init_auth_status = __esm({
|
|
|
31284
31578
|
init_claude_quota();
|
|
31285
31579
|
init_codex();
|
|
31286
31580
|
init_claude_broker_host();
|
|
31581
|
+
init_codex_executable();
|
|
31287
31582
|
execFileAsync3 = promisify4(execFile4);
|
|
31288
31583
|
ProviderAuthRequiredError = class extends Error {
|
|
31289
31584
|
backend;
|
|
@@ -31624,7 +31919,7 @@ function fileChangeDisplays(changes) {
|
|
|
31624
31919
|
}
|
|
31625
31920
|
function canonicalJson2(value) {
|
|
31626
31921
|
const visiting = /* @__PURE__ */ new WeakSet();
|
|
31627
|
-
const
|
|
31922
|
+
const normalize2 = (candidate) => {
|
|
31628
31923
|
if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean") return candidate;
|
|
31629
31924
|
if (typeof candidate === "number") {
|
|
31630
31925
|
if (!Number.isFinite(candidate)) throw new Error("non-finite JSON number");
|
|
@@ -31634,14 +31929,14 @@ function canonicalJson2(value) {
|
|
|
31634
31929
|
if (visiting.has(candidate)) throw new Error("cyclic JSON value");
|
|
31635
31930
|
visiting.add(candidate);
|
|
31636
31931
|
try {
|
|
31637
|
-
if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null :
|
|
31932
|
+
if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null : normalize2(item));
|
|
31638
31933
|
const object = candidate;
|
|
31639
|
-
return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key,
|
|
31934
|
+
return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key, normalize2(object[key])]));
|
|
31640
31935
|
} finally {
|
|
31641
31936
|
visiting.delete(candidate);
|
|
31642
31937
|
}
|
|
31643
31938
|
};
|
|
31644
|
-
return JSON.stringify(
|
|
31939
|
+
return JSON.stringify(normalize2(value));
|
|
31645
31940
|
}
|
|
31646
31941
|
function logicalRequestId(method, parts) {
|
|
31647
31942
|
const digest = createHash10("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
|
|
@@ -31957,6 +32252,7 @@ var init_codex_app_server = __esm({
|
|
|
31957
32252
|
init_auth_status();
|
|
31958
32253
|
init_codex_mcp();
|
|
31959
32254
|
init_codex_error();
|
|
32255
|
+
init_types2();
|
|
31960
32256
|
init_logging();
|
|
31961
32257
|
CODEX_APP_SERVER_PROVIDER = "codex-app-server";
|
|
31962
32258
|
CODEX_NATIVE_LISTEN_FLAG = "FRIZZ_CODEX_NATIVE_LISTEN";
|
|
@@ -33624,19 +33920,29 @@ var init_codex_app_server = __esm({
|
|
|
33624
33920
|
*/
|
|
33625
33921
|
/**
|
|
33626
33922
|
* The `config` bag for one thread's `thread/start` / `thread/resume`: frizz's MCP server mounted
|
|
33627
|
-
* PER THREAD so it knows who is calling,
|
|
33923
|
+
* PER THREAD so it knows who is calling, the operator's Codex context window from Settings, plus
|
|
33924
|
+
* whatever the caller passed.
|
|
33628
33925
|
*
|
|
33629
33926
|
* This is the only channel that can carry a caller identity on codex. The argv mount on the
|
|
33630
33927
|
* app-server is process-wide and serves every thread in the project, so `FRIZZ_THREAD_SLUG` was
|
|
33631
33928
|
* simply absent and every tool that acts on the caller's own thread failed at the moment of use —
|
|
33632
33929
|
* see `codexThreadMcpConfig` for the list and the measurement. Returns `{}` when there is nothing
|
|
33633
|
-
* to send, so a caller with no config
|
|
33634
|
-
* exactly as before.
|
|
33930
|
+
* to send, so a caller with no config, no resolved MCP descriptor and no context-window setting
|
|
33931
|
+
* sends no `config` key at all, exactly as before.
|
|
33932
|
+
*
|
|
33933
|
+
* The context window rides the same bag rather than the process-wide argv because the app-server is
|
|
33934
|
+
* a long-lived daemon: an argv value would be frozen at its first spawn, while a per-thread override
|
|
33935
|
+
* reads Settings at every start and every cold resume — the same "next thread, and a thread that
|
|
33936
|
+
* comes back" semantics the Claude compaction window has (see codexContextWindowConfig).
|
|
33635
33937
|
*
|
|
33636
33938
|
* The caller's own config wins on a key collision: this is a default, not an override.
|
|
33637
33939
|
*/
|
|
33638
33940
|
threadConfig(threadSlug, callerConfig) {
|
|
33639
|
-
const config = {
|
|
33941
|
+
const config = {
|
|
33942
|
+
...codexThreadMcpConfig(this.options.frizzMcp, threadSlug),
|
|
33943
|
+
...codexContextWindowConfig(this.options.getSettings?.()),
|
|
33944
|
+
...callerConfig
|
|
33945
|
+
};
|
|
33640
33946
|
return Object.keys(config).length ? { config } : {};
|
|
33641
33947
|
}
|
|
33642
33948
|
resumeSandboxOverride(row) {
|
|
@@ -34522,9 +34828,12 @@ function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex"
|
|
|
34522
34828
|
return new Promise((resolve12) => {
|
|
34523
34829
|
let child;
|
|
34524
34830
|
try {
|
|
34525
|
-
|
|
34831
|
+
const codex = resolveCodexExecutable(codexBin);
|
|
34832
|
+
child = spawn4(codex.file, [...codex.args, "app-server"], {
|
|
34526
34833
|
stdio: ["pipe", "pipe", "ignore"],
|
|
34527
|
-
env: { ...process.env, CODEX_HOME: codexHome }
|
|
34834
|
+
env: { ...process.env, CODEX_HOME: codexHome },
|
|
34835
|
+
// No console window for codex.exe (Windows audit 2026-09-11, finding 4).
|
|
34836
|
+
windowsHide: true
|
|
34528
34837
|
});
|
|
34529
34838
|
} catch {
|
|
34530
34839
|
resolve12(void 0);
|
|
@@ -34604,6 +34913,7 @@ var init_codex_quota = __esm({
|
|
|
34604
34913
|
"packages/server/src/backend/codex-quota.ts"() {
|
|
34605
34914
|
"use strict";
|
|
34606
34915
|
init_codex();
|
|
34916
|
+
init_codex_executable();
|
|
34607
34917
|
init_codex_app_server();
|
|
34608
34918
|
descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
|
|
34609
34919
|
TAIL_BYTES = 512 * 1024;
|
|
@@ -34681,22 +34991,181 @@ var init_quota = __esm({
|
|
|
34681
34991
|
}
|
|
34682
34992
|
});
|
|
34683
34993
|
|
|
34994
|
+
// packages/server/src/local-file.ts
|
|
34995
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
34996
|
+
import { existsSync as existsSync8, readFileSync as readFileSync18, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
|
|
34997
|
+
import { homedir as homedir11 } from "node:os";
|
|
34998
|
+
import { isAbsolute as isAbsolute4, join as join25, resolve as resolve5, sep as sep2 } from "node:path";
|
|
34999
|
+
function isUnder(real, root) {
|
|
35000
|
+
let rootReal;
|
|
35001
|
+
try {
|
|
35002
|
+
rootReal = realpathSync6(root);
|
|
35003
|
+
} catch {
|
|
35004
|
+
return false;
|
|
35005
|
+
}
|
|
35006
|
+
return real === rootReal || real.startsWith(rootReal.endsWith(sep2) ? rootReal : rootReal + sep2);
|
|
35007
|
+
}
|
|
35008
|
+
function resolveLocalFile(rawPath, roots) {
|
|
35009
|
+
if (!isAbsolute4(rawPath)) throw new Error("Local path must be absolute");
|
|
35010
|
+
let real;
|
|
35011
|
+
try {
|
|
35012
|
+
real = realpathSync6(rawPath);
|
|
35013
|
+
} catch {
|
|
35014
|
+
throw new Error("Local file was not found");
|
|
35015
|
+
}
|
|
35016
|
+
if (!roots.some((root) => isUnder(real, root))) throw new Error("Local file is outside Frizz's trusted roots");
|
|
35017
|
+
try {
|
|
35018
|
+
if (!statSync12(real).isFile()) throw new Error("Local path is not a regular file");
|
|
35019
|
+
} catch (error) {
|
|
35020
|
+
if (error instanceof Error && error.message === "Local path is not a regular file") throw error;
|
|
35021
|
+
throw new Error("Local file was not found");
|
|
35022
|
+
}
|
|
35023
|
+
return real;
|
|
35024
|
+
}
|
|
35025
|
+
function resolveOpenableFile(raw2, projectDir, roots, home = homedir11()) {
|
|
35026
|
+
const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
|
|
35027
|
+
if (!trimmed) return null;
|
|
35028
|
+
const abs = trimmed === "~" ? home : /^~[\/\\]/.test(trimmed) ? join25(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve5(projectDir, trimmed);
|
|
35029
|
+
try {
|
|
35030
|
+
return resolveLocalFile(abs, roots);
|
|
35031
|
+
} catch {
|
|
35032
|
+
return null;
|
|
35033
|
+
}
|
|
35034
|
+
}
|
|
35035
|
+
function readLocalTextFile(rawPath, roots) {
|
|
35036
|
+
const path3 = resolveLocalFile(rawPath, roots);
|
|
35037
|
+
const bytes = readFileSync18(path3);
|
|
35038
|
+
if (bytes.subarray(0, 8192).includes(0)) throw new Error("Local file is not a text file");
|
|
35039
|
+
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, text: bytes.toString("utf8"), truncated: false };
|
|
35040
|
+
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
35041
|
+
const lastBreak = head.lastIndexOf(10);
|
|
35042
|
+
return { path: path3, text: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
|
|
35043
|
+
}
|
|
35044
|
+
function readLocalMarkdown(rawPath, roots) {
|
|
35045
|
+
if (!MARKDOWN_FILE_EXT.test(rawPath.trim())) throw new Error("Local file is not a Markdown file");
|
|
35046
|
+
const path3 = resolveLocalFile(rawPath, roots);
|
|
35047
|
+
if (!MARKDOWN_FILE_EXT.test(path3)) throw new Error("Local file is not a Markdown file");
|
|
35048
|
+
const bytes = readFileSync18(path3);
|
|
35049
|
+
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, markdown: bytes.toString("utf8"), truncated: false };
|
|
35050
|
+
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
35051
|
+
const lastBreak = head.lastIndexOf(10);
|
|
35052
|
+
return { path: path3, markdown: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
|
|
35053
|
+
}
|
|
35054
|
+
function resolveWatchableLocalFile(rawPath, roots) {
|
|
35055
|
+
const path3 = resolveLocalFile(rawPath, roots);
|
|
35056
|
+
if (MARKDOWN_FILE_EXT.test(rawPath.trim()) && !MARKDOWN_FILE_EXT.test(path3)) {
|
|
35057
|
+
throw new Error("Local file is not a Markdown file");
|
|
35058
|
+
}
|
|
35059
|
+
return path3;
|
|
35060
|
+
}
|
|
35061
|
+
function defaultSpawn(command, args, options) {
|
|
35062
|
+
return spawn5(command, [...args], options);
|
|
35063
|
+
}
|
|
35064
|
+
async function awaitOpenerStart(child, command) {
|
|
35065
|
+
child.unref();
|
|
35066
|
+
await new Promise((resolve12, reject) => {
|
|
35067
|
+
child.once("error", (error) => reject(new Error(
|
|
35068
|
+
error.code === "ENOENT" ? `${command} is not installed or not on PATH` : `could not start ${command}: ${error.message}`
|
|
35069
|
+
)));
|
|
35070
|
+
child.once("spawn", resolve12);
|
|
35071
|
+
});
|
|
35072
|
+
}
|
|
35073
|
+
function localFileOpenCommand(path3, selected, options = {}) {
|
|
35074
|
+
const platform3 = options.platform ?? process.platform;
|
|
35075
|
+
if (platform3 === "darwin") {
|
|
35076
|
+
return selected === "cursor" ? { command: "open", args: ["-a", "Cursor", path3] } : selected === "vscode" ? { command: "open", args: ["-a", "Visual Studio Code", path3] } : selected === "finder" ? { command: "open", args: ["-R", path3] } : { command: "open", args: [path3] };
|
|
35077
|
+
}
|
|
35078
|
+
if (platform3 === "win32") {
|
|
35079
|
+
if (selected === "system") return { command: "explorer.exe", args: [path3] };
|
|
35080
|
+
if (selected === "finder") return { command: "explorer.exe", args: [`/select,${path3}`] };
|
|
35081
|
+
const editor = WINDOWS_EDITOR_EXE[selected];
|
|
35082
|
+
const env = options.env ?? process.env;
|
|
35083
|
+
const exists = options.exists ?? existsSync8;
|
|
35084
|
+
const localAppData = env.LOCALAPPDATA;
|
|
35085
|
+
if (localAppData) {
|
|
35086
|
+
const exe = join25(localAppData, ...editor.exe);
|
|
35087
|
+
if (exists(exe)) return { command: exe, args: [path3] };
|
|
35088
|
+
}
|
|
35089
|
+
if (/["%\r\n]/u.test(path3)) throw new Error(`cannot hand ${path3} to the ${editor.shim} shim through cmd.exe`);
|
|
35090
|
+
return { command: "cmd.exe", args: ["/d", "/s", "/c", `"${editor.shim} "${path3}""`], verbatim: true };
|
|
35091
|
+
}
|
|
35092
|
+
return selected === "cursor" ? { command: "cursor", args: [path3] } : selected === "vscode" ? { command: "code", args: [path3] } : { command: "xdg-open", args: [path3] };
|
|
35093
|
+
}
|
|
35094
|
+
async function openLocalFile(rawPath, opener, roots, options = {}) {
|
|
35095
|
+
const path3 = resolveLocalFile(rawPath, roots);
|
|
35096
|
+
const selected = options.forceSystem ? "system" : opener;
|
|
35097
|
+
if (selected === "copy") return { action: "copy", path: path3 };
|
|
35098
|
+
const spec = localFileOpenCommand(path3, selected, options);
|
|
35099
|
+
const child = (options.spawn ?? defaultSpawn)(spec.command, spec.args, {
|
|
35100
|
+
detached: true,
|
|
35101
|
+
stdio: "ignore",
|
|
35102
|
+
shell: false,
|
|
35103
|
+
windowsHide: true,
|
|
35104
|
+
...spec.verbatim ? { windowsVerbatimArguments: true } : {}
|
|
35105
|
+
});
|
|
35106
|
+
await awaitOpenerStart(child, spec.command);
|
|
35107
|
+
return { action: "opened", path: path3 };
|
|
35108
|
+
}
|
|
35109
|
+
var MARKDOWN_FILE_EXT, MARKDOWN_READ_LIMIT, WINDOWS_EDITOR_EXE;
|
|
35110
|
+
var init_local_file = __esm({
|
|
35111
|
+
"packages/server/src/local-file.ts"() {
|
|
35112
|
+
"use strict";
|
|
35113
|
+
MARKDOWN_FILE_EXT = /\.(?:md|mdx|markdown)$/i;
|
|
35114
|
+
MARKDOWN_READ_LIMIT = 1024 * 1024;
|
|
35115
|
+
WINDOWS_EDITOR_EXE = {
|
|
35116
|
+
cursor: { exe: ["Programs", "cursor", "Cursor.exe"], shim: "cursor" },
|
|
35117
|
+
vscode: { exe: ["Programs", "Microsoft VS Code", "Code.exe"], shim: "code" }
|
|
35118
|
+
};
|
|
35119
|
+
}
|
|
35120
|
+
});
|
|
35121
|
+
|
|
35122
|
+
// packages/server/src/thread-links.ts
|
|
35123
|
+
import { isAbsolute as isAbsolute5 } from "node:path";
|
|
35124
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
35125
|
+
function threadLinkView(row) {
|
|
35126
|
+
return { id: row.id, kind: row.kind, label: row.label, target: row.target };
|
|
35127
|
+
}
|
|
35128
|
+
function resolveThreadLink(target, projectDir, roots) {
|
|
35129
|
+
if (/^https?:\/\//i.test(target)) {
|
|
35130
|
+
const url = new URL(target);
|
|
35131
|
+
if (url.username || url.password) throw new Error("Link URLs must not contain credentials");
|
|
35132
|
+
return { kind: "link", target: url.href };
|
|
35133
|
+
}
|
|
35134
|
+
let path3 = target;
|
|
35135
|
+
if (/^file:/i.test(target)) {
|
|
35136
|
+
const url = new URL(target);
|
|
35137
|
+
if (url.search || url.hash) throw new Error("File links must not contain a query or fragment");
|
|
35138
|
+
path3 = fileURLToPath2(url);
|
|
35139
|
+
} else if (!isAbsolute5(target) && /^[a-z][a-z\d+.-]*:/i.test(target)) {
|
|
35140
|
+
throw new Error("Register an HTTP(S) URL or a local file");
|
|
35141
|
+
}
|
|
35142
|
+
const resolved = resolveOpenableFile(path3, projectDir, roots);
|
|
35143
|
+
if (!resolved) throw new Error("Local file was not found or is outside Frizz's trusted roots");
|
|
35144
|
+
return { kind: "file", target: resolved };
|
|
35145
|
+
}
|
|
35146
|
+
var init_thread_links = __esm({
|
|
35147
|
+
"packages/server/src/thread-links.ts"() {
|
|
35148
|
+
"use strict";
|
|
35149
|
+
init_local_file();
|
|
35150
|
+
}
|
|
35151
|
+
});
|
|
35152
|
+
|
|
34684
35153
|
// packages/server/src/frizz.ts
|
|
34685
35154
|
import { execFile as execFile5 } from "node:child_process";
|
|
34686
|
-
import { lstatSync as lstatSync2, realpathSync as
|
|
34687
|
-
import { basename as basename5, dirname as dirname9, join as
|
|
35155
|
+
import { lstatSync as lstatSync2, realpathSync as realpathSync7 } from "node:fs";
|
|
35156
|
+
import { basename as basename5, dirname as dirname9, join as join26, resolve as resolve6 } from "node:path";
|
|
34688
35157
|
import { promisify as promisify5 } from "node:util";
|
|
34689
35158
|
function frizzScriptsDir() {
|
|
34690
35159
|
if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
|
|
34691
|
-
return
|
|
35160
|
+
return resolve6(import.meta.dirname, "..", "..", "..", "board");
|
|
34692
35161
|
}
|
|
34693
35162
|
function directFrizzRoot(projectDir) {
|
|
34694
35163
|
try {
|
|
34695
|
-
const projectRoot =
|
|
34696
|
-
const path3 =
|
|
35164
|
+
const projectRoot = realpathSync7(projectDir);
|
|
35165
|
+
const path3 = join26(projectRoot, ".frizz");
|
|
34697
35166
|
const stat2 = lstatSync2(path3);
|
|
34698
35167
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
|
|
34699
|
-
const real =
|
|
35168
|
+
const real = realpathSync7(path3);
|
|
34700
35169
|
return dirname9(real) === projectRoot && basename5(real) === ".frizz" ? real : null;
|
|
34701
35170
|
} catch {
|
|
34702
35171
|
return null;
|
|
@@ -34707,7 +35176,7 @@ function frizzDirExists(projectDir) {
|
|
|
34707
35176
|
}
|
|
34708
35177
|
async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
|
|
34709
35178
|
if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
|
|
34710
|
-
const { stdout } = await execFileP("node", [
|
|
35179
|
+
const { stdout } = await execFileP("node", [join26(scriptsDir, "index.mjs"), "--json"], {
|
|
34711
35180
|
cwd: projectDir,
|
|
34712
35181
|
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
|
|
34713
35182
|
maxBuffer: 32 * 1024 * 1024
|
|
@@ -34732,7 +35201,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
|
|
|
34732
35201
|
};
|
|
34733
35202
|
}
|
|
34734
35203
|
async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
|
|
34735
|
-
await execFileP("node", [
|
|
35204
|
+
await execFileP("node", [join26(scriptsDir, "thread-update.mjs"), slug, ...args], {
|
|
34736
35205
|
cwd: projectDir,
|
|
34737
35206
|
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
|
|
34738
35207
|
});
|
|
@@ -34817,12 +35286,12 @@ var init_awaiting = __esm({
|
|
|
34817
35286
|
});
|
|
34818
35287
|
|
|
34819
35288
|
// packages/server/src/backend/project-mcp-servers.ts
|
|
34820
|
-
import { readFileSync as
|
|
34821
|
-
import { homedir as
|
|
34822
|
-
import { join as
|
|
35289
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
35290
|
+
import { homedir as homedir12 } from "node:os";
|
|
35291
|
+
import { join as join27 } from "node:path";
|
|
34823
35292
|
function readJsonObject(path3) {
|
|
34824
35293
|
try {
|
|
34825
|
-
const parsed = JSON.parse(
|
|
35294
|
+
const parsed = JSON.parse(readFileSync19(path3, "utf8"));
|
|
34826
35295
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
34827
35296
|
} catch {
|
|
34828
35297
|
return void 0;
|
|
@@ -34913,17 +35382,17 @@ function userScopeRemoteServers(account, cwd, env) {
|
|
|
34913
35382
|
}
|
|
34914
35383
|
function projectMcpServers(cwd, options = {}) {
|
|
34915
35384
|
const env = options.env ?? process.env;
|
|
34916
|
-
const home = options.home ??
|
|
35385
|
+
const home = options.home ?? homedir12();
|
|
34917
35386
|
const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
|
|
34918
|
-
const accountFile = configOverride ?
|
|
34919
|
-
const userSettingsFile =
|
|
35387
|
+
const accountFile = configOverride ? join27(configOverride, ".claude.json") : join27(home, ".claude.json");
|
|
35388
|
+
const userSettingsFile = join27(configOverride ?? join27(home, ".claude"), "settings.json");
|
|
34920
35389
|
const account = readJsonObject(accountFile);
|
|
34921
35390
|
const out = userScopeRemoteServers(account, cwd, env);
|
|
34922
|
-
const declared = objectField(readJsonObject(
|
|
35391
|
+
const declared = objectField(readJsonObject(join27(cwd, ".mcp.json")), "mcpServers");
|
|
34923
35392
|
if (!declared) return out;
|
|
34924
35393
|
const approval = collectApproval([
|
|
34925
|
-
readJsonObject(
|
|
34926
|
-
readJsonObject(
|
|
35394
|
+
readJsonObject(join27(cwd, ".claude", "settings.json")),
|
|
35395
|
+
readJsonObject(join27(cwd, ".claude", "settings.local.json")),
|
|
34927
35396
|
readJsonObject(userSettingsFile),
|
|
34928
35397
|
objectField(objectField(account, "projects"), cwd)
|
|
34929
35398
|
]);
|
|
@@ -34947,17 +35416,17 @@ var init_project_mcp_servers = __esm({
|
|
|
34947
35416
|
});
|
|
34948
35417
|
|
|
34949
35418
|
// packages/server/src/worker-plugin-dir.ts
|
|
34950
|
-
import { existsSync as
|
|
34951
|
-
import { dirname as dirname10, join as
|
|
34952
|
-
import { fileURLToPath as
|
|
35419
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
35420
|
+
import { dirname as dirname10, join as join28 } from "node:path";
|
|
35421
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
34953
35422
|
function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
|
|
34954
35423
|
const override = env.FRIZZ_WORKER_PLUGIN_DIR;
|
|
34955
|
-
if (override &&
|
|
35424
|
+
if (override && existsSync9(join28(override, ".claude-plugin", "plugin.json")))
|
|
34956
35425
|
return override;
|
|
34957
|
-
let current = dirname10(
|
|
35426
|
+
let current = dirname10(fileURLToPath3(moduleUrl));
|
|
34958
35427
|
for (; ; ) {
|
|
34959
|
-
const candidate =
|
|
34960
|
-
if (
|
|
35428
|
+
const candidate = join28(current, "cc-worker");
|
|
35429
|
+
if (existsSync9(join28(candidate, ".claude-plugin", "plugin.json"))) return candidate;
|
|
34961
35430
|
const parent = dirname10(current);
|
|
34962
35431
|
if (parent === current) return void 0;
|
|
34963
35432
|
current = parent;
|
|
@@ -34985,6 +35454,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
|
|
|
34985
35454
|
lean ? null : THREAD_EXECUTION[kind],
|
|
34986
35455
|
AGENT_COMPLETION,
|
|
34987
35456
|
VISUAL_EVIDENCE,
|
|
35457
|
+
REGISTERED_LINKS,
|
|
34988
35458
|
QUALITY_BAR,
|
|
34989
35459
|
TOKEN_ECONOMY,
|
|
34990
35460
|
QUESTIONS,
|
|
@@ -34996,7 +35466,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
|
|
|
34996
35466
|
out = out.replaceAll("{{FRIZZ_MONITORS_DIR}}", opts.monitorsDir?.trim() || MONITORS_DIR_FALLBACK);
|
|
34997
35467
|
return out;
|
|
34998
35468
|
}
|
|
34999
|
-
var INLINE, INTRO, DEFER, OPENING, ACTIVITY_CAPTIONS, SIGNALS, AGENT_COMPLETION, VISUAL_EVIDENCE, QUALITY_BAR, TOKEN_ECONOMY, QUESTIONS, STOP_CRITERION, TRIVIAL_PROMPTS, THREAD_NAME, SCRATCHPAD, BACKEND, SPAWN_THREAD, THREAD_EXECUTION, MONITORS_DIR_FALLBACK;
|
|
35469
|
+
var INLINE, INTRO, DEFER, OPENING, ACTIVITY_CAPTIONS, SIGNALS, AGENT_COMPLETION, VISUAL_EVIDENCE, REGISTERED_LINKS, QUALITY_BAR, TOKEN_ECONOMY, QUESTIONS, STOP_CRITERION, TRIVIAL_PROMPTS, THREAD_NAME, SCRATCHPAD, BACKEND, SPAWN_THREAD, THREAD_EXECUTION, MONITORS_DIR_FALLBACK;
|
|
35000
35470
|
var init_workerPrompt = __esm({
|
|
35001
35471
|
"packages/server/src/workerPrompt.ts"() {
|
|
35002
35472
|
"use strict";
|
|
@@ -35073,9 +35543,11 @@ nothing. Frizz bumps you for one, twice, then gives up \u2014 a turn wasted each
|
|
|
35073
35543
|
\`mcp__frizz__ask\` and \`mcp__frizz__watch\` each record a ROW, and frizz reads all three as a sign-off
|
|
35074
35544
|
\u2014 it will not bump you for a missing fence after any of them. A fence is a sentence with the lifetime
|
|
35075
35545
|
of the message carrying it, so it has to be rewritten at every rest and is wrong the moment anything
|
|
35076
|
-
changes; a row survives your turn ending, a compaction and a frizz restart. **The
|
|
35077
|
-
work**, they are still the right shape for the prose the human reads
|
|
35078
|
-
but where a verb exists for what you are saying, call it.
|
|
35546
|
+
changes; a row survives your turn ending, a compaction and a frizz restart. **The \`done\` and
|
|
35547
|
+
\`awaiting\` fences below still work**, they are still the right shape for the prose the human reads \u2014
|
|
35548
|
+
but where a verb exists for what you are saying, call it. **A QUESTION HAS NO FENCE ANY MORE** (retired
|
|
35549
|
+
2026-09-11): the only way to ask is \`mcp__frizz__ask\`, and a question written into a fence's body is
|
|
35550
|
+
plain prose \u2014 no card, no answer, and no sign-off.
|
|
35079
35551
|
|
|
35080
35552
|
**AN OPEN QUESTION IS A STANDING SIGN-OFF \u2014 REST NORMALLY.** Any unanswered question counts, not only
|
|
35081
35553
|
one you registered this turn, and frizz does not bump you for a missing fence while one is open. When
|
|
@@ -35089,8 +35561,9 @@ without it. Name what is still running in the prose (frizz lists every live shel
|
|
|
35089
35561
|
watcher under the prompt box whether or not a fence names it). A question you no longer need answered
|
|
35090
35562
|
is one you \`unask\`; only then can a park take.
|
|
35091
35563
|
|
|
35092
|
-
Use at most ONE fenced signal block, at the very END
|
|
35093
|
-
|
|
35564
|
+
Use at most ONE fenced signal block, at the very END (a question's PLACEMENT marker is not a signal
|
|
35565
|
+
block \u2014 see Questions for the human). The fence language is the state; the body is the card the human
|
|
35566
|
+
reads. An open registered question and real permission prompts are higher-priority asks
|
|
35094
35567
|
than \`done\` or \`awaiting\`.
|
|
35095
35568
|
|
|
35096
35569
|
**NAME THE PULL REQUEST, EVERY TIME YOU REST** \u2014 its number in EVERY resting message, not just the
|
|
@@ -35141,7 +35614,8 @@ exactly ONE of them.
|
|
|
35141
35614
|
- **A RECOMMENDATION IS NOT A CONCLUSION, AND AN UNSENT DRAFT IS NOT A DELIVERABLE.** When the
|
|
35142
35615
|
verdict is that SOMEONE SHOULD NOW DO SOMETHING \u2014 merge it, decline it, post this comment, pick
|
|
35143
35616
|
one of these designs, press the button you are not allowed to press \u2014 that someone is the human,
|
|
35144
|
-
so
|
|
35617
|
+
so register the question with \`mcp__frizz__ask\`, your recommendation as option A, and rest on
|
|
35618
|
+
it. Same for anything you WROTE
|
|
35145
35619
|
but did not SEND (a drafted comment, reply, issue body or release note): \`done\` files that draft
|
|
35146
35620
|
away with the thread.
|
|
35147
35621
|
- Follow-up work you DISCOVERED blocks \`done\` just as hard as work you were assigned, even when it
|
|
@@ -35227,8 +35701,9 @@ exactly ONE of them.
|
|
|
35227
35701
|
dimmed Snoozed band; any one dead, unknown or not yours \u21D2 you are BUMPED immediately and told which.
|
|
35228
35702
|
**A FENCE THAT NAMES NOTHING IS NOT A PARK** \u2014 \`for:\` describes a wait, it is not one \u2014 so
|
|
35229
35703
|
"waiting on the checks" plus a duration just sits in the queue: REGISTER the PR and name it, and
|
|
35230
|
-
if nothing is actually running, end with \`\`\`done or
|
|
35231
|
-
- **WAITING ON A PERSON IS A
|
|
35704
|
+
if nothing is actually running, end with \`\`\`done or register a question with \`mcp__frizz__ask\`.
|
|
35705
|
+
- **WAITING ON A PERSON IS A REGISTERED QUESTION** \u2014 \`mcp__frizz__ask\`. There is no human gate, no
|
|
35706
|
+
prose park, and no question fence.
|
|
35232
35707
|
- **CI, RELEASES, DEPLOYS AND MERGE PROGRESSION ARE AUTOMATABLE \u2014 never \` \`\`\`awaiting \` them
|
|
35233
35708
|
BLINDLY.** For a pull request, \`mcp__frizz__watch_pr\`. For anything else stay ACTIVE: dispatch a
|
|
35234
35709
|
sub-agent to own the wait (its return re-invokes you), or set a timer and name it here.
|
|
@@ -35239,13 +35714,14 @@ exactly ONE of them.
|
|
|
35239
35714
|
is still running, and emit a FRESH fence naming it. If it turns out to be automatable, arm the
|
|
35240
35715
|
active wait instead and do not fence.
|
|
35241
35716
|
|
|
35242
|
-
-
|
|
35717
|
+
- A REGISTERED QUESTION \u2014 \`mcp__frizz__ask\`, not a fence: you need the human's input, and the open
|
|
35718
|
+
row IS the handback; see **Questions for the human**.
|
|
35243
35719
|
|
|
35244
35720
|
### When the work is finished but the thread found more
|
|
35245
35721
|
|
|
35246
35722
|
Your mandate is complete and you turned up something else \u2014 a bug you were not sent for, a follow-up
|
|
35247
35723
|
the change implies. \`done\` is barred (the finding dies with the card), \`awaiting\` is barred (nothing
|
|
35248
|
-
is running), and
|
|
35724
|
+
is running), and asking only looks barred. That deadlock is not real, and stretching \`done\` is not
|
|
35249
35725
|
how you break it. Take the first exit that fits:
|
|
35250
35726
|
|
|
35251
35727
|
1. **DO IT \u2014 the default, and it covers far more than "small".** Anything inside your change's own
|
|
@@ -35254,10 +35730,10 @@ how you break it. Take the first exit that fits:
|
|
|
35254
35730
|
is more than one sitting, dispatch an in-session
|
|
35255
35731
|
SUB-AGENT: its result comes BACK to you, so the follow-up lands on YOUR card, in your diff, under one
|
|
35256
35732
|
review. Then \`done\` is honest with nothing left over.
|
|
35257
|
-
2. **ASK** \u2014 the human should choose whether or how it happens; "nothing was
|
|
35258
|
-
objection. This is the exit for work you cannot bound or verify, or that is
|
|
35259
|
-
call \u2014 and it is where a separable effort goes, as an OPTION they pick, rather
|
|
35260
|
-
create for them.
|
|
35733
|
+
2. **ASK** \u2014 \`mcp__frizz__ask\`: the human should choose whether or how it happens; "nothing was
|
|
35734
|
+
pending" is not an objection. This is the exit for work you cannot bound or verify, or that is
|
|
35735
|
+
genuinely the human's call \u2014 and it is where a separable effort goes, as an OPTION they pick, rather
|
|
35736
|
+
than a card you create for them.
|
|
35261
35737
|
3. **HAND IT OFF TO ITS OWN CARD \u2014 the LAST resort, not the tidy one.** \`mcp__frizz__spawn_thread\` puts
|
|
35262
35738
|
a separable effort on the board as its own thread; put the returned \`[title](/thread/<slug>)\` link
|
|
35263
35739
|
in the body. **A spawned thread is FIRE-AND-FORGET: it reports to the HUMAN, never to you, so
|
|
@@ -35288,6 +35764,12 @@ image paths through its guarded local-image proxy; only eligible workspace or ex
|
|
|
35288
35764
|
image files can embed, and a path outside that safe boundary stays non-navigable. Do not bulk-embed
|
|
35289
35765
|
irrelevant screenshots. Always keep a concise textual finding alongside them, so the handoff still
|
|
35290
35766
|
reads when images are unavailable.`;
|
|
35767
|
+
REGISTERED_LINKS = `## Saved links and files
|
|
35768
|
+
|
|
35769
|
+
Use \`mcp__frizz__link\` with \`label\` and \`target\` for a dev server, working document, report or download
|
|
35770
|
+
the human should keep at hand. It stays underneath the prompt. Reuse the label to update its destination;
|
|
35771
|
+
read registrations with \`activity\` and remove one with \`unlink\`. Register existing files, using an
|
|
35772
|
+
absolute path outside the project root. A saved reference is not a running process or a completion gate.`;
|
|
35291
35773
|
QUALITY_BAR = `## Quality bar
|
|
35292
35774
|
|
|
35293
35775
|
- Verify behavior end-to-end before calling anything done. A green suite over a stubbed implementation
|
|
@@ -35319,88 +35801,101 @@ The API call is the unit of cost, not the tokens you type: each message re-reads
|
|
|
35319
35801
|
do not rest on a step you could take now.`;
|
|
35320
35802
|
QUESTIONS = `## Questions for the human
|
|
35321
35803
|
|
|
35322
|
-
You run under a dashboard, not a live chat, so
|
|
35323
|
-
|
|
35324
|
-
|
|
35804
|
+
You run under a dashboard, not a live chat, so a question is a ROW the human still owes an answer to,
|
|
35805
|
+
never a sentence in a message: \`mcp__frizz__ask\` registers it, and the dashboard renders it as an
|
|
35806
|
+
answerable card. **THERE IS NO \`question\` FENCE ANY MORE.** A fence with a question written in its body
|
|
35807
|
+
is plain prose \u2014 it draws no card, nothing can answer it, and it is not a sign-off. (Retired 2026-09-11:
|
|
35808
|
+
the fence was taught beside the tool for two weeks, workers went on writing it on most days, and nothing
|
|
35809
|
+
tracks a fence's answer \u2014 a fence is bytes in a message; the row is the only lifecycle.)
|
|
35325
35810
|
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
|
|
35329
|
-
|
|
35330
|
-
|
|
35331
|
-
|
|
35332
|
-
|
|
35333
|
-
|
|
35334
|
-
|
|
35335
|
-
question
|
|
35336
|
-
|
|
35337
|
-
|
|
35338
|
-
|
|
35339
|
-
|
|
35340
|
-
|
|
35341
|
-
|
|
35342
|
-
|
|
35343
|
-
|
|
35344
|
-
|
|
35345
|
-
|
|
35811
|
+
\`ask\` takes \`questions\` \u2014 several at once, each with \`question\`, \`kind\` (\`question\` = pick one,
|
|
35812
|
+
\`multi\` = pick several, no options at all = free text), an optional \`danger\`, and \`options\` each
|
|
35813
|
+
carrying a \`label\`, a \`description\` (ONE LINE by default \u2014 name the trade-off and stop; earn more
|
|
35814
|
+
than that and spend it on a shape the human can SCAN, a short list, a table, a code block, the diff an
|
|
35815
|
+
option would produce, the message that would be posted, and never a run of one-sentence paragraphs;
|
|
35816
|
+
it renders inside the option, so the human sees it BEFORE choosing), an optional \`recommended\`, and
|
|
35817
|
+
optional \`followUps\`: questions that become live ONLY if that option is taken, so you can ask "and if
|
|
35818
|
+
so, which?" without asking it of somebody who said no. Written out, one registration reads:
|
|
35819
|
+
|
|
35820
|
+
question: Should the settings store use SQLite or a JSON file?
|
|
35821
|
+
kind: question
|
|
35822
|
+
options:
|
|
35823
|
+
- label: SQLite
|
|
35824
|
+
description: transactional, matches how sessions are already stored
|
|
35825
|
+
recommended: true
|
|
35826
|
+
- label: JSON file
|
|
35827
|
+
description: zero deps, human-editable, racy under concurrent writes
|
|
35828
|
+
|
|
35829
|
+
Each question must stand alone: the specific question on ONE line, options each with a one-line
|
|
35830
|
+
trade-off, and enough context to answer cold. The card letters the options A, B, C in the order you
|
|
35831
|
+
give them, so put the one you would take FIRST and mark it \`recommended\`; mark exactly one. Several
|
|
35832
|
+
independent questions are several entries of ONE \`ask\` call, never one bundled question. A bare
|
|
35833
|
+
"which approach?" with no options is a broken handoff.
|
|
35834
|
+
|
|
35835
|
+
NO "I" AND NO "you" ANYWHERE IN A QUESTION \u2014 the question, its options, or any interactive prompt.
|
|
35836
|
+
Clicking an option is the HUMAN speaking, so first and second person flip between writer and reader: in
|
|
35837
|
+
"Leave it \u2014 you run the command yourself" / "I run it now", neither line says which actor is which.
|
|
35838
|
+
Write the whole question actor-explicit \u2014 each option an instruction the human hands back ("Reinstall
|
|
35839
|
+
the hooks now") or one that names its actor outright ("the worker retries nightly", "left for the
|
|
35346
35840
|
maintainer's own terminal"):
|
|
35347
35841
|
|
|
35348
|
-
|
|
35349
|
-
|
|
35350
|
-
|
|
35351
|
-
-
|
|
35352
|
-
|
|
35353
|
-
|
|
35842
|
+
question: A reply for issue #482 explaining the workaround is drafted. Post it from the maintainer's GitHub account, or leave it a draft?
|
|
35843
|
+
kind: question
|
|
35844
|
+
options:
|
|
35845
|
+
- label: Post it
|
|
35846
|
+
description: the reporter is blocked and the workaround is verified
|
|
35847
|
+
recommended: true
|
|
35848
|
+
- label: Leave it in the handoff
|
|
35849
|
+
description: for edits before anything is posted
|
|
35354
35850
|
|
|
35355
|
-
Write the
|
|
35851
|
+
Write the question in the human's OWN vocabulary: they have their original prompt and nothing else \u2014 not
|
|
35356
35852
|
your plan, your notes, or the names you settled on while working. A name you coined mid-effort (a
|
|
35357
35853
|
phase, lane, tier, step or section number, "the C path", "the second variant") means nothing to them, so
|
|
35358
35854
|
translate it into what the thing does. Minimize code identifiers by default: lead with the behavior, and
|
|
35359
35855
|
spend a file, symbol or flag where it genuinely reads clearest or the human already uses it. Every input
|
|
35360
35856
|
the choice needs \u2014 what happens today, each option's user-visible consequence, any number that matters \u2014
|
|
35361
|
-
belongs INSIDE the
|
|
35362
|
-
|
|
35363
|
-
A GO/NO-GO gate is not a special
|
|
35364
|
-
decline, each a real choice the human can click). Tag
|
|
35365
|
-
|
|
35366
|
-
|
|
35367
|
-
|
|
35368
|
-
**
|
|
35369
|
-
|
|
35370
|
-
|
|
35371
|
-
|
|
35372
|
-
|
|
35373
|
-
|
|
35374
|
-
|
|
35375
|
-
|
|
35376
|
-
option would produce, the message that would be posted, and never a run of one-sentence paragraphs;
|
|
35377
|
-
it renders inside the option, so the human sees it BEFORE choosing), an optional
|
|
35378
|
-
\`recommended\`, and optional \`followUps\`: questions that become live ONLY if that option is taken,
|
|
35379
|
-
so you can ask "and if so, which?" without asking it of somebody who said no. **REGISTERING A QUESTION DOES NOT END YOUR TURN** \u2014
|
|
35380
|
-
it carries no timeout and waits on a person, so keep doing everything that does not depend on the
|
|
35381
|
-
answer. The answer comes back as its own wake, restating what was asked. Withdraw one you no longer
|
|
35382
|
-
need with \`mcp__frizz__unask\`, above all when you work the answer out yourself. **ON A THREAD
|
|
35383
|
-
CARRYING A GOAL IT REFUSES** \u2014 a standing "keep going, decide for yourself" is autonomous mode, and the
|
|
35384
|
-
refusal hands that instruction back at the moment it matters. Decide, and say which way you went. A
|
|
35385
|
-
genuinely human-owned call still belongs in the prose of your FINAL MESSAGE; autonomous does not mean
|
|
35386
|
-
nobody reads.
|
|
35857
|
+
belongs INSIDE the question and its options; "as discussed above" points at something they cannot see.
|
|
35858
|
+
|
|
35859
|
+
A GO/NO-GO gate is not a special kind \u2014 it is an ordinary \`question\` with two options (the go and the
|
|
35860
|
+
decline, each a real choice the human can click). Tag it only to change how it renders: \`danger\` for
|
|
35861
|
+
the genuinely irreversible (force-merge, deletion, history rewrite, prod rollback), \`kind: multi\` for
|
|
35862
|
+
select-several triage.
|
|
35863
|
+
|
|
35864
|
+
**REGISTERING A QUESTION DOES NOT END YOUR TURN** \u2014 it carries no timeout and waits on a person, so
|
|
35865
|
+
keep doing everything that does not depend on the answer, and register the question the moment you
|
|
35866
|
+
find it rather than saving it for the end. The answer comes back as its own wake, restating what was
|
|
35867
|
+
asked. Withdraw one you no longer need with \`mcp__frizz__unask\`, above all when you work the answer
|
|
35868
|
+
out yourself. **ON A THREAD CARRYING A GOAL IT REFUSES** \u2014 a standing "keep going, decide for yourself"
|
|
35869
|
+
is autonomous mode, and the refusal hands that instruction back at the moment it matters. Decide, and
|
|
35870
|
+
say which way you went. A genuinely human-owned call still belongs in the prose of your FINAL MESSAGE;
|
|
35871
|
+
autonomous does not mean nobody reads.
|
|
35387
35872
|
|
|
35388
35873
|
**EVERY OPEN QUESTION DRAWS ITS OWN CARD AT ITS REST \u2014 never write it into the handoff.** The
|
|
35389
|
-
registered card renders whether you mention it or not, and
|
|
35390
|
-
|
|
35391
|
-
|
|
35392
|
-
|
|
35393
|
-
|
|
35874
|
+
registered card renders whether you mention it or not, and prose that restates a registered question
|
|
35875
|
+
draws NOTHING: one question, one card, and answering the registered one is what settles the row and
|
|
35876
|
+
un-gates \`done\`. So the handoff's job is the reasoning around the ask, not the ask itself.
|
|
35877
|
+
|
|
35878
|
+
**TO PLACE A QUESTION INSIDE YOUR PROSE, WRITE AN EMPTY FENCE NAMING ITS ID** \u2014 the id \`ask\` returned
|
|
35879
|
+
(\`mcp__frizz__activity\` lists them). At the point your write-up has earned the ask \u2014 after the
|
|
35880
|
+
paragraph that sets it up, before the one that says what happens either way:
|
|
35881
|
+
|
|
35882
|
+
\`\`\`question qst_ab12cd34
|
|
35883
|
+
\`\`\`
|
|
35884
|
+
|
|
35885
|
+
Frizz draws the registered card THERE instead of at the tail. Nothing goes in the body: the card comes
|
|
35886
|
+
from the ROW, so nothing about the question is ever guessed from prose, and a marker naming no open
|
|
35887
|
+
question of yours draws nothing. One marker per question. Placement is OPTIONAL \u2014 a question with no
|
|
35888
|
+
marker renders at the tail of the rest, and a handoff that writes no marker loses nothing. Every answer
|
|
35889
|
+
of the rest is sent together, with ONE Send button at the tail, wherever the cards sit. (Placement was
|
|
35890
|
+
retired 2026-08-30 and restored 2026-09-11 with the free-form fence's retirement: the marker references
|
|
35891
|
+
a row, which is exactly what a fence with a body never did.)
|
|
35394
35892
|
|
|
35395
35893
|
A question you no longer want answered is not one you leave out of the write-up \u2014 it is one you
|
|
35396
35894
|
\`unask\`, which is the difference between deciding something yourself and quietly hoping nobody
|
|
35397
35895
|
answers. Questions asked in one \`ask\` call render together and send as one batch.
|
|
35398
35896
|
|
|
35399
|
-
|
|
35400
|
-
|
|
35401
|
-
A \` \`\`\`question \` block IS the handback \u2014 do not also emit a \`done\`/\`awaiting\` fence, and the same
|
|
35402
|
-
holds while a REGISTERED question stands open. Answers arrive as your next user message, possibly as
|
|
35403
|
-
terse as "1: A, 2: B".`;
|
|
35897
|
+
**AN OPEN REGISTERED QUESTION IS THE HANDBACK** \u2014 emit no \`done\`/\`awaiting\` fence beside it, and
|
|
35898
|
+
write no question fence at all. The answer arrives as your next user message, as its own wake.`;
|
|
35404
35899
|
STOP_CRITERION = `## The stop criterion
|
|
35405
35900
|
|
|
35406
35901
|
**COMING TO REST IS A STOP, and it needs the same justification as a question.** The far more common
|
|
@@ -35448,14 +35943,14 @@ Stop only when a wrong guess would be BOTH costly AND hard to undo: destructive
|
|
|
35448
35943
|
security posture with real exposure; product/UX direction that is genuinely the human's taste to set;
|
|
35449
35944
|
scope so vague that acting means inventing substantial new code with no way to check it. Everything
|
|
35450
35945
|
else \u2014 mechanical work, clear bugs, refactors, defaults you can justify, architecture you can reverse \u2014
|
|
35451
|
-
you finish. When you do stop,
|
|
35452
|
-
all the work that does NOT depend on the answer first.`;
|
|
35946
|
+
you finish. When you do stop, register it with \`mcp__frizz__ask\` with your recommendation marked, and
|
|
35947
|
+
do all the work that does NOT depend on the answer first.`;
|
|
35453
35948
|
TRIVIAL_PROMPTS = `## Trivial and conversational prompts
|
|
35454
35949
|
|
|
35455
35950
|
Some dispatches never deserved a work effort \u2014 a greeting, a one-line question, a joke, a test ping.
|
|
35456
35951
|
Resolve them with ZERO ceremony: answer inline in one message, and close with a \` \`\`\`done \` fence whose
|
|
35457
35952
|
body is one line ("Answered inline \u2014 conversational prompt, nothing to ship."). If the answer genuinely
|
|
35458
|
-
needs a reply,
|
|
35953
|
+
needs a reply, register it with \`mcp__frizz__ask\` instead. Do not manufacture scope, restate the "task", or ask
|
|
35459
35954
|
clarifying questions to seem busy.`;
|
|
35460
35955
|
THREAD_NAME = `## Name your thread once you know what the work is
|
|
35461
35956
|
|
|
@@ -35835,7 +36330,7 @@ Dispatches share a vocabulary for the deliverable and quality bar, not for fleet
|
|
|
35835
36330
|
first that works? Cover and synthesize every relevant prong inline unless delegation was
|
|
35836
36331
|
explicitly requested. Close with a \` \`\`\`done \` fence listing the completed research/evidence \u2014
|
|
35837
36332
|
the report IS this thread's deliverable, unlike a bug/issue investigation headed for a fix, which
|
|
35838
|
-
bare-rests; use \`
|
|
36333
|
+
bare-rests; use \`mcp__frizz__ask\` for a human call.
|
|
35839
36334
|
- **Audit thread** \u2014 adversarially verify correctness, safety, or compatibility of something that
|
|
35840
36335
|
exists. Exercise proportionate cases and lenses until dry; re-check load-bearing verdicts and cite
|
|
35841
36336
|
evidence. Thorough coverage is required, but the audit label alone does not authorize fan-out.
|
|
@@ -36112,15 +36607,14 @@ var init_claude_broker_client = __esm({
|
|
|
36112
36607
|
});
|
|
36113
36608
|
|
|
36114
36609
|
// packages/server/src/backend/claude-permission-interactions.ts
|
|
36115
|
-
import { homedir as
|
|
36610
|
+
import { homedir as homedir13 } from "node:os";
|
|
36116
36611
|
function clip(text2, max) {
|
|
36117
36612
|
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
36118
36613
|
}
|
|
36119
|
-
function tildePath(path3) {
|
|
36120
|
-
const home = homedir12();
|
|
36614
|
+
function tildePath(path3, home = homedir13()) {
|
|
36121
36615
|
if (!path3.startsWith(home)) return path3;
|
|
36122
36616
|
const rest = path3.slice(home.length);
|
|
36123
|
-
return rest === "" ? "~" : rest
|
|
36617
|
+
return rest === "" ? "~" : /^[\\/]/.test(rest) ? `~${rest}` : path3;
|
|
36124
36618
|
}
|
|
36125
36619
|
function inputPreview(input, shown) {
|
|
36126
36620
|
if (!input) return "";
|
|
@@ -36354,8 +36848,8 @@ var init_claude_permission_interactions = __esm({
|
|
|
36354
36848
|
HAS_UNSAFE_TEXT = new RegExp(UNSAFE_TEXT_SOURCE, "u");
|
|
36355
36849
|
CLAUDE_NOTES_ONLY = "(notes only)";
|
|
36356
36850
|
CLAUDE_QUESTION_DECLINED = "The operator did not answer this question from the frizz dashboard. Do not ask it again with this tool \u2014 decide with the information you already have, or raise it in your final message.";
|
|
36357
|
-
CLAUDE_ASK_DENY_MESSAGE = "Frizz could not render this question as a card.
|
|
36358
|
-
CLAUDE_ASK_WITHDRAWN_MESSAGE = "This question was withdrawn before anyone answered it \u2014 most likely because the operator sent a message instead. Read their next message and follow it. Do not ask this again with this tool; if you still need the decision,
|
|
36851
|
+
CLAUDE_ASK_DENY_MESSAGE = "Frizz could not render this question as a card. Register it with `mcp__frizz__ask` instead \u2014 context, the question, options with a one-line trade-off each, the recommended one first \u2014 never as a ```question fence, which is retired. The operator answers it from the queue and the reply arrives as your next user message.";
|
|
36852
|
+
CLAUDE_ASK_WITHDRAWN_MESSAGE = "This question was withdrawn before anyone answered it \u2014 most likely because the operator sent a message instead. Read their next message and follow it. Do not ask this again with this tool; if you still need the decision, register it with `mcp__frizz__ask`.";
|
|
36359
36853
|
}
|
|
36360
36854
|
});
|
|
36361
36855
|
|
|
@@ -36767,8 +37261,8 @@ var init_claude_agent_broker_bridge = __esm({
|
|
|
36767
37261
|
});
|
|
36768
37262
|
|
|
36769
37263
|
// packages/server/src/dispatch.ts
|
|
36770
|
-
import { closeSync as closeSync12, constants, existsSync as
|
|
36771
|
-
import { basename as basename6, join as
|
|
37264
|
+
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 statSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
|
|
37265
|
+
import { basename as basename6, join as join29, dirname as dirname11 } from "node:path";
|
|
36772
37266
|
import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
|
|
36773
37267
|
function fallbackTitle(prompt) {
|
|
36774
37268
|
const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
|
|
@@ -36787,7 +37281,7 @@ function fallbackTitle(prompt) {
|
|
|
36787
37281
|
}
|
|
36788
37282
|
function resolveSlug(frizzDir, base, taken) {
|
|
36789
37283
|
base = ThreadSlug.parse(base);
|
|
36790
|
-
const isTaken = (slug) =>
|
|
37284
|
+
const isTaken = (slug) => existsSync10(join29(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
|
|
36791
37285
|
if (!isTaken(base)) return base;
|
|
36792
37286
|
for (let n = 2; ; n++) {
|
|
36793
37287
|
const suffix = `-${n}`;
|
|
@@ -36803,16 +37297,16 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
36803
37297
|
const parsed = ThreadSlug.safeParse(value);
|
|
36804
37298
|
if (!parsed.success) return null;
|
|
36805
37299
|
try {
|
|
36806
|
-
const projectRoot =
|
|
36807
|
-
const frizzPath =
|
|
37300
|
+
const projectRoot = realpathSync8(projectDir);
|
|
37301
|
+
const frizzPath = join29(projectRoot, ".frizz");
|
|
36808
37302
|
const frizzStat = lstatSync3(frizzPath);
|
|
36809
37303
|
if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
|
|
36810
|
-
const realFrizz =
|
|
37304
|
+
const realFrizz = realpathSync8(frizzPath);
|
|
36811
37305
|
if (dirname11(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
|
|
36812
|
-
const path3 =
|
|
37306
|
+
const path3 = join29(realFrizz, `${parsed.data}.md`);
|
|
36813
37307
|
const before = lstatSync3(path3);
|
|
36814
37308
|
if (!before.isFile() || before.isSymbolicLink()) return null;
|
|
36815
|
-
const realPath =
|
|
37309
|
+
const realPath = realpathSync8(path3);
|
|
36816
37310
|
if (dirname11(realPath) !== realFrizz || basename6(realPath) !== `${parsed.data}.md`) return null;
|
|
36817
37311
|
let contents;
|
|
36818
37312
|
let openedBefore;
|
|
@@ -36820,7 +37314,7 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
36820
37314
|
const fd = openSync13(path3, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
36821
37315
|
try {
|
|
36822
37316
|
openedBefore = fstatSync2(fd);
|
|
36823
|
-
contents =
|
|
37317
|
+
contents = readFileSync20(fd);
|
|
36824
37318
|
openedAfter = fstatSync2(fd);
|
|
36825
37319
|
} finally {
|
|
36826
37320
|
closeSync12(fd);
|
|
@@ -36853,7 +37347,7 @@ function boardAuthorizesAdoption(board, slug) {
|
|
|
36853
37347
|
return !board.errorItems.some((item) => item.file === `${slug}.md`);
|
|
36854
37348
|
}
|
|
36855
37349
|
function ensureSafeDirectDirectory(parent, name) {
|
|
36856
|
-
const path3 =
|
|
37350
|
+
const path3 = join29(parent, name);
|
|
36857
37351
|
try {
|
|
36858
37352
|
mkdirSync13(path3);
|
|
36859
37353
|
} catch (error) {
|
|
@@ -36862,7 +37356,7 @@ function ensureSafeDirectDirectory(parent, name) {
|
|
|
36862
37356
|
}
|
|
36863
37357
|
const stat2 = lstatSync3(path3);
|
|
36864
37358
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) throw new Error("unsafe project directory");
|
|
36865
|
-
const real =
|
|
37359
|
+
const real = realpathSync8(path3);
|
|
36866
37360
|
if (dirname11(real) !== parent || basename6(real) !== name) throw new Error("unsafe project directory");
|
|
36867
37361
|
return real;
|
|
36868
37362
|
}
|
|
@@ -36871,7 +37365,7 @@ function scratchDirRelPath(sessionId) {
|
|
|
36871
37365
|
}
|
|
36872
37366
|
function writeScratchDir(projectDir, sessionId) {
|
|
36873
37367
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/.test(sessionId)) throw new Error("invalid session id");
|
|
36874
|
-
const projectRoot =
|
|
37368
|
+
const projectRoot = realpathSync8(projectDir);
|
|
36875
37369
|
const frizzDir = ensureSafeDirectDirectory(projectRoot, ".frizz");
|
|
36876
37370
|
const threadsDir = ensureSafeDirectDirectory(frizzDir, "threads");
|
|
36877
37371
|
ensureSafeDirectDirectory(threadsDir, sessionId);
|
|
@@ -36883,8 +37377,8 @@ function loadWorkerPrompt(kind = "claude") {
|
|
|
36883
37377
|
function monitorScriptsDir() {
|
|
36884
37378
|
const plugin = workerPluginDir();
|
|
36885
37379
|
if (!plugin) return void 0;
|
|
36886
|
-
const dir =
|
|
36887
|
-
return
|
|
37380
|
+
const dir = join29(plugin, "skills", "gh", "scripts");
|
|
37381
|
+
return existsSync10(join29(dir, "ci-watch.mjs")) ? dir : void 0;
|
|
36888
37382
|
}
|
|
36889
37383
|
function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
36890
37384
|
if (!hookScript || !sessionId) return {};
|
|
@@ -36896,7 +37390,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
36896
37390
|
}
|
|
36897
37391
|
]
|
|
36898
37392
|
});
|
|
36899
|
-
const bashBackgroundHook =
|
|
37393
|
+
const bashBackgroundHook = join29(dirname11(hookScript), "bash-background.mjs");
|
|
36900
37394
|
return {
|
|
36901
37395
|
bypass_hook_trust: true,
|
|
36902
37396
|
hooks: {
|
|
@@ -36922,7 +37416,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
36922
37416
|
}
|
|
36923
37417
|
function scratchpadHookScript() {
|
|
36924
37418
|
const plugin = workerPluginDir();
|
|
36925
|
-
return plugin ?
|
|
37419
|
+
return plugin ? join29(plugin, "hooks", "scratchpad.mjs") : void 0;
|
|
36926
37420
|
}
|
|
36927
37421
|
function composePrompt(sessionId, prompt, kind = "claude") {
|
|
36928
37422
|
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.";
|
|
@@ -36938,12 +37432,12 @@ function scratchpadOrientation(sessionId, kind = "claude") {
|
|
|
36938
37432
|
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.`;
|
|
36939
37433
|
}
|
|
36940
37434
|
function frizzConfigBlock(projectDir) {
|
|
36941
|
-
const path3 =
|
|
37435
|
+
const path3 = join29(projectDir, "FRIZZ.md");
|
|
36942
37436
|
let body;
|
|
36943
37437
|
try {
|
|
36944
|
-
const st =
|
|
37438
|
+
const st = statSync13(path3);
|
|
36945
37439
|
if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
|
|
36946
|
-
body =
|
|
37440
|
+
body = readFileSync20(path3, "utf8").trim();
|
|
36947
37441
|
} catch {
|
|
36948
37442
|
return "";
|
|
36949
37443
|
}
|
|
@@ -36982,8 +37476,8 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
|
|
|
36982
37476
|
const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
|
|
36983
37477
|
const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
|
|
36984
37478
|
if (!pluginDir) return void 0;
|
|
36985
|
-
const scriptPath =
|
|
36986
|
-
if (!
|
|
37479
|
+
const scriptPath = join29(pluginDir, "bin", FRIZZ_MCP.script);
|
|
37480
|
+
if (!existsSync10(scriptPath)) return void 0;
|
|
36987
37481
|
return {
|
|
36988
37482
|
scriptPath,
|
|
36989
37483
|
stateDir,
|
|
@@ -37065,7 +37559,7 @@ function buildClaudeResumeCommand(opts) {
|
|
|
37065
37559
|
}
|
|
37066
37560
|
function createDispatcher(deps) {
|
|
37067
37561
|
const readBoardSource = deps.readBoard ?? readBoard;
|
|
37068
|
-
const frizzDir =
|
|
37562
|
+
const frizzDir = join29(deps.project.dir, ".frizz");
|
|
37069
37563
|
const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
|
|
37070
37564
|
function cleanupPrewrites(built) {
|
|
37071
37565
|
for (const path3 of new Set(built.prewrite.map((file) => file.path))) {
|
|
@@ -37078,7 +37572,7 @@ function createDispatcher(deps) {
|
|
|
37078
37572
|
function cleanupDispatchFiles(scratchRel, built, sessionId) {
|
|
37079
37573
|
cleanupPrewrites(built);
|
|
37080
37574
|
try {
|
|
37081
|
-
rmSync9(
|
|
37575
|
+
rmSync9(join29(deps.project.dir, scratchRel), { force: true, recursive: true });
|
|
37082
37576
|
} catch {
|
|
37083
37577
|
}
|
|
37084
37578
|
cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
|
|
@@ -37516,11 +38010,11 @@ var init_dispatch = __esm({
|
|
|
37516
38010
|
|
|
37517
38011
|
// packages/server/src/board.ts
|
|
37518
38012
|
import {
|
|
37519
|
-
realpathSync as
|
|
38013
|
+
realpathSync as realpathSync9,
|
|
37520
38014
|
watch as fsWatch
|
|
37521
38015
|
} from "node:fs";
|
|
37522
|
-
import { homedir as
|
|
37523
|
-
import { dirname as dirname12, join as
|
|
38016
|
+
import { homedir as homedir14 } from "node:os";
|
|
38017
|
+
import { dirname as dirname12, join as join30 } from "node:path";
|
|
37524
38018
|
import watcher from "@parcel/watcher";
|
|
37525
38019
|
function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
|
|
37526
38020
|
if (!liveness) return false;
|
|
@@ -37831,20 +38325,20 @@ function resolveSessionProfile(row, tele) {
|
|
|
37831
38325
|
function resolveSessionPermission(row, tele) {
|
|
37832
38326
|
const saved = PermissionMode.safeParse(row.permission_mode);
|
|
37833
38327
|
const pending = PermissionMode.safeParse(row.permission_pending);
|
|
37834
|
-
const
|
|
37835
|
-
if (saved.success && pending.success &&
|
|
38328
|
+
const normalize2 = (mode) => effectivePermissionMode(row.backend === "codex" ? "codex" : "claude", mode);
|
|
38329
|
+
if (saved.success && pending.success && normalize2(saved.data) === normalize2(pending.data)) return normalize2(saved.data);
|
|
37836
38330
|
if (row.backend === "codex" && saved.success) {
|
|
37837
38331
|
const observedAt = tele?.permissionModeAt ? Date.parse(tele.permissionModeAt) : NaN;
|
|
37838
38332
|
const spawnedAt = Date.parse(row.spawned_at);
|
|
37839
38333
|
if (tele?.permissionMode && Number.isFinite(observedAt) && Number.isFinite(spawnedAt) && observedAt >= spawnedAt) {
|
|
37840
38334
|
const setAt = row.permission_set_at ? Date.parse(row.permission_set_at) : NaN;
|
|
37841
|
-
if (Number.isFinite(setAt) && setAt > observedAt) return
|
|
37842
|
-
return
|
|
38335
|
+
if (Number.isFinite(setAt) && setAt > observedAt) return normalize2(saved.data);
|
|
38336
|
+
return normalize2(tele.permissionMode);
|
|
37843
38337
|
}
|
|
37844
|
-
return
|
|
38338
|
+
return normalize2(saved.data);
|
|
37845
38339
|
}
|
|
37846
|
-
if (saved.success) return
|
|
37847
|
-
return tele?.permissionMode ?
|
|
38340
|
+
if (saved.success) return normalize2(saved.data);
|
|
38341
|
+
return tele?.permissionMode ? normalize2(tele.permissionMode) : void 0;
|
|
37848
38342
|
}
|
|
37849
38343
|
function resolvePendingPermission(row) {
|
|
37850
38344
|
const parsed = PermissionMode.safeParse(row.permission_pending);
|
|
@@ -37903,6 +38397,7 @@ function registeredDoneFence(done, lastUserAt) {
|
|
|
37903
38397
|
}
|
|
37904
38398
|
function readThreadRegistries(storage) {
|
|
37905
38399
|
return {
|
|
38400
|
+
links: storage.threadLinksBySlug(),
|
|
37906
38401
|
prWatches: storage.armedPrWatchesBySlug(),
|
|
37907
38402
|
timers: storage.armedThreadTimersBySlug(),
|
|
37908
38403
|
questions: storage.threadQuestionsBySlug(),
|
|
@@ -38010,6 +38505,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
|
|
|
38010
38505
|
lastAssistantAt: tele?.lastAssistantAt,
|
|
38011
38506
|
subAgents: stampStoppable(tele?.subAgents ?? [], row),
|
|
38012
38507
|
bgShells: stampStoppableShells(tele?.bgShells ?? [], row),
|
|
38508
|
+
links: (registries.links.get(row.slug) ?? []).map(threadLinkView),
|
|
38013
38509
|
// ONE SOURCE: the FENCE. Both kinds are derived from what the worker wrote — `prs:` entries
|
|
38014
38510
|
// become the github rows, `watch:` lines the shell rows — so this strip lists exactly what will
|
|
38015
38511
|
// actually wake the thread, and the two cannot drift into claiming different things. There is no
|
|
@@ -38257,7 +38753,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
38257
38753
|
...project.githubRepo ? { githubRepo: project.githubRepo } : {},
|
|
38258
38754
|
projectSlug: findByPath(project.dir)?.slug,
|
|
38259
38755
|
// So the client can expand a `~` a worker wrote in prose (see BoardSnapshot.homeDir).
|
|
38260
|
-
homeDir:
|
|
38756
|
+
homeDir: homedir14()
|
|
38261
38757
|
};
|
|
38262
38758
|
const sessionThreads = buildSessionThreads(assembledAtMs);
|
|
38263
38759
|
armSnoozeWake(sessionThreads, assembledAtMs);
|
|
@@ -38358,10 +38854,10 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
38358
38854
|
if (parcelSub || stopped) return Promise.resolve();
|
|
38359
38855
|
if (watchSetup) return watchSetup;
|
|
38360
38856
|
const setup = (async () => {
|
|
38361
|
-
const dir =
|
|
38857
|
+
const dir = join30(project.dir, ".frizz");
|
|
38362
38858
|
const roots = /* @__PURE__ */ new Set([dir]);
|
|
38363
38859
|
try {
|
|
38364
|
-
roots.add(
|
|
38860
|
+
roots.add(realpathSync9(dir));
|
|
38365
38861
|
} catch {
|
|
38366
38862
|
}
|
|
38367
38863
|
const next = await subscribe(
|
|
@@ -38373,7 +38869,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
38373
38869
|
}
|
|
38374
38870
|
if (events.some((event) => isBoardRelevantFrizzPath(roots, event.path))) scheduleRebuild();
|
|
38375
38871
|
},
|
|
38376
|
-
{ ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) =>
|
|
38872
|
+
{ ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join30(dir, name)) }
|
|
38377
38873
|
);
|
|
38378
38874
|
if (stopped) {
|
|
38379
38875
|
await next.unsubscribe();
|
|
@@ -38455,6 +38951,7 @@ var init_board = __esm({
|
|
|
38455
38951
|
"use strict";
|
|
38456
38952
|
init_src();
|
|
38457
38953
|
init_storage();
|
|
38954
|
+
init_thread_links();
|
|
38458
38955
|
init_thread_profiles();
|
|
38459
38956
|
init_frizz();
|
|
38460
38957
|
init_awaiting();
|
|
@@ -40003,8 +40500,8 @@ ${wakeTimeHeader(nowMs, spokeAt)}`,
|
|
|
40003
40500
|
"in a sentence breaks the YAML, which is why `reason:` is gone.",
|
|
40004
40501
|
"",
|
|
40005
40502
|
"`mcp__frizz__activity` lists everything you have running, and prints it back as a ready-to-paste",
|
|
40006
|
-
"fence. If you are not waiting on anything, you are not awaiting \u2014 end with ```done, or
|
|
40007
|
-
"
|
|
40503
|
+
"fence. If you are not waiting on anything, you are not awaiting \u2014 end with ```done, or register",
|
|
40504
|
+
"a question with `mcp__frizz__ask`."
|
|
40008
40505
|
].join("\n") : nameless ? [
|
|
40009
40506
|
`${PARK_CORRECTION_NAMES_LEAD}nothing to wait on, so it is not a park \u2014 your thread is still`,
|
|
40010
40507
|
"in the queue, and nothing will wake you.",
|
|
@@ -40020,15 +40517,15 @@ ${wakeTimeHeader(nowMs, spokeAt)}`,
|
|
|
40020
40517
|
"Use `mcp__frizz__activity` to read it all back as a ready-to-paste fence.",
|
|
40021
40518
|
"",
|
|
40022
40519
|
"AND IF YOU ARE NOT WAITING ON ANYTHING, you are not awaiting \u2014 you are done. End with ```done,",
|
|
40023
|
-
"or
|
|
40520
|
+
"or register a question with `mcp__frizz__ask` if you need the human."
|
|
40024
40521
|
].join("\n") : expired ? parkExpiredWakeMessage(status) : allFinished ? parkFinishedWakeMessage(status, dead.length !== 1) : [
|
|
40025
40522
|
`${PARK_CORRECTION_NAMES_LEAD}${dead.length === 1 ? "something that is" : "things that are"} not running, so it is not a park and your thread stayed in the queue.`,
|
|
40026
40523
|
"",
|
|
40027
40524
|
...status,
|
|
40028
40525
|
"",
|
|
40029
40526
|
"Use `mcp__frizz__activity` to read back what you actually have out, with the exact id each line",
|
|
40030
|
-
"needs, then re-fence naming only those \u2014 or end in ```done or a
|
|
40031
|
-
"left to wait for."
|
|
40527
|
+
"needs, then re-fence naming only those \u2014 or end in ```done or a registered question",
|
|
40528
|
+
"(`mcp__frizz__ask`) if there is nothing left to wait for."
|
|
40032
40529
|
].join("\n");
|
|
40033
40530
|
const cause = retired.length > 0 ? "retired" : nameless ? "nameless" : expired ? "expired" : "dead";
|
|
40034
40531
|
const ops = cause === "expired" ? [] : liveOpsLines({
|
|
@@ -41050,7 +41547,7 @@ var init_resume = __esm({
|
|
|
41050
41547
|
});
|
|
41051
41548
|
|
|
41052
41549
|
// packages/server/src/backend/claude.ts
|
|
41053
|
-
import { join as
|
|
41550
|
+
import { join as join31 } from "node:path";
|
|
41054
41551
|
function toolResultText2(content) {
|
|
41055
41552
|
if (typeof content === "string") return content;
|
|
41056
41553
|
if (!Array.isArray(content)) return "";
|
|
@@ -41141,7 +41638,7 @@ function createClaudeBackend(opts) {
|
|
|
41141
41638
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
41142
41639
|
},
|
|
41143
41640
|
transcriptPath(sessionId) {
|
|
41144
|
-
return
|
|
41641
|
+
return join31(opts.logDir, `${sessionId}.jsonl`);
|
|
41145
41642
|
},
|
|
41146
41643
|
parseLine(line) {
|
|
41147
41644
|
return parseClaudeLine(line);
|
|
@@ -41170,10 +41667,31 @@ import { randomBytes } from "node:crypto";
|
|
|
41170
41667
|
import pty from "node-pty";
|
|
41171
41668
|
function createLoginUtility(deps) {
|
|
41172
41669
|
const spawnPty = deps.spawnPty ?? pty.spawn;
|
|
41670
|
+
const env = deps.env ?? process.env;
|
|
41173
41671
|
const lifetimeMs = deps.lifetimeMs ?? ATTEMPT_LIFETIME_MS;
|
|
41174
41672
|
const attempts = /* @__PURE__ */ new Map();
|
|
41175
41673
|
function loginArgv(backend) {
|
|
41176
|
-
|
|
41674
|
+
if (backend === "codex") {
|
|
41675
|
+
const codex = resolveCodexExecutable(deps.codexBin, { env });
|
|
41676
|
+
return { file: codex.file, args: [...codex.args, "login"] };
|
|
41677
|
+
}
|
|
41678
|
+
return { file: resolveClaudeExecutableAbsolute(deps.claudeBin, env), args: ["auth", "login"] };
|
|
41679
|
+
}
|
|
41680
|
+
function failedAttempt(id, backend, cause) {
|
|
41681
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
41682
|
+
const error = `Could not start the ${backend === "codex" ? "Codex" : "Claude"} sign-in: ${reason}`;
|
|
41683
|
+
return {
|
|
41684
|
+
id,
|
|
41685
|
+
backend,
|
|
41686
|
+
timer: setTimeout(() => teardown(id), lifetimeMs),
|
|
41687
|
+
buffer: `${error}\r
|
|
41688
|
+
`,
|
|
41689
|
+
bufferBytes: Buffer.byteLength(error) + 2,
|
|
41690
|
+
exited: true,
|
|
41691
|
+
error,
|
|
41692
|
+
dataListeners: /* @__PURE__ */ new Set(),
|
|
41693
|
+
exitListeners: /* @__PURE__ */ new Set()
|
|
41694
|
+
};
|
|
41177
41695
|
}
|
|
41178
41696
|
function teardown(id) {
|
|
41179
41697
|
const attempt = attempts.get(id);
|
|
@@ -41191,7 +41709,7 @@ function createLoginUtility(deps) {
|
|
|
41191
41709
|
}
|
|
41192
41710
|
attempt.exitListeners.clear();
|
|
41193
41711
|
try {
|
|
41194
|
-
attempt.term
|
|
41712
|
+
attempt.term?.kill();
|
|
41195
41713
|
} catch {
|
|
41196
41714
|
}
|
|
41197
41715
|
}
|
|
@@ -41203,14 +41721,22 @@ function createLoginUtility(deps) {
|
|
|
41203
41721
|
teardown(attempt2.id);
|
|
41204
41722
|
}
|
|
41205
41723
|
const id = `login-${randomBytes(8).toString("hex")}`;
|
|
41206
|
-
|
|
41207
|
-
|
|
41208
|
-
|
|
41209
|
-
|
|
41210
|
-
|
|
41211
|
-
|
|
41212
|
-
|
|
41213
|
-
|
|
41724
|
+
let term;
|
|
41725
|
+
try {
|
|
41726
|
+
const { file, args } = loginArgv(backend);
|
|
41727
|
+
term = spawnPty(file, args, {
|
|
41728
|
+
name: process.platform === "win32" ? "xterm-color" : "xterm-256color",
|
|
41729
|
+
cwd: deps.cwd,
|
|
41730
|
+
env,
|
|
41731
|
+
cols: 120,
|
|
41732
|
+
rows: 30
|
|
41733
|
+
});
|
|
41734
|
+
} catch (cause) {
|
|
41735
|
+
const failed = failedAttempt(id, backend, cause);
|
|
41736
|
+
failed.timer.unref?.();
|
|
41737
|
+
attempts.set(id, failed);
|
|
41738
|
+
return { attemptId: id };
|
|
41739
|
+
}
|
|
41214
41740
|
const attempt = {
|
|
41215
41741
|
id,
|
|
41216
41742
|
backend,
|
|
@@ -41262,7 +41788,7 @@ function createLoginUtility(deps) {
|
|
|
41262
41788
|
write: (data) => {
|
|
41263
41789
|
if (!attempt.exited) {
|
|
41264
41790
|
try {
|
|
41265
|
-
attempt.term
|
|
41791
|
+
attempt.term?.write(data);
|
|
41266
41792
|
} catch {
|
|
41267
41793
|
}
|
|
41268
41794
|
}
|
|
@@ -41270,7 +41796,7 @@ function createLoginUtility(deps) {
|
|
|
41270
41796
|
resize: (cols, rows) => {
|
|
41271
41797
|
if (!attempt.exited) {
|
|
41272
41798
|
try {
|
|
41273
|
-
attempt.term
|
|
41799
|
+
attempt.term?.resize(cols, rows);
|
|
41274
41800
|
} catch {
|
|
41275
41801
|
}
|
|
41276
41802
|
}
|
|
@@ -41291,7 +41817,7 @@ function createLoginUtility(deps) {
|
|
|
41291
41817
|
status(attemptId) {
|
|
41292
41818
|
const attempt = attempts.get(attemptId);
|
|
41293
41819
|
if (!attempt) return { state: "exited" };
|
|
41294
|
-
return { state: attempt.exited ? "exited" : "running", backend: attempt.backend };
|
|
41820
|
+
return { state: attempt.exited ? "exited" : "running", backend: attempt.backend, ...attempt.error ? { error: attempt.error } : {} };
|
|
41295
41821
|
},
|
|
41296
41822
|
cancel(attemptId) {
|
|
41297
41823
|
teardown(attemptId);
|
|
@@ -41305,19 +41831,21 @@ var ATTEMPT_LIFETIME_MS, REPLAY_CAP_BYTES;
|
|
|
41305
41831
|
var init_login_utility = __esm({
|
|
41306
41832
|
"packages/server/src/login-utility.ts"() {
|
|
41307
41833
|
"use strict";
|
|
41834
|
+
init_claude_broker_host();
|
|
41835
|
+
init_codex_executable();
|
|
41308
41836
|
ATTEMPT_LIFETIME_MS = 10 * 60 * 1e3;
|
|
41309
41837
|
REPLAY_CAP_BYTES = 256 * 1024;
|
|
41310
41838
|
}
|
|
41311
41839
|
});
|
|
41312
41840
|
|
|
41313
41841
|
// packages/server/src/backend/codex-app-server-diagnostics.ts
|
|
41314
|
-
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as
|
|
41315
|
-
import { join as
|
|
41842
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as statSync14 } from "node:fs";
|
|
41843
|
+
import { join as join32 } from "node:path";
|
|
41316
41844
|
function codexDiagnosticLogPath(stateDir, projectId) {
|
|
41317
|
-
return
|
|
41845
|
+
return join32(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
|
|
41318
41846
|
}
|
|
41319
41847
|
function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
|
|
41320
|
-
const dir =
|
|
41848
|
+
const dir = join32(stateDir, "codex-app-server");
|
|
41321
41849
|
const path3 = codexDiagnosticLogPath(stateDir, projectId);
|
|
41322
41850
|
let ensured = false;
|
|
41323
41851
|
return (event) => {
|
|
@@ -41328,7 +41856,7 @@ function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__
|
|
|
41328
41856
|
ensured = true;
|
|
41329
41857
|
}
|
|
41330
41858
|
try {
|
|
41331
|
-
if (
|
|
41859
|
+
if (statSync14(path3).size > MAX_BYTES2) renameSync8(path3, `${path3}.1`);
|
|
41332
41860
|
} catch {
|
|
41333
41861
|
}
|
|
41334
41862
|
appendFileSync3(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
|
|
@@ -41349,7 +41877,7 @@ var init_codex_app_server_diagnostics = __esm({
|
|
|
41349
41877
|
import { execFile as execFile8 } from "node:child_process";
|
|
41350
41878
|
import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
|
|
41351
41879
|
import { connect as connectSocket } from "node:net";
|
|
41352
|
-
import { join as
|
|
41880
|
+
import { join as join33 } from "node:path";
|
|
41353
41881
|
function parseLsofSocketNames(stdout) {
|
|
41354
41882
|
const referenced = /* @__PURE__ */ new Set();
|
|
41355
41883
|
for (const line of stdout.split("\n")) {
|
|
@@ -41366,7 +41894,7 @@ function sweepStaleSockets(options, deps = {}) {
|
|
|
41366
41894
|
const keep = new Set(options.keep ?? []);
|
|
41367
41895
|
let candidates;
|
|
41368
41896
|
try {
|
|
41369
|
-
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) =>
|
|
41897
|
+
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join33(options.dir, name)).filter((path3) => !keep.has(path3));
|
|
41370
41898
|
} catch {
|
|
41371
41899
|
return;
|
|
41372
41900
|
}
|
|
@@ -41416,7 +41944,7 @@ var init_stale_socket_sweep = __esm({
|
|
|
41416
41944
|
|
|
41417
41945
|
// packages/server/src/orphan-reaper.ts
|
|
41418
41946
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
41419
|
-
import { readFileSync as
|
|
41947
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
41420
41948
|
import { dirname as dirname13 } from "node:path";
|
|
41421
41949
|
function firstTokenBasename(command) {
|
|
41422
41950
|
const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
|
|
@@ -41572,7 +42100,7 @@ ${chunk}`;
|
|
|
41572
42100
|
}
|
|
41573
42101
|
return rows;
|
|
41574
42102
|
}
|
|
41575
|
-
function reapSubtrees(seed, rows, protectedPids, live, kill =
|
|
42103
|
+
function reapSubtrees(seed, rows, protectedPids, live, kill = defaultKill2) {
|
|
41576
42104
|
const keep = (row) => protectedPids.has(row.pid) || isSessionRoot(row.command) || isTmuxServer(row.command) || row.slug !== null && live.has(row.slug);
|
|
41577
42105
|
const order = subtreeKillOrder(seed, rows, keep);
|
|
41578
42106
|
for (const pid of order) kill(pid);
|
|
@@ -41624,8 +42152,9 @@ function sweepOrphansOnce(deps = {}) {
|
|
|
41624
42152
|
let rows;
|
|
41625
42153
|
try {
|
|
41626
42154
|
rows = enumerateProcs(exec, { platform: deps.platform, readEnv: deps.readEnv });
|
|
41627
|
-
} catch {
|
|
41628
|
-
|
|
42155
|
+
} catch (error) {
|
|
42156
|
+
const code = error?.code;
|
|
42157
|
+
return { reaped: 0, deadSlugs: [], liveSlugs: [], ...code === "ENOENT" ? { unavailable: "`ps` is not on PATH" } : {} };
|
|
41629
42158
|
}
|
|
41630
42159
|
const protectedPids = selfAndAncestors(rows, selfPid);
|
|
41631
42160
|
const { reap, liveSlugs } = decideOrphans(rows, { minAgeMs, protectedPids });
|
|
@@ -41649,14 +42178,25 @@ function sweepStaleBrokerSockets() {
|
|
|
41649
42178
|
}
|
|
41650
42179
|
}
|
|
41651
42180
|
function startOrphanReaper(deps = {}) {
|
|
42181
|
+
if ((deps.platform ?? process.platform) === "win32") {
|
|
42182
|
+
deps.log?.(UNAVAILABLE_ON_WINDOWS);
|
|
42183
|
+
return () => {
|
|
42184
|
+
};
|
|
42185
|
+
}
|
|
41652
42186
|
const intervalMs = deps.intervalMs ?? ORPHAN_REAP_INTERVAL_MS;
|
|
42187
|
+
const unavailable = (result) => {
|
|
42188
|
+
if (!result.unavailable) return false;
|
|
42189
|
+
deps.log?.(`orphan-reaper: unavailable on this machine \u2014 ${result.unavailable}; background processes a stopped thread leaves behind are not collected here`);
|
|
42190
|
+
return true;
|
|
42191
|
+
};
|
|
41653
42192
|
try {
|
|
41654
|
-
sweepOrphansOnce(deps)
|
|
42193
|
+
if (unavailable(sweepOrphansOnce(deps))) return () => {
|
|
42194
|
+
};
|
|
41655
42195
|
} catch {
|
|
41656
42196
|
}
|
|
41657
42197
|
const timer = setInterval(() => {
|
|
41658
42198
|
try {
|
|
41659
|
-
sweepOrphansOnce(deps);
|
|
42199
|
+
if (unavailable(sweepOrphansOnce(deps))) clearInterval(timer);
|
|
41660
42200
|
} catch {
|
|
41661
42201
|
}
|
|
41662
42202
|
}, intervalMs);
|
|
@@ -41671,7 +42211,7 @@ function startOrphanReaper(deps = {}) {
|
|
|
41671
42211
|
clearInterval(socketTimer);
|
|
41672
42212
|
};
|
|
41673
42213
|
}
|
|
41674
|
-
var ORPHAN_REAP_INTERVAL_MS, ORPHAN_MIN_AGE_MS, SLUG_RE, defaultExec, defaultReadEnv,
|
|
42214
|
+
var ORPHAN_REAP_INTERVAL_MS, ORPHAN_MIN_AGE_MS, SLUG_RE, defaultExec, defaultReadEnv, defaultKill2, STALE_SOCKET_SWEEP_INTERVAL_MS, STALE_SOCKET_SWEEP_DELAY_MS, CLAUDE_BROKER_SOCKET_PREFIX, UNAVAILABLE_ON_WINDOWS;
|
|
41675
42215
|
var init_orphan_reaper = __esm({
|
|
41676
42216
|
"packages/server/src/orphan-reaper.ts"() {
|
|
41677
42217
|
"use strict";
|
|
@@ -41683,12 +42223,12 @@ var init_orphan_reaper = __esm({
|
|
|
41683
42223
|
defaultExec = (file, args) => execFileSync4(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
|
|
41684
42224
|
defaultReadEnv = (pid) => {
|
|
41685
42225
|
try {
|
|
41686
|
-
return
|
|
42226
|
+
return readFileSync21(`/proc/${pid}/environ`, "utf8");
|
|
41687
42227
|
} catch {
|
|
41688
42228
|
return null;
|
|
41689
42229
|
}
|
|
41690
42230
|
};
|
|
41691
|
-
|
|
42231
|
+
defaultKill2 = (pid) => {
|
|
41692
42232
|
try {
|
|
41693
42233
|
process.kill(pid, "SIGKILL");
|
|
41694
42234
|
} catch {
|
|
@@ -41697,6 +42237,7 @@ var init_orphan_reaper = __esm({
|
|
|
41697
42237
|
STALE_SOCKET_SWEEP_INTERVAL_MS = 10 * 6e4;
|
|
41698
42238
|
STALE_SOCKET_SWEEP_DELAY_MS = 3e4;
|
|
41699
42239
|
CLAUDE_BROKER_SOCKET_PREFIX = "frizz-claude-";
|
|
42240
|
+
UNAVAILABLE_ON_WINDOWS = "orphan-reaper: unavailable on Windows \u2014 a worker's FRIZZ_THREAD is read from its process environment, which Windows does not expose; background processes a stopped thread leaves behind are not collected here, and a live thread's runaway work is not reported";
|
|
41700
42241
|
}
|
|
41701
42242
|
});
|
|
41702
42243
|
|
|
@@ -41830,9 +42371,9 @@ var init_thread_hibernation = __esm({
|
|
|
41830
42371
|
});
|
|
41831
42372
|
|
|
41832
42373
|
// packages/server/src/context.ts
|
|
41833
|
-
import { join as
|
|
42374
|
+
import { join as join34 } from "node:path";
|
|
41834
42375
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
41835
|
-
import { homedir as
|
|
42376
|
+
import { homedir as homedir15 } from "node:os";
|
|
41836
42377
|
function reconcileSessions(storage) {
|
|
41837
42378
|
for (const row of storage.allSessions()) {
|
|
41838
42379
|
if (isHeadlessRow(row)) continue;
|
|
@@ -41978,9 +42519,9 @@ async function createContext(opts = {}) {
|
|
|
41978
42519
|
}
|
|
41979
42520
|
}
|
|
41980
42521
|
function createContextUnchecked(opts, resources) {
|
|
41981
|
-
const home = opts.home ??
|
|
42522
|
+
const home = opts.home ?? homedir15();
|
|
41982
42523
|
const project = opts.project ?? resolveProject();
|
|
41983
|
-
const storage = createStorage(opts.database ??
|
|
42524
|
+
const storage = createStorage(opts.database ?? join34(project.stateDir, "ui.db"), project.id);
|
|
41984
42525
|
resources.storage = storage;
|
|
41985
42526
|
const bus = new Bus();
|
|
41986
42527
|
const transcriptChange = new Emitter();
|
|
@@ -42090,7 +42631,10 @@ function createContextUnchecked(opts, resources) {
|
|
|
42090
42631
|
const mode = PermissionMode.safeParse(row.permission_mode);
|
|
42091
42632
|
if (!mode.success) return void 0;
|
|
42092
42633
|
return codexSandbox(mode.data);
|
|
42093
|
-
}
|
|
42634
|
+
},
|
|
42635
|
+
// Read at every thread/start and cold resume, so the Codex context window the operator picks in
|
|
42636
|
+
// Settings reaches the next thread without an app-server restart.
|
|
42637
|
+
getSettings: () => getSettings(storage, home)
|
|
42094
42638
|
}) : void 0;
|
|
42095
42639
|
resources.codexAppServer = codexAppServer;
|
|
42096
42640
|
if (codexAppServer) {
|
|
@@ -45035,8 +45579,8 @@ var init_server = __esm({
|
|
|
45035
45579
|
});
|
|
45036
45580
|
|
|
45037
45581
|
// packages/server/src/repair.ts
|
|
45038
|
-
import { existsSync as
|
|
45039
|
-
import { basename as basename7, dirname as dirname14, resolve as
|
|
45582
|
+
import { existsSync as existsSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "node:fs";
|
|
45583
|
+
import { basename as basename7, dirname as dirname14, resolve as resolve8 } from "node:path";
|
|
45040
45584
|
function quoteValue(v) {
|
|
45041
45585
|
if (/^[\w./#:+-]+$/.test(v)) return v;
|
|
45042
45586
|
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
@@ -45046,12 +45590,12 @@ function deriveTitle(body, slug) {
|
|
|
45046
45590
|
return m ? m[1].trim() : slug;
|
|
45047
45591
|
}
|
|
45048
45592
|
function repairThreadFile(frizzDir, file) {
|
|
45049
|
-
const root =
|
|
45050
|
-
const abs =
|
|
45593
|
+
const root = resolve8(frizzDir);
|
|
45594
|
+
const abs = resolve8(root, file);
|
|
45051
45595
|
if (dirname14(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
|
|
45052
45596
|
if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
|
|
45053
|
-
if (!
|
|
45054
|
-
const content =
|
|
45597
|
+
if (!existsSync12(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
|
|
45598
|
+
const content = readFileSync22(abs, "utf8");
|
|
45055
45599
|
const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
|
|
45056
45600
|
if (firstLine2?.trim() === "---") {
|
|
45057
45601
|
throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
|
|
@@ -45242,9 +45786,9 @@ var init_repo_files = __esm({
|
|
|
45242
45786
|
});
|
|
45243
45787
|
|
|
45244
45788
|
// cc-worker/hooks/bash-background.mjs
|
|
45245
|
-
import { readFileSync as
|
|
45789
|
+
import { readFileSync as readFileSync23, realpathSync as realpathSync10 } from "node:fs";
|
|
45246
45790
|
import { basename as basename8 } from "node:path";
|
|
45247
|
-
import { pathToFileURL } from "node:url";
|
|
45791
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
|
|
45248
45792
|
function emit(obj) {
|
|
45249
45793
|
process.stdout.write(JSON.stringify(obj));
|
|
45250
45794
|
process.exit(0);
|
|
@@ -45380,8 +45924,14 @@ function evaluateBashBackgroundHook(input, env = process.env) {
|
|
|
45380
45924
|
}
|
|
45381
45925
|
};
|
|
45382
45926
|
}
|
|
45383
|
-
function isDirectHookExecution(argv1, moduleUrl) {
|
|
45384
|
-
|
|
45927
|
+
function isDirectHookExecution(argv1, moduleUrl, realpath = realpathSync10) {
|
|
45928
|
+
if (typeof argv1 !== "string" || basename8(argv1) !== "bash-background.mjs") return false;
|
|
45929
|
+
if (pathToFileURL(argv1).href === moduleUrl) return true;
|
|
45930
|
+
try {
|
|
45931
|
+
return realpath(argv1) === realpath(fileURLToPath4(moduleUrl));
|
|
45932
|
+
} catch {
|
|
45933
|
+
return false;
|
|
45934
|
+
}
|
|
45385
45935
|
}
|
|
45386
45936
|
var LOCAL_SHELL_SCRIPT;
|
|
45387
45937
|
var init_bash_background = __esm({
|
|
@@ -45391,7 +45941,7 @@ var init_bash_background = __esm({
|
|
|
45391
45941
|
if (isDirectHookExecution(process.argv[1], import.meta.url)) {
|
|
45392
45942
|
try {
|
|
45393
45943
|
const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
|
|
45394
|
-
emit(evaluateBashBackgroundHook(JSON.parse(
|
|
45944
|
+
emit(evaluateBashBackgroundHook(JSON.parse(readFileSync23(0, "utf8")), env));
|
|
45395
45945
|
} catch {
|
|
45396
45946
|
emit({});
|
|
45397
45947
|
}
|
|
@@ -45400,11 +45950,11 @@ var init_bash_background = __esm({
|
|
|
45400
45950
|
});
|
|
45401
45951
|
|
|
45402
45952
|
// packages/server/src/transcript.ts
|
|
45403
|
-
import { closeSync as closeSync13, existsSync as
|
|
45953
|
+
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 statSync15, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
|
|
45404
45954
|
import { createHash as createHash13 } from "node:crypto";
|
|
45405
45955
|
import { StringDecoder as StringDecoder4 } from "node:string_decoder";
|
|
45406
|
-
import { join as
|
|
45407
|
-
import { homedir as
|
|
45956
|
+
import { join as join35 } from "node:path";
|
|
45957
|
+
import { homedir as homedir16 } from "node:os";
|
|
45408
45958
|
function formatTokens(n) {
|
|
45409
45959
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
45410
45960
|
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
@@ -45983,14 +46533,14 @@ function pruneScreenshotCache() {
|
|
|
45983
46533
|
if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
|
|
45984
46534
|
const byMtime = entries2.map((n) => {
|
|
45985
46535
|
try {
|
|
45986
|
-
return { n, m:
|
|
46536
|
+
return { n, m: statSync15(join35(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
|
|
45987
46537
|
} catch {
|
|
45988
46538
|
return { n, m: 0 };
|
|
45989
46539
|
}
|
|
45990
46540
|
}).sort((a, b) => b.m - a.m);
|
|
45991
46541
|
for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
|
|
45992
46542
|
try {
|
|
45993
|
-
unlinkSync5(
|
|
46543
|
+
unlinkSync5(join35(SCREENSHOT_CACHE_DIR, n));
|
|
45994
46544
|
} catch {
|
|
45995
46545
|
}
|
|
45996
46546
|
}
|
|
@@ -46020,14 +46570,14 @@ function persistBase64Image(mediaType, data, idKey) {
|
|
|
46020
46570
|
const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
|
|
46021
46571
|
if (!ext || !data) return void 0;
|
|
46022
46572
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
46023
|
-
const path3 =
|
|
46573
|
+
const path3 = join35(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
|
|
46024
46574
|
try {
|
|
46025
|
-
if (
|
|
46575
|
+
if (existsSync13(path3)) return path3;
|
|
46026
46576
|
if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
|
|
46027
46577
|
const buf = Buffer.from(data, "base64");
|
|
46028
46578
|
if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
|
|
46029
46579
|
mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
46030
|
-
const tmp =
|
|
46580
|
+
const tmp = join35(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
46031
46581
|
writeFileSync12(tmp, buf);
|
|
46032
46582
|
renameSync9(tmp, path3);
|
|
46033
46583
|
pruneScreenshotCache();
|
|
@@ -46042,14 +46592,14 @@ function persistSentFile(srcPath, idKey) {
|
|
|
46042
46592
|
const outExt = ext === "jpeg" ? "jpg" : ext;
|
|
46043
46593
|
try {
|
|
46044
46594
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
46045
|
-
const dest =
|
|
46046
|
-
if (
|
|
46047
|
-
const size =
|
|
46595
|
+
const dest = join35(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
|
|
46596
|
+
if (existsSync13(dest)) return dest;
|
|
46597
|
+
const size = statSync15(srcPath).size;
|
|
46048
46598
|
if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
|
|
46049
|
-
const buf =
|
|
46599
|
+
const buf = readFileSync24(srcPath);
|
|
46050
46600
|
if (!looksLikeImage(buf, outExt)) return void 0;
|
|
46051
46601
|
mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
46052
|
-
const tmp =
|
|
46602
|
+
const tmp = join35(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
46053
46603
|
writeFileSync12(tmp, buf);
|
|
46054
46604
|
renameSync9(tmp, dest);
|
|
46055
46605
|
pruneScreenshotCache();
|
|
@@ -46487,7 +47037,7 @@ function readTranscript(project, sessionId) {
|
|
|
46487
47037
|
}
|
|
46488
47038
|
function verifyIncrementalParse(path3, identityPrefix, incremental) {
|
|
46489
47039
|
try {
|
|
46490
|
-
const fresh = parseTranscript(
|
|
47040
|
+
const fresh = parseTranscript(readFileSync24(path3, "utf8"), identityPrefix);
|
|
46491
47041
|
const a = JSON.stringify(incremental);
|
|
46492
47042
|
const b = JSON.stringify(fresh);
|
|
46493
47043
|
if (a !== b) {
|
|
@@ -46507,16 +47057,16 @@ function verifyIncrementalParse(path3, identityPrefix, incremental) {
|
|
|
46507
47057
|
}
|
|
46508
47058
|
}
|
|
46509
47059
|
function logDirOf(project) {
|
|
46510
|
-
return
|
|
47060
|
+
return join35(homedir16(), ".claude", "projects", project.cwdSlug);
|
|
46511
47061
|
}
|
|
46512
47062
|
function resolveTranscriptPath(project, sessionId) {
|
|
46513
|
-
const path3 =
|
|
47063
|
+
const path3 = join35(logDirOf(project), `${sessionId}.jsonl`);
|
|
46514
47064
|
try {
|
|
46515
|
-
if (
|
|
47065
|
+
if (statSync15(path3).size > 0) return path3;
|
|
46516
47066
|
} catch {
|
|
46517
47067
|
}
|
|
46518
47068
|
const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
|
|
46519
|
-
return stranded ?
|
|
47069
|
+
return stranded ? join35(stranded, `${sessionId}.jsonl`) : path3;
|
|
46520
47070
|
}
|
|
46521
47071
|
function projectCodexTranscript(raw2, identityPrefix = "codex") {
|
|
46522
47072
|
const out = [];
|
|
@@ -46581,8 +47131,9 @@ ${text2}` : text2;
|
|
|
46581
47131
|
}
|
|
46582
47132
|
case "tool-call": {
|
|
46583
47133
|
const m = openAssistant(ev.at, sourceId);
|
|
46584
|
-
const
|
|
46585
|
-
call
|
|
47134
|
+
const split = codexToolCards(ev.name, ev.input, ev.id);
|
|
47135
|
+
const call = split.owner;
|
|
47136
|
+
for (const card of split.cards) card.status = "pending";
|
|
46586
47137
|
if (pendingCaption) {
|
|
46587
47138
|
if (!call.desc) call.desc = pendingCaption;
|
|
46588
47139
|
pendingCaption = void 0;
|
|
@@ -46592,8 +47143,10 @@ ${text2}` : text2;
|
|
|
46592
47143
|
const interruptedOwner = call.name === "Interrupt process" && call.sessionId !== void 0 ? shellSessions.get(runningKey("Poll process", call.sessionId)) : void 0;
|
|
46593
47144
|
if (!owner) {
|
|
46594
47145
|
if (isPoll) call.backgroundState = "unknown";
|
|
46595
|
-
|
|
46596
|
-
|
|
47146
|
+
for (const card of split.cards) {
|
|
47147
|
+
pushToolPart(m, card);
|
|
47148
|
+
m.tools.push(card);
|
|
47149
|
+
}
|
|
46597
47150
|
}
|
|
46598
47151
|
if (call.name === "Spawn agent" && call.detail) agentDispatches.set(call.detail, { call, at: ev.at });
|
|
46599
47152
|
if (ev.id) pendingCalls.set(ev.id, {
|
|
@@ -46602,7 +47155,8 @@ ${text2}` : text2;
|
|
|
46602
47155
|
owner,
|
|
46603
47156
|
interruptedOwner,
|
|
46604
47157
|
orphanPoll: isPoll && !owner,
|
|
46605
|
-
explicitBackground: codexExplicitBackground(ev.name, ev.input)
|
|
47158
|
+
explicitBackground: codexExplicitBackground(ev.name, ev.input),
|
|
47159
|
+
...split.cards.length > 1 ? { companions: split.cards.filter((card) => card !== call), views: split.views } : {}
|
|
46606
47160
|
});
|
|
46607
47161
|
break;
|
|
46608
47162
|
}
|
|
@@ -46612,7 +47166,17 @@ ${text2}` : text2;
|
|
|
46612
47166
|
pendingCalls.delete(ev.id);
|
|
46613
47167
|
const result = codexToolResult(ev.text);
|
|
46614
47168
|
if (result.durationMs === void 0) result.durationMs = elapsedBetween(pending.at, ev.at);
|
|
46615
|
-
if (
|
|
47169
|
+
if (pending.companions) {
|
|
47170
|
+
for (const card of pending.companions) card.status = result.status === "cancelled" ? "cancelled" : "completed";
|
|
47171
|
+
if (result.status !== "cancelled") {
|
|
47172
|
+
const inline = ev.images ?? (ev.image ? [ev.image] : []);
|
|
47173
|
+
pending.views?.forEach((view, i) => {
|
|
47174
|
+
if (view.outputImage || !inline[i]) return;
|
|
47175
|
+
const shot = persistDataUrlImage(inline[i], `${ev.id}:view:${i}`);
|
|
47176
|
+
if (shot) view.outputImage = shot;
|
|
47177
|
+
});
|
|
47178
|
+
}
|
|
47179
|
+
} else if (ev.image && result.status !== "failed" && result.status !== "cancelled") {
|
|
46616
47180
|
const shot = persistDataUrlImage(ev.image, ev.id);
|
|
46617
47181
|
if (shot) (pending.owner ?? pending).call.outputImage = shot;
|
|
46618
47182
|
}
|
|
@@ -46841,6 +47405,11 @@ function latestTranscriptWindow(messages) {
|
|
|
46841
47405
|
}
|
|
46842
47406
|
return [...earlierInstructions, ...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
|
|
46843
47407
|
}
|
|
47408
|
+
function codexToolCards(name, input, callId) {
|
|
47409
|
+
if (name === "exec" && typeof input === "string") return codexExecWrapperCards(input, callId);
|
|
47410
|
+
const owner = codexToolCall(name, input, callId);
|
|
47411
|
+
return { owner, cards: [owner] };
|
|
47412
|
+
}
|
|
46844
47413
|
function codexToolCall(name, input, callId) {
|
|
46845
47414
|
if (name === "exec" && typeof input === "string") return codexExecWrapperCall(input, callId);
|
|
46846
47415
|
const obj = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
@@ -46999,8 +47568,32 @@ function formatCompactDuration(ms) {
|
|
|
46999
47568
|
if (minutes < 60) return `${minutes}m`;
|
|
47000
47569
|
return minutes % 60 ? `${Math.floor(minutes / 60)}h ${minutes % 60}m` : `${Math.floor(minutes / 60)}h`;
|
|
47001
47570
|
}
|
|
47002
|
-
function
|
|
47571
|
+
function codexExecWrapperCards(source, callId) {
|
|
47003
47572
|
const calls = wrappedInvocations(source);
|
|
47573
|
+
const rest = [];
|
|
47574
|
+
const views = [];
|
|
47575
|
+
const order = [];
|
|
47576
|
+
if (calls.length > 1) {
|
|
47577
|
+
for (const call of calls) {
|
|
47578
|
+
if (call.name !== "view_image") {
|
|
47579
|
+
if (!rest.length) order.push("rest");
|
|
47580
|
+
rest.push(call);
|
|
47581
|
+
continue;
|
|
47582
|
+
}
|
|
47583
|
+
const view = viewImageCall(jsStringProperty(call.args, "path"), `${callId ?? source}:view:${views.length}`);
|
|
47584
|
+
views.push(view);
|
|
47585
|
+
order.push(view);
|
|
47586
|
+
}
|
|
47587
|
+
}
|
|
47588
|
+
if (!views.length) {
|
|
47589
|
+
const owner = codexExecWrapperCall(source, callId, calls);
|
|
47590
|
+
return { owner, cards: [owner] };
|
|
47591
|
+
}
|
|
47592
|
+
const remainder = rest.length ? codexExecWrapperCall(source, callId, rest) : void 0;
|
|
47593
|
+
const cards = order.map((entry) => entry === "rest" ? remainder : entry);
|
|
47594
|
+
return { owner: remainder ?? views[0], cards, views };
|
|
47595
|
+
}
|
|
47596
|
+
function codexExecWrapperCall(source, callId, calls = wrappedInvocations(source)) {
|
|
47004
47597
|
if (calls.length !== 1) {
|
|
47005
47598
|
return {
|
|
47006
47599
|
name: "Exec",
|
|
@@ -47008,7 +47601,9 @@ function codexExecWrapperCall(source, callId) {
|
|
|
47008
47601
|
input: capToolInput(source.trim())
|
|
47009
47602
|
};
|
|
47010
47603
|
}
|
|
47011
|
-
|
|
47604
|
+
return wrappedSingleCall(calls[0], source, callId);
|
|
47605
|
+
}
|
|
47606
|
+
function wrappedSingleCall(call, source, callId) {
|
|
47012
47607
|
if (call.name === "exec_command") {
|
|
47013
47608
|
const cmd = jsStringProperty(call.args, "cmd") ?? jsStringProperty(call.args, "command");
|
|
47014
47609
|
const cwd = jsStringProperty(call.args, "workdir") ?? jsStringProperty(call.args, "cwd");
|
|
@@ -47384,7 +47979,7 @@ function unifiedToolResult(text2) {
|
|
|
47384
47979
|
if (!header) return void 0;
|
|
47385
47980
|
const wrapperStatus = header[1] === "failed" ? "failed" : header[1] === "terminated" ? "cancelled" : "completed";
|
|
47386
47981
|
const wrapperDurationMs = Number(header[2]) * 1e3;
|
|
47387
|
-
const body = raw2.slice(header[0].length).trim();
|
|
47982
|
+
const body = raw2.slice(header[0].length).split("[image output]").join("").trim();
|
|
47388
47983
|
if (!body || body === "{}") return { status: wrapperStatus, durationMs: wrapperDurationMs, terminal: true };
|
|
47389
47984
|
try {
|
|
47390
47985
|
const parsed = JSON.parse(body);
|
|
@@ -47498,7 +48093,7 @@ function sourceForThread(project, storage, slug, backendFor) {
|
|
|
47498
48093
|
}
|
|
47499
48094
|
if (!FOREIGN_SESSION_ID_RE.test(slug)) return void 0;
|
|
47500
48095
|
const claudePath = resolveTranscriptPath(project, slug);
|
|
47501
|
-
if (
|
|
48096
|
+
if (existsSync13(claudePath)) {
|
|
47502
48097
|
return { slug, sessionId: slug, nativeId: slug, backend: "claude", runtimeGeneration: 0, path: claudePath };
|
|
47503
48098
|
}
|
|
47504
48099
|
const rollout = (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(slug);
|
|
@@ -47764,7 +48359,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
|
|
|
47764
48359
|
}
|
|
47765
48360
|
function readCodexTranscriptFile(absPath, nativeId = absPath) {
|
|
47766
48361
|
try {
|
|
47767
|
-
return parseCodexTranscript(
|
|
48362
|
+
return parseCodexTranscript(readFileSync24(absPath, "utf8"), `codex:${nativeId}`);
|
|
47768
48363
|
} catch {
|
|
47769
48364
|
return [];
|
|
47770
48365
|
}
|
|
@@ -47862,7 +48457,7 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
|
|
|
47862
48457
|
function readTranscriptFile(absPath) {
|
|
47863
48458
|
try {
|
|
47864
48459
|
const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
|
|
47865
|
-
return parseTranscript(
|
|
48460
|
+
return parseTranscript(readFileSync24(absPath, "utf8"), `claude-file:${pathKey}`);
|
|
47866
48461
|
} catch {
|
|
47867
48462
|
return [];
|
|
47868
48463
|
}
|
|
@@ -47938,7 +48533,7 @@ var init_transcript = __esm({
|
|
|
47938
48533
|
});
|
|
47939
48534
|
|
|
47940
48535
|
// packages/server/src/open-external.ts
|
|
47941
|
-
import { spawn as
|
|
48536
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
47942
48537
|
function validateExternalUrl(raw2) {
|
|
47943
48538
|
let parsed;
|
|
47944
48539
|
try {
|
|
@@ -47951,102 +48546,30 @@ function validateExternalUrl(raw2) {
|
|
|
47951
48546
|
}
|
|
47952
48547
|
return { ok: true, url: parsed.toString() };
|
|
47953
48548
|
}
|
|
47954
|
-
function
|
|
48549
|
+
function externalUrlOpenCommand(url, platform3 = process.platform) {
|
|
48550
|
+
if (platform3 === "darwin") return { command: "open", args: [url] };
|
|
48551
|
+
if (platform3 === "win32") return { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url] };
|
|
48552
|
+
return { command: "xdg-open", args: [url] };
|
|
48553
|
+
}
|
|
48554
|
+
async function openExternalUrl(raw2, options = {}) {
|
|
47955
48555
|
const v = validateExternalUrl(raw2);
|
|
47956
48556
|
if (!v.ok) throw new Error(v.reason);
|
|
47957
|
-
const
|
|
47958
|
-
|
|
47959
|
-
}
|
|
47960
|
-
var init_open_external = __esm({
|
|
47961
|
-
"packages/server/src/open-external.ts"() {
|
|
47962
|
-
"use strict";
|
|
47963
|
-
}
|
|
47964
|
-
});
|
|
47965
|
-
|
|
47966
|
-
// packages/server/src/local-file.ts
|
|
47967
|
-
import { spawn as spawn6 } from "node:child_process";
|
|
47968
|
-
import { readFileSync as readFileSync23, realpathSync as realpathSync9, statSync as statSync15 } from "node:fs";
|
|
47969
|
-
import { homedir as homedir16 } from "node:os";
|
|
47970
|
-
import { isAbsolute as isAbsolute4, join as join35, resolve as resolve8, sep as sep2 } from "node:path";
|
|
47971
|
-
function isUnder(real, root) {
|
|
47972
|
-
let rootReal;
|
|
48557
|
+
const spec = externalUrlOpenCommand(v.url, options.platform);
|
|
48558
|
+
const child = (options.spawn ?? spawn6)(spec.command, [...spec.args], { detached: true, stdio: "ignore", windowsHide: true });
|
|
47973
48559
|
try {
|
|
47974
|
-
|
|
47975
|
-
|
|
47976
|
-
return false;
|
|
47977
|
-
}
|
|
47978
|
-
return real === rootReal || real.startsWith(rootReal.endsWith(sep2) ? rootReal : rootReal + sep2);
|
|
47979
|
-
}
|
|
47980
|
-
function resolveLocalFile(rawPath, roots) {
|
|
47981
|
-
if (!isAbsolute4(rawPath)) throw new Error("Local path must be absolute");
|
|
47982
|
-
let real;
|
|
47983
|
-
try {
|
|
47984
|
-
real = realpathSync9(rawPath);
|
|
47985
|
-
} catch {
|
|
47986
|
-
throw new Error("Local file was not found");
|
|
47987
|
-
}
|
|
47988
|
-
if (!roots.some((root) => isUnder(real, root))) throw new Error("Local file is outside Frizz's trusted roots");
|
|
47989
|
-
try {
|
|
47990
|
-
if (!statSync15(real).isFile()) throw new Error("Local path is not a regular file");
|
|
48560
|
+
await awaitOpenerStart(child, spec.command);
|
|
48561
|
+
return { opened: true };
|
|
47991
48562
|
} catch (error) {
|
|
47992
|
-
|
|
47993
|
-
|
|
47994
|
-
|
|
47995
|
-
return real;
|
|
47996
|
-
}
|
|
47997
|
-
function resolveOpenableFile(raw2, projectDir, roots, home = homedir16()) {
|
|
47998
|
-
const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
|
|
47999
|
-
if (!trimmed) return null;
|
|
48000
|
-
const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join35(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve8(projectDir, trimmed);
|
|
48001
|
-
try {
|
|
48002
|
-
return resolveLocalFile(abs, roots);
|
|
48003
|
-
} catch {
|
|
48004
|
-
return null;
|
|
48005
|
-
}
|
|
48006
|
-
}
|
|
48007
|
-
function readLocalTextFile(rawPath, roots) {
|
|
48008
|
-
const path3 = resolveLocalFile(rawPath, roots);
|
|
48009
|
-
const bytes = readFileSync23(path3);
|
|
48010
|
-
if (bytes.subarray(0, 8192).includes(0)) throw new Error("Local file is not a text file");
|
|
48011
|
-
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, text: bytes.toString("utf8"), truncated: false };
|
|
48012
|
-
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
48013
|
-
const lastBreak = head.lastIndexOf(10);
|
|
48014
|
-
return { path: path3, text: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
|
|
48015
|
-
}
|
|
48016
|
-
function readLocalMarkdown(rawPath, roots) {
|
|
48017
|
-
if (!MARKDOWN_FILE_EXT.test(rawPath.trim())) throw new Error("Local file is not a Markdown file");
|
|
48018
|
-
const path3 = resolveLocalFile(rawPath, roots);
|
|
48019
|
-
if (!MARKDOWN_FILE_EXT.test(path3)) throw new Error("Local file is not a Markdown file");
|
|
48020
|
-
const bytes = readFileSync23(path3);
|
|
48021
|
-
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, markdown: bytes.toString("utf8"), truncated: false };
|
|
48022
|
-
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
48023
|
-
const lastBreak = head.lastIndexOf(10);
|
|
48024
|
-
return { path: path3, markdown: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
|
|
48025
|
-
}
|
|
48026
|
-
function resolveWatchableLocalFile(rawPath, roots) {
|
|
48027
|
-
const path3 = resolveLocalFile(rawPath, roots);
|
|
48028
|
-
if (MARKDOWN_FILE_EXT.test(rawPath.trim()) && !MARKDOWN_FILE_EXT.test(path3)) {
|
|
48029
|
-
throw new Error("Local file is not a Markdown file");
|
|
48563
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48564
|
+
(options.onError ?? ((line) => log.warn("open-external", line)))(`could not open ${v.url}: ${message}`);
|
|
48565
|
+
return { opened: false, error: message };
|
|
48030
48566
|
}
|
|
48031
|
-
return path3;
|
|
48032
|
-
}
|
|
48033
|
-
function defaultSpawn(command, args, options) {
|
|
48034
|
-
return spawn6(command, [...args], options);
|
|
48035
|
-
}
|
|
48036
|
-
function openLocalFile(rawPath, opener, roots, options = {}) {
|
|
48037
|
-
const path3 = resolveLocalFile(rawPath, roots);
|
|
48038
|
-
const selected = options.forceSystem ? "system" : opener;
|
|
48039
|
-
if (selected === "copy") return { action: "copy", path: path3 };
|
|
48040
|
-
const spec = process.platform === "darwin" ? selected === "cursor" ? ["open", ["-a", "Cursor", path3]] : selected === "vscode" ? ["open", ["-a", "Visual Studio Code", path3]] : selected === "finder" ? ["open", ["-R", path3]] : ["open", [path3]] : selected === "finder" ? ["xdg-open", [path3]] : selected === "cursor" ? ["cursor", [path3]] : selected === "vscode" ? ["code", [path3]] : ["xdg-open", [path3]];
|
|
48041
|
-
(options.spawn ?? defaultSpawn)(spec[0], spec[1], { detached: true, stdio: "ignore", shell: false }).unref();
|
|
48042
|
-
return { action: "opened", path: path3 };
|
|
48043
48567
|
}
|
|
48044
|
-
var
|
|
48045
|
-
|
|
48046
|
-
"packages/server/src/local-file.ts"() {
|
|
48568
|
+
var init_open_external = __esm({
|
|
48569
|
+
"packages/server/src/open-external.ts"() {
|
|
48047
48570
|
"use strict";
|
|
48048
|
-
|
|
48049
|
-
|
|
48571
|
+
init_local_file();
|
|
48572
|
+
init_logging();
|
|
48050
48573
|
}
|
|
48051
48574
|
});
|
|
48052
48575
|
|
|
@@ -48375,7 +48898,14 @@ var init_account_actions = __esm({
|
|
|
48375
48898
|
function shellQuote(value) {
|
|
48376
48899
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
48377
48900
|
}
|
|
48378
|
-
function
|
|
48901
|
+
function powershellQuote(value) {
|
|
48902
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
48903
|
+
}
|
|
48904
|
+
function providerResumeCommand(backend, projectDir, sessionId, platform3 = process.platform) {
|
|
48905
|
+
if (platform3 === "win32") {
|
|
48906
|
+
const resume2 = backend === "codex" ? `& codex resume ${powershellQuote(sessionId)} --dangerously-bypass-approvals-and-sandbox` : `& claude --resume ${powershellQuote(sessionId)} --dangerously-skip-permissions`;
|
|
48907
|
+
return `Set-Location -LiteralPath ${powershellQuote(projectDir)}; ${resume2}`;
|
|
48908
|
+
}
|
|
48379
48909
|
const resume = backend === "codex" ? `codex resume ${shellQuote(sessionId)} --dangerously-bypass-approvals-and-sandbox` : `claude --resume ${shellQuote(sessionId)} --dangerously-skip-permissions`;
|
|
48380
48910
|
return `cd ${shellQuote(projectDir)} && ${resume}`;
|
|
48381
48911
|
}
|
|
@@ -48723,11 +49253,11 @@ var init_directory_picker = __esm({
|
|
|
48723
49253
|
});
|
|
48724
49254
|
|
|
48725
49255
|
// packages/server/src/router.ts
|
|
48726
|
-
import { readFileSync as
|
|
49256
|
+
import { readFileSync as readFileSync25, statSync as statSync17 } from "node:fs";
|
|
48727
49257
|
import { join as join36, resolve as resolve9 } from "node:path";
|
|
48728
49258
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
48729
49259
|
import { basename as basename9, dirname as dirname15 } from "node:path";
|
|
48730
|
-
import { existsSync as
|
|
49260
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
|
|
48731
49261
|
import { homedir as homedir17 } from "node:os";
|
|
48732
49262
|
function validateGithubDispatchProfile(input) {
|
|
48733
49263
|
validateThreadProfile(input.backend, input.model, input.effort);
|
|
@@ -48937,7 +49467,7 @@ function storeProjectIcon(id, name, bytes) {
|
|
|
48937
49467
|
function setProjectIconFromFile(id, file) {
|
|
48938
49468
|
let bytes;
|
|
48939
49469
|
try {
|
|
48940
|
-
bytes =
|
|
49470
|
+
bytes = readFileSync25(file);
|
|
48941
49471
|
} catch {
|
|
48942
49472
|
throw new Error("That file could not be read.");
|
|
48943
49473
|
}
|
|
@@ -50164,7 +50694,7 @@ ${gapNote}` : input.message;
|
|
|
50164
50694
|
since: new Date(w.created_at).toISOString()
|
|
50165
50695
|
});
|
|
50166
50696
|
}
|
|
50167
|
-
return { activity, questions: openQuestionViews(input.slug) };
|
|
50697
|
+
return { activity, questions: openQuestionViews(input.slug), links: ctx.storage.listThreadLinks(input.slug).map(threadLinkView) };
|
|
50168
50698
|
}
|
|
50169
50699
|
}),
|
|
50170
50700
|
// THE WATCHER REGISTRY WAS DELETED ON 2026-08-14 AND CAME BACK ON 2026-08-26, under two narrow verbs
|
|
@@ -50178,6 +50708,34 @@ ${gapNote}` : input.message;
|
|
|
50178
50708
|
// binary naming them and gets a 404, which is the honest answer: its arguments do not fit this
|
|
50179
50709
|
// registry (there is no `for:` in them at all), so an alias would have to invent the one field that
|
|
50180
50710
|
// must not be guessed at.
|
|
50711
|
+
upsertOwnLink: mutation({
|
|
50712
|
+
input: UpsertOwnLinkInput,
|
|
50713
|
+
output: UpsertOwnLinkResult,
|
|
50714
|
+
handler: async ({ input }) => {
|
|
50715
|
+
const row = ctx.storage.getSession(input.slug);
|
|
50716
|
+
if (!row) throw new Error(`thread ${input.slug} is not registered`);
|
|
50717
|
+
if (row.state === "archived" || row.archived === 1) throw new Error("Reopen this thread before registering a link");
|
|
50718
|
+
const destination = resolveThreadLink(input.target, ctx.project.dir, openRoots);
|
|
50719
|
+
const link = ctx.storage.upsertThreadLink({
|
|
50720
|
+
id: `lnk_${randomUUID16().replace(/-/g, "").slice(0, 12)}`,
|
|
50721
|
+
slug: input.slug,
|
|
50722
|
+
label: input.label,
|
|
50723
|
+
...destination,
|
|
50724
|
+
createdAtMs: Date.now()
|
|
50725
|
+
});
|
|
50726
|
+
ctx.board.refresh();
|
|
50727
|
+
return { link: threadLinkView(link) };
|
|
50728
|
+
}
|
|
50729
|
+
}),
|
|
50730
|
+
dropOwnLink: mutation({
|
|
50731
|
+
input: DropOwnLinkInput,
|
|
50732
|
+
output: DropOwnLinkResult,
|
|
50733
|
+
handler: async ({ input }) => {
|
|
50734
|
+
const dropped = ctx.storage.dropThreadLink(input.slug, input.id);
|
|
50735
|
+
if (dropped) ctx.board.refresh();
|
|
50736
|
+
return { dropped };
|
|
50737
|
+
}
|
|
50738
|
+
}),
|
|
50181
50739
|
// ---- REGISTERED PR WATCHERS (add / drop / list) ---------------------------------------------
|
|
50182
50740
|
// The worker's own PR watchers, from `mcp__frizz__watch_pr`. Same caller and therefore the same rules
|
|
50183
50741
|
// as the timers above: slug-only (the MCP server outlives the session ids underneath it), and no
|
|
@@ -50529,7 +51087,8 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
50529
51087
|
openExternal: mutation({
|
|
50530
51088
|
input: external_exports.object({ url: external_exports.string() }),
|
|
50531
51089
|
handler: async ({ input }) => {
|
|
50532
|
-
openExternalUrl(input.url);
|
|
51090
|
+
const result = await openExternalUrl(input.url);
|
|
51091
|
+
if (!result.opened) throw new Error(result.error);
|
|
50533
51092
|
}
|
|
50534
51093
|
}),
|
|
50535
51094
|
// A local file can be opened only after its canonical real path is contained by the openable roots
|
|
@@ -50937,7 +51496,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
50937
51496
|
}
|
|
50938
51497
|
const updated = clearProjectIcon(input.id);
|
|
50939
51498
|
if (!updated) throw new Error("No such project.");
|
|
50940
|
-
return projectCard(updated, !
|
|
51499
|
+
return projectCard(updated, !existsSync14(updated.path));
|
|
50941
51500
|
}
|
|
50942
51501
|
}),
|
|
50943
51502
|
/**
|
|
@@ -51121,6 +51680,7 @@ var init_router5 = __esm({
|
|
|
51121
51680
|
init_open_external();
|
|
51122
51681
|
init_local_file();
|
|
51123
51682
|
init_project();
|
|
51683
|
+
init_thread_links();
|
|
51124
51684
|
init_github();
|
|
51125
51685
|
init_github_hovercard();
|
|
51126
51686
|
init_dispatch();
|
|
@@ -51342,21 +51902,21 @@ var init_compression = __esm({
|
|
|
51342
51902
|
});
|
|
51343
51903
|
|
|
51344
51904
|
// packages/server/src/local-image.ts
|
|
51345
|
-
import { readFileSync as
|
|
51346
|
-
import { extname as extname2, isAbsolute as
|
|
51905
|
+
import { readFileSync as readFileSync26, realpathSync as realpathSync11, statSync as statSync18 } from "node:fs";
|
|
51906
|
+
import { extname as extname2, isAbsolute as isAbsolute6 } from "node:path";
|
|
51347
51907
|
function resolveLocalImage(rawPath) {
|
|
51348
|
-
if (!rawPath || !
|
|
51908
|
+
if (!rawPath || !isAbsolute6(rawPath)) return { status: 400 };
|
|
51349
51909
|
const contentType = IMAGE_CONTENT_TYPE[extname2(rawPath).toLowerCase()];
|
|
51350
51910
|
if (!contentType) return { status: 400 };
|
|
51351
51911
|
let real;
|
|
51352
51912
|
try {
|
|
51353
|
-
real =
|
|
51913
|
+
real = realpathSync11(rawPath);
|
|
51354
51914
|
} catch {
|
|
51355
51915
|
return { status: 404 };
|
|
51356
51916
|
}
|
|
51357
51917
|
try {
|
|
51358
51918
|
if (!statSync18(real).isFile()) return { status: 404 };
|
|
51359
|
-
return { status: 200, contentType, body:
|
|
51919
|
+
return { status: 200, contentType, body: readFileSync26(real) };
|
|
51360
51920
|
} catch {
|
|
51361
51921
|
return { status: 404 };
|
|
51362
51922
|
}
|
|
@@ -51376,7 +51936,7 @@ var init_local_image = __esm({
|
|
|
51376
51936
|
});
|
|
51377
51937
|
|
|
51378
51938
|
// packages/server/src/project-icon.ts
|
|
51379
|
-
import { readFileSync as
|
|
51939
|
+
import { readFileSync as readFileSync27, readdirSync as readdirSync10, realpathSync as realpathSync12, statSync as statSync19 } from "node:fs";
|
|
51380
51940
|
import { homedir as homedir18 } from "node:os";
|
|
51381
51941
|
import { basename as basename10, dirname as dirname16, extname as extname3, join as join37, relative as relative2, sep as sep3 } from "node:path";
|
|
51382
51942
|
function normalizeStem(stem) {
|
|
@@ -51438,7 +51998,7 @@ function iconDirectories(root) {
|
|
|
51438
51998
|
function manifestIcons(manifestPath) {
|
|
51439
51999
|
let parsed;
|
|
51440
52000
|
try {
|
|
51441
|
-
parsed = JSON.parse(
|
|
52001
|
+
parsed = JSON.parse(readFileSync27(manifestPath, "utf8"));
|
|
51442
52002
|
} catch {
|
|
51443
52003
|
return [];
|
|
51444
52004
|
}
|
|
@@ -51525,21 +52085,21 @@ function resolveProjectIconResponse(id, options = {}) {
|
|
|
51525
52085
|
if (!contentType) return { status: 404 };
|
|
51526
52086
|
let real;
|
|
51527
52087
|
try {
|
|
51528
|
-
real =
|
|
52088
|
+
real = realpathSync12(path3);
|
|
51529
52089
|
if (!statSync19(real).isFile()) return { status: 404 };
|
|
51530
52090
|
} catch {
|
|
51531
52091
|
return { status: 404 };
|
|
51532
52092
|
}
|
|
51533
52093
|
const roots = [entry.path, projectStateDir(id, home)].map((root) => {
|
|
51534
52094
|
try {
|
|
51535
|
-
return
|
|
52095
|
+
return realpathSync12(root);
|
|
51536
52096
|
} catch {
|
|
51537
52097
|
return root;
|
|
51538
52098
|
}
|
|
51539
52099
|
});
|
|
51540
52100
|
if (!roots.some((root) => real === root || real.startsWith(root + sep3))) return { status: 404 };
|
|
51541
52101
|
try {
|
|
51542
|
-
return { status: 200, contentType, body:
|
|
52102
|
+
return { status: 200, contentType, body: readFileSync27(real) };
|
|
51543
52103
|
} catch {
|
|
51544
52104
|
return { status: 404 };
|
|
51545
52105
|
}
|
|
@@ -51651,7 +52211,7 @@ var init_project_icon = __esm({
|
|
|
51651
52211
|
});
|
|
51652
52212
|
|
|
51653
52213
|
// packages/server/src/local-visualization.ts
|
|
51654
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
52214
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync28, realpathSync as realpathSync13, statSync as statSync20 } from "node:fs";
|
|
51655
52215
|
import { join as join38, sep as sep4 } from "node:path";
|
|
51656
52216
|
function children(path3, pattern) {
|
|
51657
52217
|
try {
|
|
@@ -51668,8 +52228,8 @@ function resolveFragment(projectDir, sessionId, file) {
|
|
|
51668
52228
|
let projectReal;
|
|
51669
52229
|
let baseReal;
|
|
51670
52230
|
try {
|
|
51671
|
-
projectReal =
|
|
51672
|
-
baseReal =
|
|
52231
|
+
projectReal = realpathSync13(projectDir);
|
|
52232
|
+
baseReal = realpathSync13(base);
|
|
51673
52233
|
} catch {
|
|
51674
52234
|
return null;
|
|
51675
52235
|
}
|
|
@@ -51682,7 +52242,7 @@ function resolveFragment(projectDir, sessionId, file) {
|
|
|
51682
52242
|
const sessionRoot = join38(baseReal, year, month, day, sessionId);
|
|
51683
52243
|
const candidate = join38(monthDir, day, sessionId, file);
|
|
51684
52244
|
try {
|
|
51685
|
-
const real =
|
|
52245
|
+
const real = realpathSync13(candidate);
|
|
51686
52246
|
if (isUnder2(real, sessionRoot) && statSync20(real).isFile()) return real;
|
|
51687
52247
|
} catch {
|
|
51688
52248
|
}
|
|
@@ -51766,7 +52326,7 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
|
|
|
51766
52326
|
try {
|
|
51767
52327
|
const size = statSync20(path3).size;
|
|
51768
52328
|
if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
|
|
51769
|
-
fragment =
|
|
52329
|
+
fragment = readFileSync28(path3, "utf8");
|
|
51770
52330
|
} catch {
|
|
51771
52331
|
return { status: 404 };
|
|
51772
52332
|
}
|
|
@@ -53288,7 +53848,7 @@ var init_local_file_watch = __esm({
|
|
|
53288
53848
|
});
|
|
53289
53849
|
|
|
53290
53850
|
// packages/server/src/boot-progress.ts
|
|
53291
|
-
import { mkdirSync as mkdirSync18, readFileSync as
|
|
53851
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync29, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
|
|
53292
53852
|
import { basename as basename12, dirname as dirname18, join as join40 } from "node:path";
|
|
53293
53853
|
function bootProgressPath(stateDir) {
|
|
53294
53854
|
return join40(stateDir, BOOT_PROGRESS_NAME);
|
|
@@ -53336,30 +53896,111 @@ var init_boot_progress = __esm({
|
|
|
53336
53896
|
}
|
|
53337
53897
|
});
|
|
53338
53898
|
|
|
53899
|
+
// packages/server/src/runtime-lease.ts
|
|
53900
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync19, readdirSync as readdirSync12, readFileSync as readFileSync30, unlinkSync as unlinkSync6, writeFileSync as writeFileSync16 } from "node:fs";
|
|
53901
|
+
import { dirname as dirname19, join as join41 } from "node:path";
|
|
53902
|
+
function runtimeVersionDir(bin) {
|
|
53903
|
+
let dir = dirname19(bin);
|
|
53904
|
+
for (let step = 0; step < MAX_ASCENT; step++) {
|
|
53905
|
+
if (existsSync15(join41(dir, PROVISIONED_MARKER))) return dir;
|
|
53906
|
+
const parent = dirname19(dir);
|
|
53907
|
+
if (parent === dir) return void 0;
|
|
53908
|
+
dir = parent;
|
|
53909
|
+
}
|
|
53910
|
+
return void 0;
|
|
53911
|
+
}
|
|
53912
|
+
function pidAlive4(pid) {
|
|
53913
|
+
try {
|
|
53914
|
+
process.kill(pid, 0);
|
|
53915
|
+
return true;
|
|
53916
|
+
} catch (error) {
|
|
53917
|
+
return error.code === "EPERM";
|
|
53918
|
+
}
|
|
53919
|
+
}
|
|
53920
|
+
function leaseRuntime(bin, role) {
|
|
53921
|
+
const versionDir = runtimeVersionDir(bin);
|
|
53922
|
+
if (!versionDir) return () => {
|
|
53923
|
+
};
|
|
53924
|
+
const path3 = join41(versionDir, LEASES_DIR, `${process.pid}.json`);
|
|
53925
|
+
try {
|
|
53926
|
+
mkdirSync19(dirname19(path3), { recursive: true });
|
|
53927
|
+
const lease = { pid: process.pid, role, bin, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
53928
|
+
writeFileSync16(path3, JSON.stringify(lease));
|
|
53929
|
+
} catch {
|
|
53930
|
+
return () => {
|
|
53931
|
+
};
|
|
53932
|
+
}
|
|
53933
|
+
return () => {
|
|
53934
|
+
try {
|
|
53935
|
+
unlinkSync6(path3);
|
|
53936
|
+
} catch {
|
|
53937
|
+
}
|
|
53938
|
+
};
|
|
53939
|
+
}
|
|
53940
|
+
function liveRuntimeLeases(versionDir) {
|
|
53941
|
+
const dir = join41(versionDir, LEASES_DIR);
|
|
53942
|
+
let names;
|
|
53943
|
+
try {
|
|
53944
|
+
names = readdirSync12(dir);
|
|
53945
|
+
} catch {
|
|
53946
|
+
return [];
|
|
53947
|
+
}
|
|
53948
|
+
const live = [];
|
|
53949
|
+
for (const name of names) {
|
|
53950
|
+
if (!name.endsWith(".json")) continue;
|
|
53951
|
+
const path3 = join41(dir, name);
|
|
53952
|
+
let lease;
|
|
53953
|
+
try {
|
|
53954
|
+
lease = JSON.parse(readFileSync30(path3, "utf8"));
|
|
53955
|
+
} catch {
|
|
53956
|
+
continue;
|
|
53957
|
+
}
|
|
53958
|
+
if (typeof lease.pid !== "number" || !pidAlive4(lease.pid)) {
|
|
53959
|
+
try {
|
|
53960
|
+
unlinkSync6(path3);
|
|
53961
|
+
} catch {
|
|
53962
|
+
}
|
|
53963
|
+
;
|
|
53964
|
+
continue;
|
|
53965
|
+
}
|
|
53966
|
+
live.push({ pid: lease.pid, role: typeof lease.role === "string" ? lease.role : "unknown", bin: typeof lease.bin === "string" ? lease.bin : "", at: typeof lease.at === "string" ? lease.at : "" });
|
|
53967
|
+
}
|
|
53968
|
+
return live;
|
|
53969
|
+
}
|
|
53970
|
+
var PROVISIONED_MARKER, LEASES_DIR, MAX_ASCENT;
|
|
53971
|
+
var init_runtime_lease = __esm({
|
|
53972
|
+
"packages/server/src/runtime-lease.ts"() {
|
|
53973
|
+
"use strict";
|
|
53974
|
+
PROVISIONED_MARKER = "provisioned.json";
|
|
53975
|
+
LEASES_DIR = ".leases";
|
|
53976
|
+
MAX_ASCENT = 6;
|
|
53977
|
+
}
|
|
53978
|
+
});
|
|
53979
|
+
|
|
53339
53980
|
// packages/server/src/runtimes.ts
|
|
53340
53981
|
import { createHash as createHash15 } from "node:crypto";
|
|
53341
53982
|
import {
|
|
53342
53983
|
chmodSync,
|
|
53343
53984
|
closeSync as closeSync16,
|
|
53344
|
-
existsSync as
|
|
53345
|
-
mkdirSync as
|
|
53985
|
+
existsSync as existsSync16,
|
|
53986
|
+
mkdirSync as mkdirSync20,
|
|
53346
53987
|
openSync as openSync17,
|
|
53347
|
-
readFileSync as
|
|
53348
|
-
readdirSync as
|
|
53988
|
+
readFileSync as readFileSync31,
|
|
53989
|
+
readdirSync as readdirSync13,
|
|
53349
53990
|
renameSync as renameSync11,
|
|
53350
53991
|
rmSync as rmSync12,
|
|
53351
53992
|
statSync as statSync22,
|
|
53352
|
-
writeFileSync as
|
|
53993
|
+
writeFileSync as writeFileSync17,
|
|
53353
53994
|
writeSync as writeSync2
|
|
53354
53995
|
} from "node:fs";
|
|
53355
|
-
import { dirname as
|
|
53996
|
+
import { dirname as dirname20, isAbsolute as isAbsolute7, join as join42, normalize, relative as relative3, sep as sep5 } from "node:path";
|
|
53356
53997
|
import { Readable, Transform, Writable as Writable3 } from "node:stream";
|
|
53357
53998
|
import { pipeline } from "node:stream/promises";
|
|
53358
53999
|
import { createGunzip } from "node:zlib";
|
|
53359
54000
|
function runtimesRoot(env = process.env) {
|
|
53360
54001
|
const override = env[RUNTIMES_DIR_ENV];
|
|
53361
|
-
if (override &&
|
|
53362
|
-
return
|
|
54002
|
+
if (override && isAbsolute7(override)) return override;
|
|
54003
|
+
return join42(frizzRoots().cache, "runtimes");
|
|
53363
54004
|
}
|
|
53364
54005
|
function isMusl() {
|
|
53365
54006
|
if (process.platform !== "linux") return false;
|
|
@@ -53376,16 +54017,16 @@ function runtimeCoordinates(backend, platform3 = process.platform, arch = proces
|
|
|
53376
54017
|
return { pkg: "@openai/codex", packageVersion: `${CODEX_VERSION}-${platform3}-${arch}`, label: CODEX_VERSION, binary: `codex${exe}` };
|
|
53377
54018
|
}
|
|
53378
54019
|
function provisionedBinary(backend, root, label) {
|
|
53379
|
-
const dir =
|
|
54020
|
+
const dir = join42(root, backend, label);
|
|
53380
54021
|
let marker;
|
|
53381
54022
|
try {
|
|
53382
|
-
marker = JSON.parse(
|
|
54023
|
+
marker = JSON.parse(readFileSync31(join42(dir, MARKER), "utf8"));
|
|
53383
54024
|
} catch {
|
|
53384
54025
|
return void 0;
|
|
53385
54026
|
}
|
|
53386
54027
|
if (typeof marker.binary !== "string" || marker.label !== label) return void 0;
|
|
53387
|
-
const bin =
|
|
53388
|
-
return
|
|
54028
|
+
const bin = join42(dir, marker.binary);
|
|
54029
|
+
return existsSync16(bin) ? bin : void 0;
|
|
53389
54030
|
}
|
|
53390
54031
|
function octal(buffer, offset, length) {
|
|
53391
54032
|
const text2 = buffer.toString("latin1", offset, offset + length).replace(/\0.*$/su, "").trim();
|
|
@@ -53398,27 +54039,27 @@ function safeEntryPath(dest, name) {
|
|
|
53398
54039
|
const parts = name.split("/").filter((part) => part.length > 0);
|
|
53399
54040
|
if (parts.length < 2) return void 0;
|
|
53400
54041
|
const rel = normalize(parts.slice(1).join(sep5));
|
|
53401
|
-
if (
|
|
53402
|
-
const full =
|
|
54042
|
+
if (isAbsolute7(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return void 0;
|
|
54043
|
+
const full = join42(dest, rel);
|
|
53403
54044
|
const back = relative3(dest, full);
|
|
53404
|
-
if (back.startsWith("..") ||
|
|
54045
|
+
if (back.startsWith("..") || isAbsolute7(back)) return void 0;
|
|
53405
54046
|
return full;
|
|
53406
54047
|
}
|
|
53407
54048
|
function locateBinary(dir, backend, binary) {
|
|
53408
54049
|
if (backend === "claude") {
|
|
53409
|
-
const direct =
|
|
53410
|
-
return
|
|
54050
|
+
const direct = join42(dir, binary);
|
|
54051
|
+
return existsSync16(direct) ? direct : void 0;
|
|
53411
54052
|
}
|
|
53412
|
-
const vendor =
|
|
54053
|
+
const vendor = join42(dir, "vendor");
|
|
53413
54054
|
let triples;
|
|
53414
54055
|
try {
|
|
53415
|
-
triples =
|
|
54056
|
+
triples = readdirSync13(vendor);
|
|
53416
54057
|
} catch {
|
|
53417
54058
|
return void 0;
|
|
53418
54059
|
}
|
|
53419
54060
|
for (const triple of triples) {
|
|
53420
|
-
const candidate =
|
|
53421
|
-
if (
|
|
54061
|
+
const candidate = join42(vendor, triple, "bin", binary);
|
|
54062
|
+
if (existsSync16(candidate)) return candidate;
|
|
53422
54063
|
}
|
|
53423
54064
|
return void 0;
|
|
53424
54065
|
}
|
|
@@ -53433,12 +54074,12 @@ async function provisionRuntime(backend, options) {
|
|
|
53433
54074
|
const registry = options.registry ?? REGISTRY;
|
|
53434
54075
|
const progress = options.onProgress ?? (() => {
|
|
53435
54076
|
});
|
|
53436
|
-
const backendDir =
|
|
53437
|
-
const finalDir =
|
|
53438
|
-
const partial =
|
|
53439
|
-
|
|
54077
|
+
const backendDir = join42(root, backend);
|
|
54078
|
+
const finalDir = join42(backendDir, coordinates.label);
|
|
54079
|
+
const partial = join42(backendDir, `.partial-${coordinates.label}-${process.pid}`);
|
|
54080
|
+
mkdirSync20(backendDir, { recursive: true });
|
|
53440
54081
|
rmSync12(partial, { recursive: true, force: true });
|
|
53441
|
-
|
|
54082
|
+
mkdirSync20(partial);
|
|
53442
54083
|
try {
|
|
53443
54084
|
const manifestUrl = `${registry}/${coordinates.pkg.replace("/", "%2F")}/${coordinates.packageVersion}`;
|
|
53444
54085
|
progress(`resolving ${coordinates.pkg}@${coordinates.packageVersion}`);
|
|
@@ -53482,10 +54123,19 @@ async function provisionRuntime(backend, options) {
|
|
|
53482
54123
|
binary: relative3(partial, bin),
|
|
53483
54124
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
53484
54125
|
};
|
|
53485
|
-
|
|
54126
|
+
writeFileSync17(join42(partial, MARKER), `${JSON.stringify(marker, null, 2)}
|
|
53486
54127
|
`, "utf8");
|
|
54128
|
+
const rename2 = options.rename ?? renameSync11;
|
|
53487
54129
|
try {
|
|
53488
|
-
|
|
54130
|
+
for (let attempt = 1; ; attempt++) {
|
|
54131
|
+
try {
|
|
54132
|
+
rename2(partial, finalDir);
|
|
54133
|
+
break;
|
|
54134
|
+
} catch (err) {
|
|
54135
|
+
if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") || existsSync16(finalDir)) throw err;
|
|
54136
|
+
await new Promise((resolve12) => setTimeout(resolve12, options.retryDelayMs ?? RENAME_RETRY_DELAY_MS));
|
|
54137
|
+
}
|
|
54138
|
+
}
|
|
53489
54139
|
} catch (err) {
|
|
53490
54140
|
const theirs = provisionedBinary(backend, root, coordinates.label);
|
|
53491
54141
|
if (!theirs) throw err;
|
|
@@ -53493,35 +54143,48 @@ async function provisionRuntime(backend, options) {
|
|
|
53493
54143
|
return { bin: theirs, label: coordinates.label, fetched: false };
|
|
53494
54144
|
}
|
|
53495
54145
|
progress("ready");
|
|
53496
|
-
return { bin:
|
|
54146
|
+
return { bin: join42(finalDir, marker.binary), label: coordinates.label, fetched: true };
|
|
53497
54147
|
} catch (err) {
|
|
53498
54148
|
rmSync12(partial, { recursive: true, force: true });
|
|
53499
54149
|
throw err;
|
|
53500
54150
|
}
|
|
53501
54151
|
}
|
|
53502
|
-
function sweepRuntimes(backend, root, keepLabel, now = Date.now()) {
|
|
53503
|
-
const
|
|
54152
|
+
function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
|
|
54153
|
+
const rm2 = deps.rm ?? defaultSweepRm;
|
|
54154
|
+
const backendDir = join42(root, backend);
|
|
53504
54155
|
let entries2;
|
|
53505
54156
|
try {
|
|
53506
|
-
entries2 =
|
|
54157
|
+
entries2 = readdirSync13(backendDir);
|
|
53507
54158
|
} catch {
|
|
53508
|
-
return [];
|
|
54159
|
+
return { removed: [], kept: [] };
|
|
53509
54160
|
}
|
|
53510
|
-
const
|
|
54161
|
+
const sweep = { removed: [], kept: [] };
|
|
53511
54162
|
for (const entry of entries2) {
|
|
53512
54163
|
if (entry === keepLabel) continue;
|
|
53513
|
-
const full =
|
|
54164
|
+
const full = join42(backendDir, entry);
|
|
53514
54165
|
if (entry.startsWith(".partial-")) {
|
|
53515
54166
|
try {
|
|
53516
54167
|
if (now - statSync22(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
|
|
53517
54168
|
} catch {
|
|
53518
54169
|
continue;
|
|
53519
54170
|
}
|
|
54171
|
+
} else {
|
|
54172
|
+
const leases = liveRuntimeLeases(full);
|
|
54173
|
+
if (leases.length > 0) {
|
|
54174
|
+
sweep.kept.push({ dir: full, leases: leases.map(({ pid, role }) => ({ pid, role })) });
|
|
54175
|
+
continue;
|
|
54176
|
+
}
|
|
54177
|
+
}
|
|
54178
|
+
try {
|
|
54179
|
+
rm2(full);
|
|
54180
|
+
sweep.removed.push(full);
|
|
54181
|
+
} catch (err) {
|
|
54182
|
+
const code = errorCode4(err);
|
|
54183
|
+
const reason = code && TRANSIENT_HANDLE_CODES.has(code) ? `in use (${code})` : `could not remove: ${err instanceof Error ? err.message : String(err)}`;
|
|
54184
|
+
sweep.kept.push({ dir: full, leases: [], reason });
|
|
53520
54185
|
}
|
|
53521
|
-
rmSync12(full, { recursive: true, force: true });
|
|
53522
|
-
removed.push(full);
|
|
53523
54186
|
}
|
|
53524
|
-
return
|
|
54187
|
+
return sweep;
|
|
53525
54188
|
}
|
|
53526
54189
|
async function resolveOne(backend, explicit, options) {
|
|
53527
54190
|
const env = options.env ?? process.env;
|
|
@@ -53538,22 +54201,34 @@ async function resolveOne(backend, explicit, options) {
|
|
|
53538
54201
|
return { bin: bare, source: "path", version: "unknown", note };
|
|
53539
54202
|
}
|
|
53540
54203
|
const root = options.root ?? runtimesRoot(env);
|
|
54204
|
+
let provisioned;
|
|
53541
54205
|
try {
|
|
53542
|
-
|
|
54206
|
+
provisioned = await provisionRuntime(backend, {
|
|
53543
54207
|
root,
|
|
53544
54208
|
coordinates,
|
|
53545
54209
|
fetch: options.fetch,
|
|
53546
54210
|
registry: options.registry,
|
|
54211
|
+
rename: options.rename,
|
|
53547
54212
|
onProgress: (message) => options.onProgress?.(backend, message)
|
|
53548
54213
|
});
|
|
53549
|
-
if (provisioned.fetched) log3("info", `runtimes: provisioned ${backend} ${provisioned.label} at ${provisioned.bin}`);
|
|
53550
|
-
for (const retired of sweepRuntimes(backend, root, coordinates.label)) log3("info", `runtimes: retired ${retired}`);
|
|
53551
|
-
return { bin: provisioned.bin, source: "provisioned", version: provisioned.label };
|
|
53552
54214
|
} catch (err) {
|
|
53553
54215
|
const note = err instanceof Error ? err.message : String(err);
|
|
53554
54216
|
log3("warn", `runtimes: could not provision ${backend} ${coordinates.label} (${note}); using ${bare} from PATH \u2014 the version Frizz was built against is not what will run`);
|
|
53555
54217
|
return { bin: bare, source: "path", version: "unknown", note };
|
|
53556
54218
|
}
|
|
54219
|
+
if (provisioned.fetched) log3("info", `runtimes: provisioned ${backend} ${provisioned.label} at ${provisioned.bin}`);
|
|
54220
|
+
leaseRuntime(provisioned.bin, "server");
|
|
54221
|
+
try {
|
|
54222
|
+
const sweep = sweepRuntimes(backend, root, coordinates.label, Date.now(), { rm: options.rm });
|
|
54223
|
+
for (const retired of sweep.removed) log3("info", `runtimes: retired ${retired}`);
|
|
54224
|
+
for (const { dir, leases, reason } of sweep.kept) {
|
|
54225
|
+
const why = reason ?? `still used by ${leases.map((lease) => `${lease.role} pid ${lease.pid}`).join(", ")}`;
|
|
54226
|
+
log3("info", `runtimes: kept ${dir} \u2014 ${why}`);
|
|
54227
|
+
}
|
|
54228
|
+
} catch (err) {
|
|
54229
|
+
log3("warn", `runtimes: sweep of superseded ${backend} versions failed (${err instanceof Error ? err.message : String(err)}); the pin is unaffected`);
|
|
54230
|
+
}
|
|
54231
|
+
return { bin: provisioned.bin, source: "provisioned", version: provisioned.label };
|
|
53557
54232
|
}
|
|
53558
54233
|
async function resolveRuntimes(options = {}) {
|
|
53559
54234
|
const [claude, codex] = await Promise.all([
|
|
@@ -53572,12 +54247,13 @@ function describeRuntime(backend, runtime) {
|
|
|
53572
54247
|
return `${backend}: ${runtime.bin} from PATH${runtime.note ? ` \u2014 ${runtime.note}` : ""}`;
|
|
53573
54248
|
}
|
|
53574
54249
|
}
|
|
53575
|
-
var CLAUDE_AGENT_SDK_VERSION, CLAUDE_CODE_VERSION, CODEX_VERSION, RUNTIMES_MODE_ENV, RUNTIMES_DIR_ENV, CLAUDE_BIN_ENV, CODEX_BIN_ENV, REGISTRY, MARKER, BLOCK, TarExtractor;
|
|
54250
|
+
var CLAUDE_AGENT_SDK_VERSION, CLAUDE_CODE_VERSION, CODEX_VERSION, RUNTIMES_MODE_ENV, RUNTIMES_DIR_ENV, CLAUDE_BIN_ENV, CODEX_BIN_ENV, REGISTRY, MARKER, BLOCK, TarExtractor, RENAME_ATTEMPTS, RENAME_RETRY_DELAY_MS, TRANSIENT_HANDLE_CODES, errorCode4, defaultSweepRm;
|
|
53576
54251
|
var init_runtimes = __esm({
|
|
53577
54252
|
"packages/server/src/runtimes.ts"() {
|
|
53578
54253
|
"use strict";
|
|
53579
54254
|
init_codex_app_server();
|
|
53580
54255
|
init_frizz_paths();
|
|
54256
|
+
init_runtime_lease();
|
|
53581
54257
|
CLAUDE_AGENT_SDK_VERSION = "0.3.268";
|
|
53582
54258
|
CLAUDE_CODE_VERSION = "2.1.268";
|
|
53583
54259
|
CODEX_VERSION = CODEX_APP_SERVER_SUPPORTED_VERSION;
|
|
@@ -53668,7 +54344,7 @@ var init_runtimes = __esm({
|
|
|
53668
54344
|
return;
|
|
53669
54345
|
case "5": {
|
|
53670
54346
|
const dir = safeEntryPath(this.dest, name);
|
|
53671
|
-
if (dir)
|
|
54347
|
+
if (dir) mkdirSync20(dir, { recursive: true });
|
|
53672
54348
|
return;
|
|
53673
54349
|
}
|
|
53674
54350
|
case "0":
|
|
@@ -53676,7 +54352,7 @@ var init_runtimes = __esm({
|
|
|
53676
54352
|
case "7": {
|
|
53677
54353
|
const file = safeEntryPath(this.dest, name);
|
|
53678
54354
|
if (!file) return;
|
|
53679
|
-
|
|
54355
|
+
mkdirSync20(dirname20(file), { recursive: true });
|
|
53680
54356
|
this.fd = openSync17(file, "w", octal(header, 100, 8) & 511 || 420);
|
|
53681
54357
|
this.written.push(file);
|
|
53682
54358
|
return;
|
|
@@ -53716,6 +54392,11 @@ var init_runtimes = __esm({
|
|
|
53716
54392
|
}
|
|
53717
54393
|
}
|
|
53718
54394
|
};
|
|
54395
|
+
RENAME_ATTEMPTS = 5;
|
|
54396
|
+
RENAME_RETRY_DELAY_MS = 200;
|
|
54397
|
+
TRANSIENT_HANDLE_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
54398
|
+
errorCode4 = (err) => err?.code;
|
|
54399
|
+
defaultSweepRm = (path3) => rmSync12(path3, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
53719
54400
|
}
|
|
53720
54401
|
});
|
|
53721
54402
|
|
|
@@ -54248,18 +54929,18 @@ var init_legacy_project_db = __esm({
|
|
|
54248
54929
|
});
|
|
54249
54930
|
|
|
54250
54931
|
// packages/server/src/frizz-db.ts
|
|
54251
|
-
import { existsSync as
|
|
54252
|
-
import { basename as basename13, dirname as
|
|
54932
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync21, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync23 } from "node:fs";
|
|
54933
|
+
import { basename as basename13, dirname as dirname21, join as join43 } from "node:path";
|
|
54253
54934
|
function frizzDatabaseLocation(options) {
|
|
54254
54935
|
if (options.stateDir !== void 0) {
|
|
54255
|
-
const projectsDir =
|
|
54936
|
+
const projectsDir = dirname21(options.stateDir);
|
|
54256
54937
|
if (basename13(projectsDir) === "projects") {
|
|
54257
|
-
return { path: options.path ??
|
|
54938
|
+
return { path: options.path ?? join43(dirname21(projectsDir), "ui.db"), projectsDir };
|
|
54258
54939
|
}
|
|
54259
|
-
return { path: options.path ??
|
|
54940
|
+
return { path: options.path ?? join43(options.stateDir, "ui.db"), projectsDir: void 0 };
|
|
54260
54941
|
}
|
|
54261
54942
|
const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
|
|
54262
|
-
return { path: options.path ??
|
|
54943
|
+
return { path: options.path ?? join43(data, "ui.db"), projectsDir: join43(data, "projects") };
|
|
54263
54944
|
}
|
|
54264
54945
|
function ensureFrizzSchema(db) {
|
|
54265
54946
|
db.exec(`
|
|
@@ -54278,7 +54959,7 @@ function ensureFrizzSchema(db) {
|
|
|
54278
54959
|
}
|
|
54279
54960
|
function openFrizzDatabase(options = {}) {
|
|
54280
54961
|
const { path: path3, projectsDir } = frizzDatabaseLocation(options);
|
|
54281
|
-
|
|
54962
|
+
mkdirSync21(dirname21(path3), { recursive: true });
|
|
54282
54963
|
const db = new sqlite_default(path3);
|
|
54283
54964
|
db.pragma("busy_timeout = 5000");
|
|
54284
54965
|
db.pragma("journal_mode = WAL");
|
|
@@ -54290,10 +54971,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
54290
54971
|
);
|
|
54291
54972
|
for (const projectId of retiredLegacyProjectIds(projectsDir)) {
|
|
54292
54973
|
if (alreadyImported.has(projectId)) continue;
|
|
54293
|
-
const dir =
|
|
54974
|
+
const dir = join43(projectsDir, projectId);
|
|
54294
54975
|
try {
|
|
54295
|
-
renameSync12(
|
|
54296
|
-
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId,
|
|
54976
|
+
renameSync12(join43(dir, "ui.db.imported"), join43(dir, "ui.db"));
|
|
54977
|
+
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join43(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
|
|
54297
54978
|
alreadyImported.add(projectId);
|
|
54298
54979
|
log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
|
|
54299
54980
|
} catch (error) {
|
|
@@ -54303,7 +54984,7 @@ function openFrizzDatabase(options = {}) {
|
|
|
54303
54984
|
}
|
|
54304
54985
|
for (const projectId of legacyProjectIds(projectsDir)) {
|
|
54305
54986
|
if (alreadyImported.has(projectId)) continue;
|
|
54306
|
-
const legacyPath =
|
|
54987
|
+
const legacyPath = join43(projectsDir, projectId, "ui.db");
|
|
54307
54988
|
try {
|
|
54308
54989
|
const rows = importLegacyProject(db, projectId, legacyPath);
|
|
54309
54990
|
imported.push({ projectId, rows });
|
|
@@ -54333,10 +55014,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
54333
55014
|
};
|
|
54334
55015
|
}
|
|
54335
55016
|
function projectIdsWithFile(projectsDir, file) {
|
|
54336
|
-
if (!
|
|
54337
|
-
return
|
|
55017
|
+
if (!existsSync17(projectsDir)) return [];
|
|
55018
|
+
return readdirSync14(projectsDir).filter((name) => {
|
|
54338
55019
|
try {
|
|
54339
|
-
return statSync23(
|
|
55020
|
+
return statSync23(join43(projectsDir, name, file)).isFile();
|
|
54340
55021
|
} catch {
|
|
54341
55022
|
return false;
|
|
54342
55023
|
}
|
|
@@ -54346,7 +55027,7 @@ function legacyProjectIds(projectsDir) {
|
|
|
54346
55027
|
return projectIdsWithFile(projectsDir, "ui.db");
|
|
54347
55028
|
}
|
|
54348
55029
|
function retiredLegacyProjectIds(projectsDir) {
|
|
54349
|
-
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !
|
|
55030
|
+
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync17(join43(projectsDir, id, "ui.db")));
|
|
54350
55031
|
}
|
|
54351
55032
|
function tableColumns(db, schema, table) {
|
|
54352
55033
|
return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
|
|
@@ -54502,7 +55183,7 @@ var init_tenant_prime = __esm({
|
|
|
54502
55183
|
// packages/server/src/project-teardown.ts
|
|
54503
55184
|
import { rmSync as rmSync13 } from "node:fs";
|
|
54504
55185
|
import { homedir as homedir19 } from "node:os";
|
|
54505
|
-
import { dirname as
|
|
55186
|
+
import { dirname as dirname22, resolve as resolve10 } from "node:path";
|
|
54506
55187
|
async function stopProjectWorkers(ctx) {
|
|
54507
55188
|
let stopped = 0;
|
|
54508
55189
|
for (const row of ctx.storage.allSessions()) {
|
|
@@ -54522,7 +55203,7 @@ function deleteProjectState(projectId, home = homedir19()) {
|
|
|
54522
55203
|
if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
|
|
54523
55204
|
throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
|
|
54524
55205
|
}
|
|
54525
|
-
if (
|
|
55206
|
+
if (dirname22(resolve10(target)) !== resolve10(dirname22(projectStateDir("x", home)))) {
|
|
54526
55207
|
throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
|
|
54527
55208
|
}
|
|
54528
55209
|
rmSync13(target, { recursive: true, force: true });
|
|
@@ -54551,11 +55232,11 @@ __export(index_exports, {
|
|
|
54551
55232
|
unknownProjectPage: () => unknownProjectPage
|
|
54552
55233
|
});
|
|
54553
55234
|
import { createServer } from "node:http";
|
|
54554
|
-
import { readFileSync as
|
|
54555
|
-
import { dirname as
|
|
55235
|
+
import { readFileSync as readFileSync32, existsSync as existsSync18, statSync as statSync24 } from "node:fs";
|
|
55236
|
+
import { dirname as dirname23, join as join44, resolve as resolve11, extname as extname4, posix as posix2, sep as sep6 } from "node:path";
|
|
54556
55237
|
function readServerAddressHolder(path3) {
|
|
54557
55238
|
try {
|
|
54558
|
-
const value = JSON.parse(
|
|
55239
|
+
const value = JSON.parse(readFileSync32(path3, "utf8"));
|
|
54559
55240
|
if (typeof value?.pid !== "number") return void 0;
|
|
54560
55241
|
return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
|
|
54561
55242
|
} catch {
|
|
@@ -54679,13 +55360,13 @@ function staticIsFresh(req, etag, mtimeMs) {
|
|
|
54679
55360
|
return Number.isFinite(since) && Math.floor(mtimeMs / 1e3) <= Math.floor(since / 1e3);
|
|
54680
55361
|
}
|
|
54681
55362
|
function serveStatic(distDir, req, res) {
|
|
54682
|
-
const rel =
|
|
54683
|
-
let file =
|
|
54684
|
-
if (!file.startsWith(distDir)) file =
|
|
54685
|
-
if (!
|
|
55363
|
+
const rel = posix2.normalize((req.url ?? "/").split("?")[0].replace(/\\/g, "/")).replace(/^(\.\.[/\\])+/, "");
|
|
55364
|
+
let file = join44(distDir, rel === "/" ? "index.html" : rel);
|
|
55365
|
+
if (!file.startsWith(distDir + sep6)) file = join44(distDir, "index.html");
|
|
55366
|
+
if (!existsSync18(file)) file = join44(distDir, "index.html");
|
|
54686
55367
|
try {
|
|
54687
55368
|
const stats = statSync24(file);
|
|
54688
|
-
const cacheControl = file.startsWith(
|
|
55369
|
+
const cacheControl = file.startsWith(join44(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
|
|
54689
55370
|
const etag = `"${stats.size.toString(16)}-${Math.trunc(stats.mtimeMs).toString(16)}"`;
|
|
54690
55371
|
const headers = {
|
|
54691
55372
|
"content-type": MIME[extname4(file)] ?? "application/octet-stream",
|
|
@@ -54701,7 +55382,7 @@ function serveStatic(distDir, req, res) {
|
|
|
54701
55382
|
res.end();
|
|
54702
55383
|
return;
|
|
54703
55384
|
}
|
|
54704
|
-
let body =
|
|
55385
|
+
let body = readFileSync32(file);
|
|
54705
55386
|
const accept = req.headers["accept-encoding"];
|
|
54706
55387
|
const encoding = negotiateEncoding(Array.isArray(accept) ? accept.join(",") : accept);
|
|
54707
55388
|
if (encoding && shouldCompress(new Headers({ "content-type": headers["content-type"] }), body.byteLength)) {
|
|
@@ -54963,7 +55644,7 @@ async function startServer(opts = {}) {
|
|
|
54963
55644
|
if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
|
|
54964
55645
|
if (statusPath && statusIdentity) {
|
|
54965
55646
|
try {
|
|
54966
|
-
runtime.removeStatus(serverAddressPathForStateDir(
|
|
55647
|
+
runtime.removeStatus(serverAddressPathForStateDir(dirname23(statusPath)), statusIdentity);
|
|
54967
55648
|
} catch {
|
|
54968
55649
|
}
|
|
54969
55650
|
}
|
|
@@ -55101,7 +55782,7 @@ async function startServer(opts = {}) {
|
|
|
55101
55782
|
}
|
|
55102
55783
|
statusPath = serverLockPathFor(ctx.project);
|
|
55103
55784
|
const webRoot = resolve11(import.meta.dirname, "..", "..", "web");
|
|
55104
|
-
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) :
|
|
55785
|
+
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join44(webRoot, "dist");
|
|
55105
55786
|
startupPhase = "Vite";
|
|
55106
55787
|
if (opts.dev) {
|
|
55107
55788
|
try {
|
|
@@ -55177,7 +55858,7 @@ async function startServer(opts = {}) {
|
|
|
55177
55858
|
if (vite) {
|
|
55178
55859
|
vite.middlewares(req, res, () => {
|
|
55179
55860
|
try {
|
|
55180
|
-
const html =
|
|
55861
|
+
const html = readFileSync32(join44(webRoot, "index.html"), "utf8");
|
|
55181
55862
|
void vite.transformIndexHtml(url, html).then((out) => {
|
|
55182
55863
|
res.writeHead(200, { "content-type": "text/html" });
|
|
55183
55864
|
res.end(out);
|
|
@@ -55189,7 +55870,7 @@ async function startServer(opts = {}) {
|
|
|
55189
55870
|
});
|
|
55190
55871
|
return;
|
|
55191
55872
|
}
|
|
55192
|
-
if (
|
|
55873
|
+
if (existsSync18(distDir)) {
|
|
55193
55874
|
serveStatic(distDir, req, res);
|
|
55194
55875
|
return;
|
|
55195
55876
|
}
|
|
@@ -55387,7 +56068,7 @@ var init_index = __esm({
|
|
|
55387
56068
|
}
|
|
55388
56069
|
};
|
|
55389
56070
|
isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
|
|
55390
|
-
serverLockPathFor = (project) =>
|
|
56071
|
+
serverLockPathFor = (project) => join44(project.stateDir, "server.lock");
|
|
55391
56072
|
MIME = {
|
|
55392
56073
|
".html": "text/html",
|
|
55393
56074
|
".js": "text/javascript",
|
|
@@ -55410,8 +56091,8 @@ init_project();
|
|
|
55410
56091
|
init_project_launch();
|
|
55411
56092
|
init_shutdown();
|
|
55412
56093
|
init_logging();
|
|
55413
|
-
import { existsSync as
|
|
55414
|
-
import { join as
|
|
56094
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
56095
|
+
import { join as join45 } from "node:path";
|
|
55415
56096
|
process.on("uncaughtException", (error) => {
|
|
55416
56097
|
log.error("dev-child", `uncaught exception: ${error instanceof Error ? error.stack ?? error.message : error}`);
|
|
55417
56098
|
process.exit(1);
|
|
@@ -55433,6 +56114,15 @@ function notifySupervisor(message) {
|
|
|
55433
56114
|
} catch {
|
|
55434
56115
|
}
|
|
55435
56116
|
}
|
|
56117
|
+
var shutdown;
|
|
56118
|
+
process.once("disconnect", () => {
|
|
56119
|
+
if (shutdown) {
|
|
56120
|
+
void shutdown();
|
|
56121
|
+
return;
|
|
56122
|
+
}
|
|
56123
|
+
log.info("dev-child", "supervisor disconnected before the server was up; exiting");
|
|
56124
|
+
process.exit(0);
|
|
56125
|
+
});
|
|
55436
56126
|
try {
|
|
55437
56127
|
const target = projectLaunchTargetFromEnvironment(process.env);
|
|
55438
56128
|
const launchOwnerToken = projectLaunchOwnerTokenFromEnvironment(process.env);
|
|
@@ -55449,9 +56139,9 @@ try {
|
|
|
55449
56139
|
["FRIZZ_SCRIPTS_DIR", process.env.FRIZZ_SCRIPTS_DIR, "index.mjs"],
|
|
55450
56140
|
["FRIZZ_WORKER_PLUGIN_DIR", process.env.FRIZZ_WORKER_PLUGIN_DIR, ".claude-plugin/plugin.json"]
|
|
55451
56141
|
];
|
|
55452
|
-
if (!
|
|
56142
|
+
if (!existsSync19(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
|
|
55453
56143
|
for (const [name, directory, requiredFile] of required) {
|
|
55454
|
-
if (!directory || !
|
|
56144
|
+
if (!directory || !existsSync19(join45(directory, requiredFile)))
|
|
55455
56145
|
throw new Error(`stable artifact launch is missing verified ${name}`);
|
|
55456
56146
|
}
|
|
55457
56147
|
}
|
|
@@ -55472,7 +56162,7 @@ try {
|
|
|
55472
56162
|
bootId: server.ctx.bootId
|
|
55473
56163
|
});
|
|
55474
56164
|
let shuttingDown = false;
|
|
55475
|
-
const
|
|
56165
|
+
const stop = async () => {
|
|
55476
56166
|
if (shuttingDown) return;
|
|
55477
56167
|
shuttingDown = true;
|
|
55478
56168
|
const force = setTimeout(() => process.exit(1), 15e3);
|
|
@@ -55495,9 +56185,9 @@ try {
|
|
|
55495
56185
|
process.exit(1);
|
|
55496
56186
|
}
|
|
55497
56187
|
};
|
|
55498
|
-
|
|
55499
|
-
process.on("
|
|
55500
|
-
process.
|
|
56188
|
+
shutdown = stop;
|
|
56189
|
+
process.on("SIGINT", () => void stop());
|
|
56190
|
+
process.on("SIGTERM", () => void stop());
|
|
55501
56191
|
} catch (err) {
|
|
55502
56192
|
log.error("dev-child", `failed to start: ${err instanceof Error ? err.stack ?? err.message : err}`);
|
|
55503
56193
|
process.exit(1);
|