frizz 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-agent-broker.js +141 -12
- package/dist/dev-child.js +876 -337
- package/dist/frizz.js +81 -19
- package/package.json +1 -1
- package/runtime/cc-worker/DECISIONS.md +11 -0
- package/runtime/cc-worker/bin/frizz-mcp.mjs +58 -24
- package/web-dist/assets/{TerminalPane-CNFymHXK.js → TerminalPane-VbmxsjxK.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-DNBrcJfV.js → abnfDiagram-VRR7QNED-CBCkSzJg.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-DGkzyd6b.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-SXVydqly.js → architectureDiagram-ZJ3FMSHR-Rd44A8-r.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-CgI1GSdx.js → blockDiagram-677ZJIJ3-C8vX06UX.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-KDmJm2tG.js → c4Diagram-LMCZKHZV-CMMjtui-.js} +1 -1
- package/web-dist/assets/channel-BvYE_fQ0.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-j3eOjq4Z.js → chunk-32BRIVSS-DtsFPb8y.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-DZHfa78H.js → chunk-52WLFC77-35RvKhG9.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-CiB5uo24.js → chunk-C7G6YPKG-Dusr0gOw.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-B35Inyjz.js → chunk-EX3LRPZG-BadaF-x2.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-D2sRvM5U.js → chunk-FWX5IMBZ-CpQ7yEDx.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-D247PDv6.js → chunk-HOUHSVGY-CxygFxWC.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-Bu3Tb6Ec.js → chunk-ICXQ74PX-YC4-v0mV.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-C_lQy4on.js → chunk-MOJQB5TN-CTgO9Z8o.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-DU5ZBedR.js → chunk-OGEWGWER-D87J1JIz.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-DWRT7ztS.js → chunk-PUDLZKDR-CVB9kmxS.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-CCX4AK_n.js → chunk-Q4XR5HBZ-B2a88YVr.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-VbixbkDG.js → chunk-V7JOEXUC-DoFngcpl.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-PJvChxMA.js → chunk-VAUOI2AC-X71d5AYZ.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-BjsMfxj8.js → chunk-VR4S4FIN-39jPx_ES.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-Bcb97r3w.js → chunk-WYO6CB5R-CnCFbrQ7.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-CT2sZlhT.js → chunk-ZGVPDNZ5-B7wPa-pc.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-BmoT0Xoa.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-BmoT0Xoa.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-dUpO4u0w.js → cynefin-VYW2F7L2-C4GE-6ZJ.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-BQZ3UIsx.js → cynefinDiagram-TSTJHNR4-DM7vNSjQ.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-K-oO8w51.js → dagre-VKFMJZFB-CBRFVbWb.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-Cr47GJ_S.js → diagram-FQU43EPY-D3t9pHH6.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-BbWzSkoZ.js → diagram-G47NLZAW-CBjFvnAL.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-BGJJBgGM.js → diagram-NH7WQ7WH-DAI3KCBU.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-2W7aunEN.js → diagram-OA4YK3LP-C82PfYbV.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-D-4ITFI6.js → diagram-WEI45ONY-fOJDDfoI.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-CxUcx103.js → ebnfDiagram-CCIWWBDH-B5h_VbLA.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-Cf_5SWAE.js → erDiagram-Q63AITRT-BPje_yvH.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-Bqo9jfNt.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-BwssDy7H.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-CVZI8Ywa.js → ganttDiagram-NO4QXBWP-sBrAv0Zq.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-BsiGpR9l.js → gitGraph-TEB2WS4Q-DRA1mvOm.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DVym_4l1.js → gitGraphDiagram-IHSO6WYX-DBMOi0IS.js} +1 -1
- package/web-dist/assets/index-C_iC5T6e.css +1 -0
- package/web-dist/assets/index-YBqvcp8M.js +506 -0
- package/web-dist/assets/{info-DKCQHKI2-DYcsKFv-.js → info-DKCQHKI2-faVLVx1A.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-hjVxlrTT.js → infoDiagram-FWYZ7A6U-BST26f54.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-B0DKOPSS.js → ishikawaDiagram-FXEZZL3T-AQe4w48R.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-Cohibc5O.js → journeyDiagram-5HDEW3XC-DHYMyWnH.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-_6Zr31vM.js → kanban-definition-HUTT4EX6-DRjdUC1w.js} +1 -1
- package/web-dist/assets/{line-B14EZI6M.js → line-DsDFTOBE.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-DHkc6cqw.js → mermaid-parser.core-BQmWWHcM.js} +3 -3
- package/web-dist/assets/{mermaid.core-DuTvdsKf.js → mermaid.core-DyyZgnTM.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DPRF9pW2.js → mindmap-definition-LN4V7U3C-DKFwErMl.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-Ci8oCcGM.js → packet-7NZHBO7P-Dc2mNrwB.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-Cr3ctC6e.js → pegDiagram-2B236MQR-CApGxts7.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-EHgA307y.js → pie-RZYD4A2V-420IM2Cy.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-UW6Bmu6F.js → pieDiagram-ENE6RG2P-B9AabvqO.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-D6tAsRj9.js → quadrantDiagram-ABIIQ3AL-KDtGo5xp.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-CAQqojcd.js → radar-I7S5WNFK-r00cL8de.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-BZsAOTrm.js → railroad-3IZDKUUU-u3-ja2m8.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-BIa3zJb3.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-Dhv_ZRtc.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-BSnPbaOy.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-bSpff55H.js → railroadDiagram-RFXS5EU6-PXQXCI8m.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-DIAYvXoB.js → requirementDiagram-TGXJPOKE-CsigfYcM.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-DEnsSz3F.js → sankeyDiagram-HTMAVEWB-CZTeOkfq.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-DsG7eb9R.js → sequenceDiagram-DBY2YBRQ-BVnCAk_N.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-DT3XNwjo.js → stateDiagram-2N3HPSRC-ChCJipZW.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BopBzYGR.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-B5hZwgRo.js → swimlanes-5IMT3BWC-DHR9HD5O.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-B3z_1cGb.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-BVTsjkfu.js → timeline-definition-FHXFAJF6-BZl68jCj.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-M6HmP4bB.js → treeView-QDETBFTQ-CcWDnN_I.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-C60TppfC.js → treemap-6X3UGDF4-CoPTx3oa.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-Dv1y3g4S.js → vennDiagram-L72KCM5P-lrsO-4o9.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-D0lMJMWt.js → wardley-OPB4EBWU-hA_IMYdR.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-B1E5NtZY.js → wardleyDiagram-EHGQE667-6T7WmjKn.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-oA91YOvh.js → xychartDiagram-FW5EYKEG-BPYtf6cM.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/architecture-TIHT7OUA-BSUDKbro.js +0 -1
- package/web-dist/assets/channel-DaCOnTxr.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-BlylbZXV.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-BlylbZXV.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-DxKQnZNV.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-Cdz9YM2Z.js +0 -1
- package/web-dist/assets/index-CYzJK7u0.js +0 -497
- package/web-dist/assets/index-CovJDCWc.css +0 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-_2rSHfPz.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-xM3eAMFz.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-cZlBUsED.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-C06v0Ltu.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-CXyN7ZyO.js +0 -8
package/dist/dev-child.js
CHANGED
|
@@ -727,6 +727,20 @@ function ensureProjectIdFile(root, home = homedir3(), seed) {
|
|
|
727
727
|
function hasAny(dir, names) {
|
|
728
728
|
return names.some((name) => existsSync2(join4(dir, name)));
|
|
729
729
|
}
|
|
730
|
+
function isHomeDirectory(dir, home = homedir3()) {
|
|
731
|
+
const canonical = (value) => {
|
|
732
|
+
try {
|
|
733
|
+
return realpathSync2(resolve3(value));
|
|
734
|
+
} catch {
|
|
735
|
+
return resolve3(value);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
try {
|
|
739
|
+
return canonical(dir) === canonical(home);
|
|
740
|
+
} catch {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
730
744
|
function existingProjectId(dir) {
|
|
731
745
|
const fromFile = readProjectIdFile(dir);
|
|
732
746
|
if (fromFile) return fromFile;
|
|
@@ -736,6 +750,9 @@ function existingProjectId(dir) {
|
|
|
736
750
|
return void 0;
|
|
737
751
|
}
|
|
738
752
|
}
|
|
753
|
+
function hasProjectMarker(dir) {
|
|
754
|
+
return hasAny(dir, REPO_MARKERS) || hasAny(dir, PROJECT_MARKERS);
|
|
755
|
+
}
|
|
739
756
|
function discoverProjectRoot(cwd = process.cwd(), home = homedir3()) {
|
|
740
757
|
let dir;
|
|
741
758
|
try {
|
|
@@ -754,6 +771,11 @@ function discoverProjectRoot(cwd = process.cwd(), home = homedir3()) {
|
|
|
754
771
|
}
|
|
755
772
|
return dir;
|
|
756
773
|
}
|
|
774
|
+
function chosenProjectRoot(chosen, home = homedir3()) {
|
|
775
|
+
const dir = resolve3(chosen);
|
|
776
|
+
const discovered = discoverProjectRoot(dir, home);
|
|
777
|
+
return discovered === dir || hasProjectMarker(discovered) ? discovered : dir;
|
|
778
|
+
}
|
|
757
779
|
var FRIZZ_DIR, ID_FILE, SELF_IGNORE, REPO_MARKERS, PROJECT_MARKERS;
|
|
758
780
|
var init_project_root = __esm({
|
|
759
781
|
"packages/server/src/project-root.ts"() {
|
|
@@ -13053,12 +13075,12 @@ function parseAwaitingYaml(text2) {
|
|
|
13053
13075
|
function isAwaitingItemKind(kind) {
|
|
13054
13076
|
return AWAITING_ITEM_KINDS.includes(kind);
|
|
13055
13077
|
}
|
|
13056
|
-
function
|
|
13078
|
+
function parseAwaitingDurationRaw(value) {
|
|
13057
13079
|
const m = AWAITING_DURATION_RE.exec(value.trim());
|
|
13058
13080
|
if (!m) return null;
|
|
13059
13081
|
const ms = Number(m[1]) * DURATION_UNIT_MS[m[2]];
|
|
13060
13082
|
if (!Number.isFinite(ms) || ms <= 0) return null;
|
|
13061
|
-
return
|
|
13083
|
+
return ms;
|
|
13062
13084
|
}
|
|
13063
13085
|
function isInterruptMarker(text2) {
|
|
13064
13086
|
return INTERRUPT_MARKERS.includes(text2.trim());
|
|
@@ -13383,7 +13405,7 @@ function parseGithubWakeSteer(text2) {
|
|
|
13383
13405
|
function frizzRoute(path) {
|
|
13384
13406
|
return `${FRIZZ_ROUTE_PREFIX}${path.startsWith("/") ? path : `/${path}`}`;
|
|
13385
13407
|
}
|
|
13386
|
-
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, 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_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,
|
|
13408
|
+
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, 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, 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, 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_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;
|
|
13387
13409
|
var init_src = __esm({
|
|
13388
13410
|
"packages/shared/src/index.ts"() {
|
|
13389
13411
|
"use strict";
|
|
@@ -13663,8 +13685,13 @@ var init_src = __esm({
|
|
|
13663
13685
|
subAgentCount: external_exports.number().default(0),
|
|
13664
13686
|
// total live sub-agents (≥ subAgents.length)
|
|
13665
13687
|
bgShells: external_exports.array(CompletionHoldOp).default([]),
|
|
13666
|
-
bgShellCount: external_exports.number().default(0)
|
|
13688
|
+
bgShellCount: external_exports.number().default(0),
|
|
13667
13689
|
// total live background shells (≥ bgShells.length)
|
|
13690
|
+
// The worker is DEAD and its recorded turn never ended — cut off by a reboot, a signal or a crash
|
|
13691
|
+
// mid-tool-call (router.cutOffHold). Nothing is running, so nothing will be killed; the hold exists
|
|
13692
|
+
// because the thread is not finished and Done would say it was. Optional rather than defaulted so it
|
|
13693
|
+
// is absent (⇒ false) on every hold that is not this one, and a pre-change client reads them unchanged.
|
|
13694
|
+
cutOff: external_exports.boolean().optional()
|
|
13668
13695
|
});
|
|
13669
13696
|
AskOption = external_exports.object({
|
|
13670
13697
|
label: external_exports.string(),
|
|
@@ -13867,7 +13894,8 @@ var init_src = __esm({
|
|
|
13867
13894
|
* known, so a timer row always carries this. */
|
|
13868
13895
|
timer: external_exports.object({ fireAt: external_exports.string(), prompt: external_exports.string() }).strict().optional()
|
|
13869
13896
|
}).strict();
|
|
13870
|
-
|
|
13897
|
+
PR_WATCH_FOR_MAX_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
13898
|
+
PR_WATCH_DEFAULT_FOR_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
13871
13899
|
PR_WATCH_MAX_ARMED = 32;
|
|
13872
13900
|
PrWatchView = external_exports.object({
|
|
13873
13901
|
id: external_exports.string(),
|
|
@@ -13883,13 +13911,18 @@ var init_src = __esm({
|
|
|
13883
13911
|
/** `owner/repo#123` or a PR URL. Parsed server-side; an unparseable ref is refused rather than stored,
|
|
13884
13912
|
* because a watcher that can never fire is worse than none — the worker rests believing it is covered. */
|
|
13885
13913
|
target: external_exports.string().trim().min(1).max(200),
|
|
13886
|
-
/** How long to watch, as a DURATION (`
|
|
13914
|
+
/** How long to watch, as a DURATION (`2h`, `3d`, `180d` — parseAwaitingDurationRaw, capped at
|
|
13915
|
+
* PR_WATCH_FOR_MAX_MS).
|
|
13887
13916
|
*
|
|
13888
13917
|
* A PR nobody ever reviews would otherwise be polled forever, and a thread parked on it would wait
|
|
13889
13918
|
* forever with it — the same unbounded wait the awaiting fence's `for:` closes, one level down. A
|
|
13890
13919
|
* duration rather than an instant for the same reason it is one there: it cannot be written in the
|
|
13891
13920
|
* past (maintainer 2026-08-15, asking for it explicitly).
|
|
13892
13921
|
*
|
|
13922
|
+
* BOUNDED IS NOT THE SAME AS SHORT, and conflating them is what made this field a noise source. The
|
|
13923
|
+
* bound exists so a forgotten watcher stops polling eventually, which a year satisfies exactly as
|
|
13924
|
+
* well as a day — and a day spent it against every external PR, which does not move on that clock.
|
|
13925
|
+
*
|
|
13893
13926
|
* REQUIRED BY THE TOOL, OPTIONAL ON THE WIRE, and the asymmetry is deliberate. A worker's MCP server
|
|
13894
13927
|
* outlives every frizz restart, so a session dispatched before this existed still holds a binary that
|
|
13895
13928
|
* cannot send it — and making the RPC reject that would break `watch_pr` outright for every thread
|
|
@@ -13904,6 +13937,12 @@ var init_src = __esm({
|
|
|
13904
13937
|
/** True when this exact PR was ALREADY watched by this thread, so the call registered nothing new.
|
|
13905
13938
|
* Re-registering after a compaction is the common case, and a duplicate would double every wake. */
|
|
13906
13939
|
alreadyArmed: external_exports.boolean(),
|
|
13940
|
+
/** When this watcher runs out. Read back so the worker sees the duration it ACTUALLY got rather than
|
|
13941
|
+
* the one it asked for — on a re-registration that is the original expiry, which the call left alone. */
|
|
13942
|
+
expiresAt: external_exports.string(),
|
|
13943
|
+
/** The `for:` the worker wrote, present ONLY when it exceeded the ceiling and was capped. A clamp
|
|
13944
|
+
* nobody is told about is a worker resting on a year of coverage it does not have. */
|
|
13945
|
+
clampedFrom: external_exports.string().optional(),
|
|
13907
13946
|
watches: external_exports.array(PrWatchView)
|
|
13908
13947
|
}).strict();
|
|
13909
13948
|
DropOwnPrWatchInput = external_exports.object({
|
|
@@ -13930,7 +13969,11 @@ var init_src = __esm({
|
|
|
13930
13969
|
* that is both the stronger check and the one that catches the real mistake: naming a sub-agent under
|
|
13931
13970
|
* `kind: "shell"`, which is what put two sub-agents under a "Background shells" heading on 2026-08-26. */
|
|
13932
13971
|
target: external_exports.string().trim().min(1).max(200),
|
|
13933
|
-
/** REQUIRED, and a DURATION (`30m`, `2h`, `3d` —
|
|
13972
|
+
/** REQUIRED, and a DURATION (`30m`, `2h`, `3d` — parseAwaitingDurationRaw, capped at 24h).
|
|
13973
|
+
*
|
|
13974
|
+
* STILL A DAY, where a PR watcher now gets a year: this names a shell or a sub-agent, which lives
|
|
13975
|
+
* inside the session that launched it, so a wait standing longer than a day is one whose target is
|
|
13976
|
+
* almost certainly already gone.
|
|
13934
13977
|
*
|
|
13935
13978
|
* No default and no wire-level optionality, unlike `AddOwnPrWatchInput.for`: that field is optional
|
|
13936
13979
|
* only to keep working for sessions dispatched before it existed, and this RPC has no such sessions.
|
|
@@ -13955,6 +13998,10 @@ var init_src = __esm({
|
|
|
13955
13998
|
/** True when this exact (kind, target) was already watched, so the call registered nothing new and the
|
|
13956
13999
|
* existing expiry stands. Re-registering after a wake or a compaction is the common, correct case. */
|
|
13957
14000
|
alreadyArmed: external_exports.boolean(),
|
|
14001
|
+
/** The `for:` the worker wrote, present ONLY when it exceeded the ceiling and was capped — the twin of
|
|
14002
|
+
* `AddOwnPrWatchResult.clampedFrom`, for the same reason: a silent clamp is a worker resting on
|
|
14003
|
+
* coverage it does not have. */
|
|
14004
|
+
clampedFrom: external_exports.string().optional(),
|
|
13958
14005
|
watches: external_exports.array(OwnWatchView)
|
|
13959
14006
|
}).strict();
|
|
13960
14007
|
DropOwnWatchInput = external_exports.object({
|
|
@@ -13968,25 +14015,38 @@ var init_src = __esm({
|
|
|
13968
14015
|
OWN_WATCH_MAX_ARMED = 24;
|
|
13969
14016
|
AskQuestionKind = external_exports.enum(["question", "multi"]);
|
|
13970
14017
|
ASK_MAX_DEPTH = 3;
|
|
13971
|
-
ASK_MAX_PER_CALL = 4;
|
|
13972
|
-
ASK_MAX_OPEN = 12;
|
|
13973
14018
|
AskedOptionSchema = external_exports.lazy(() => external_exports.object({
|
|
13974
|
-
|
|
13975
|
-
|
|
14019
|
+
// 400, not the 120 it launched with (2026-09-03): a label is a chip line, but "Post the drafted
|
|
14020
|
+
// comment as written" plus its qualifier runs long, and the fence has no cap at all. The answer's
|
|
14021
|
+
// `chosen` carries labels, so its per-item cap matches this one.
|
|
14022
|
+
label: external_exports.string().trim().min(1).max(400),
|
|
14023
|
+
// 20000, not the 4000 the retired `preview` had (2026-09-03): a description is allowed to BE the rich
|
|
14024
|
+
// body — a diff, a drafted comment, a table — and a real diff exceeds 4000.
|
|
14025
|
+
description: external_exports.string().trim().max(2e4).optional(),
|
|
13976
14026
|
recommended: external_exports.boolean().optional(),
|
|
13977
14027
|
preview: external_exports.string().max(4e3).optional(),
|
|
13978
|
-
|
|
14028
|
+
// No count cap, like `options` (2026-09-03); the tree is bounded by ASK_MAX_DEPTH instead.
|
|
14029
|
+
followUps: external_exports.array(AskedQuestionSchema).optional()
|
|
13979
14030
|
}).strict());
|
|
13980
14031
|
AskedQuestionSchema = external_exports.lazy(() => external_exports.object({
|
|
13981
|
-
|
|
14032
|
+
// 4000, not the 600 it launched with (2026-09-03): this field is the whole card context — everything
|
|
14033
|
+
// the human needs to answer cold — and a question that carries its own evidence needs the room. The
|
|
14034
|
+
// answer restates it, so QuestionAnswerSchema's cap matches.
|
|
14035
|
+
question: external_exports.string().trim().min(1).max(4e3),
|
|
13982
14036
|
header: external_exports.string().trim().max(24).optional(),
|
|
13983
14037
|
kind: AskQuestionKind,
|
|
13984
14038
|
danger: external_exports.boolean().optional(),
|
|
13985
|
-
|
|
14039
|
+
// UNBOUNDED, deliberately. This carried `.max(8)` from launch until 2026-09-03, when the maintainer
|
|
14040
|
+
// asked for the cap to go ("allow arbitrary numbers of options"): a `multi` over a long list — which
|
|
14041
|
+
// gates to run, which of twenty findings to act on — is a real shape, and the card letters past 26
|
|
14042
|
+
// (`AA.`) already. The count is the worker's to choose; the answer's `chosen` is unbounded to match.
|
|
14043
|
+
options: external_exports.array(AskedOptionSchema).optional()
|
|
13986
14044
|
}).strict());
|
|
13987
14045
|
AskInput = external_exports.object({
|
|
13988
14046
|
slug: ThreadSlug,
|
|
13989
|
-
|
|
14047
|
+
// No cap on the count (four until 2026-09-03): the tool's own text says "several at once is one
|
|
14048
|
+
// call — register them together", and a cap here told a worker with six to batch and then refused it.
|
|
14049
|
+
questions: external_exports.array(AskedQuestionSchema).min(1)
|
|
13990
14050
|
}).strict();
|
|
13991
14051
|
RegisteredQuestionView = external_exports.object({
|
|
13992
14052
|
/** Minted by frizz. The worker never chose it, which is why an answer RESTATES the question text —
|
|
@@ -14010,17 +14070,19 @@ var init_src = __esm({
|
|
|
14010
14070
|
}).strict();
|
|
14011
14071
|
QuestionAnswerSchema = external_exports.lazy(() => external_exports.object({
|
|
14012
14072
|
questionId: external_exports.string().min(1).max(64),
|
|
14013
|
-
question: external_exports.string().min(1).max(
|
|
14014
|
-
|
|
14073
|
+
question: external_exports.string().min(1).max(4e3),
|
|
14074
|
+
// No count cap: a `multi` may carry any number of options (see AskedQuestionSchema), and the human
|
|
14075
|
+
// may pick every one of them. The per-item cap is the option label's.
|
|
14076
|
+
chosen: external_exports.array(external_exports.string().max(400)),
|
|
14015
14077
|
text: external_exports.string().max(8e3).optional(),
|
|
14016
|
-
followUps: external_exports.array(QuestionAnswerSchema).
|
|
14078
|
+
followUps: external_exports.array(QuestionAnswerSchema).optional()
|
|
14017
14079
|
}).strict());
|
|
14018
14080
|
AnswerQuestionsInput = external_exports.object({
|
|
14019
14081
|
slug: ThreadSlug,
|
|
14020
14082
|
/** SUBMITTED AS A UNIT. The card sends whatever was answered in one call, because a per-question send
|
|
14021
14083
|
* would half-wake a turn: the worker would come back to a payload it cannot act on and would have to
|
|
14022
14084
|
* ask again for the rest. */
|
|
14023
|
-
answers: external_exports.array(QuestionAnswerSchema).min(1)
|
|
14085
|
+
answers: external_exports.array(QuestionAnswerSchema).min(1)
|
|
14024
14086
|
}).strict();
|
|
14025
14087
|
AnswerQuestionsResult = external_exports.object({
|
|
14026
14088
|
/** The ids that were open and are now answered. An id that was already settled is silently absent
|
|
@@ -14030,7 +14092,7 @@ var init_src = __esm({
|
|
|
14030
14092
|
}).strict();
|
|
14031
14093
|
DismissQuestionsInput = external_exports.object({
|
|
14032
14094
|
slug: ThreadSlug,
|
|
14033
|
-
ids: external_exports.array(external_exports.string().min(1).max(64)).min(1)
|
|
14095
|
+
ids: external_exports.array(external_exports.string().min(1).max(64)).min(1)
|
|
14034
14096
|
}).strict();
|
|
14035
14097
|
DismissQuestionsResult = external_exports.object({
|
|
14036
14098
|
dismissed: external_exports.array(external_exports.string()),
|
|
@@ -14213,6 +14275,11 @@ var init_src = __esm({
|
|
|
14213
14275
|
// AUTO-bump (the scheduler resumes the agent with exactly this text) rather than a reminder, which is
|
|
14214
14276
|
// the distinction the held row's tooltip renders. Absent ⇒ the card merely re-surfaces.
|
|
14215
14277
|
snoozePrompt: external_exports.string().optional(),
|
|
14278
|
+
// The instant the human PINNED this thread (absent = not pinned). A pinned thread leaves the rail's
|
|
14279
|
+
// band system entirely — Rested/Active/Snoozed/Done — and holds the pinned band at the very top, in
|
|
14280
|
+
// this instant's order. Lifecycle metadata like the snooze: written only by the pin/unpin verb, and
|
|
14281
|
+
// it outranks every derived state (a pinned thread that finishes stays pinned).
|
|
14282
|
+
pinnedAt: external_exports.string().optional(),
|
|
14216
14283
|
/** The EVENT snooze on the resting card is armed for this exact rest — the human has said "hide this
|
|
14217
14284
|
* until something reports". Distinct from `snoozedUntil`, which is a wall-clock park on the whole
|
|
14218
14285
|
* thread: this one has no deadline and clears itself when the thread comes to a NEW rest.
|
|
@@ -14458,6 +14525,12 @@ var init_src = __esm({
|
|
|
14458
14525
|
// Code caps to the model's real window. Optional so an old blob parses; defaultSettings pins 500_000
|
|
14459
14526
|
// (maintainer 2026-08-26: "a default compaction window of 500k by default").
|
|
14460
14527
|
autoCompactWindow: external_exports.number().int().positive().optional(),
|
|
14528
|
+
// The prompt-cache tier a NEW Claude worker writes to. "auto" leaves the CLI's own choice (1h on a
|
|
14529
|
+
// subscription); "5m" and "1h" reach the worker as CLAUDE_CODE_PROMPT_CACHE_TTL (and the sub-agent
|
|
14530
|
+
// twin). The 1h tier bills a cache write at 2x input against 1.25x for 5m; on 2026-09-03 cache
|
|
14531
|
+
// writes were 51% of a day's spend while the entries were invalidated every 15–30 minutes anyway
|
|
14532
|
+
// (see claudePromptCacheEnv). Optional so an old blob parses; defaultSettings pins "auto".
|
|
14533
|
+
promptCacheTtl: external_exports.enum(["auto", "5m", "1h"]).optional(),
|
|
14461
14534
|
// The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
|
|
14462
14535
|
// unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
|
|
14463
14536
|
// the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
|
|
@@ -14596,6 +14669,11 @@ var init_src = __esm({
|
|
|
14596
14669
|
// can never leave an armed prompt behind.
|
|
14597
14670
|
prompt: SnoozePrompt.nullable().optional()
|
|
14598
14671
|
}).strict();
|
|
14672
|
+
SetThreadPinnedInput = external_exports.object({
|
|
14673
|
+
slug: ThreadSlug,
|
|
14674
|
+
sessionId: external_exports.string().min(1),
|
|
14675
|
+
pinned: external_exports.boolean()
|
|
14676
|
+
}).strict();
|
|
14599
14677
|
SetThreadRecurringPromptInput = external_exports.object({
|
|
14600
14678
|
slug: ThreadSlug,
|
|
14601
14679
|
sessionId: external_exports.string().min(1),
|
|
@@ -15287,10 +15365,16 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15287
15365
|
cursor: TranscriptPageCursor
|
|
15288
15366
|
}).strict();
|
|
15289
15367
|
SocketTranscriptSlug = ThreadSlug;
|
|
15290
|
-
|
|
15368
|
+
SocketFilePath = external_exports.string().min(1).max(4096);
|
|
15369
|
+
SocketTranscriptClientMsg = external_exports.discriminatedUnion("t", [
|
|
15291
15370
|
external_exports.object({ t: external_exports.literal("sub"), topic: external_exports.literal("transcript"), slug: SocketTranscriptSlug }).strict(),
|
|
15292
15371
|
external_exports.object({ t: external_exports.literal("unsub"), topic: external_exports.literal("transcript"), slug: SocketTranscriptSlug }).strict()
|
|
15293
15372
|
]);
|
|
15373
|
+
SocketFileClientMsg = external_exports.discriminatedUnion("t", [
|
|
15374
|
+
external_exports.object({ t: external_exports.literal("sub"), topic: external_exports.literal("file"), path: SocketFilePath }).strict(),
|
|
15375
|
+
external_exports.object({ t: external_exports.literal("unsub"), topic: external_exports.literal("file"), path: SocketFilePath }).strict()
|
|
15376
|
+
]);
|
|
15377
|
+
SocketClientMsg = external_exports.union([SocketTranscriptClientMsg, SocketFileClientMsg]);
|
|
15294
15378
|
ProjectCard = external_exports.object({
|
|
15295
15379
|
id: external_exports.string(),
|
|
15296
15380
|
slug: external_exports.string(),
|
|
@@ -16818,7 +16902,7 @@ function createShutdownBarrier(options) {
|
|
|
16818
16902
|
void drainPromise.catch(() => void 0);
|
|
16819
16903
|
return drainPromise;
|
|
16820
16904
|
};
|
|
16821
|
-
const
|
|
16905
|
+
const close2 = () => {
|
|
16822
16906
|
if (closePromise) return closePromise;
|
|
16823
16907
|
closePromise = deadline(beginDrain(), options.timeoutMs).catch((error) => {
|
|
16824
16908
|
if (error instanceof ShutdownTimeoutError) {
|
|
@@ -16832,7 +16916,7 @@ function createShutdownBarrier(options) {
|
|
|
16832
16916
|
get closing() {
|
|
16833
16917
|
return closing;
|
|
16834
16918
|
},
|
|
16835
|
-
close,
|
|
16919
|
+
close: close2,
|
|
16836
16920
|
whenDrained: beginDrain
|
|
16837
16921
|
};
|
|
16838
16922
|
}
|
|
@@ -16912,13 +16996,13 @@ function linkLatest(dir, target) {
|
|
|
16912
16996
|
}
|
|
16913
16997
|
}
|
|
16914
16998
|
function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Date.now()) {
|
|
16915
|
-
let
|
|
16999
|
+
let entries2;
|
|
16916
17000
|
try {
|
|
16917
|
-
|
|
17001
|
+
entries2 = readdirSync2(dir).filter((name) => name.endsWith(".log") && name !== "latest.log");
|
|
16918
17002
|
} catch {
|
|
16919
17003
|
return;
|
|
16920
17004
|
}
|
|
16921
|
-
const dated =
|
|
17005
|
+
const dated = entries2.map((name) => {
|
|
16922
17006
|
try {
|
|
16923
17007
|
return { name, at: statSync3(join8(dir, name)).mtimeMs };
|
|
16924
17008
|
} catch {
|
|
@@ -18549,6 +18633,12 @@ function slugMintedFromTitle(slug, title) {
|
|
|
18549
18633
|
}
|
|
18550
18634
|
function ensureStorageSchema(db) {
|
|
18551
18635
|
db.exec(STORAGE_SCHEMA);
|
|
18636
|
+
for (const column of ["pinned_at TEXT"]) {
|
|
18637
|
+
try {
|
|
18638
|
+
db.exec(`ALTER TABLE session ADD COLUMN ${column}`);
|
|
18639
|
+
} catch {
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18552
18642
|
}
|
|
18553
18643
|
function createStorage(source, projectId) {
|
|
18554
18644
|
const owned = typeof source === "string";
|
|
@@ -18813,6 +18903,7 @@ function createStorage(source, projectId) {
|
|
|
18813
18903
|
WHERE project_id = @project_id AND slug = ? AND session_id = ? AND runtime_generation = ?
|
|
18814
18904
|
`);
|
|
18815
18905
|
const snoozedUntilStmt = scope.prepare("UPDATE session SET snoozed_until = ?, snooze_prompt = ? WHERE project_id = @project_id AND slug = ?");
|
|
18906
|
+
const pinnedAtStmt = scope.prepare("UPDATE session SET pinned_at = ? WHERE project_id = @project_id AND slug = ?");
|
|
18816
18907
|
const snoozedUntilIfCurrentStmt = scope.prepare(`
|
|
18817
18908
|
UPDATE session SET snoozed_until = ?
|
|
18818
18909
|
WHERE project_id = @project_id AND slug = ? AND session_id = ? AND runtime_generation = ?
|
|
@@ -18995,7 +19086,7 @@ function createStorage(source, projectId) {
|
|
|
18995
19086
|
WHERE project_id = @project_id AND id = ? AND state = 'open'
|
|
18996
19087
|
`);
|
|
18997
19088
|
const undeliveredSettlementsStmt = scope.prepare(
|
|
18998
|
-
"SELECT * FROM thread_question WHERE project_id = @project_id AND state IN ('answered', 'dismissed') AND delivered = 0 ORDER BY
|
|
19089
|
+
"SELECT * FROM thread_question WHERE project_id = @project_id AND state IN ('answered', 'dismissed') AND delivered = 0 ORDER BY asked_at, rowid"
|
|
18999
19090
|
);
|
|
19000
19091
|
const markSettlementDeliveredStmt = scope.prepare(
|
|
19001
19092
|
"UPDATE thread_question SET delivered = 1 WHERE project_id = @project_id AND id = ? AND delivered = 0"
|
|
@@ -19168,6 +19259,7 @@ function createStorage(source, projectId) {
|
|
|
19168
19259
|
SET model = ?, effort = ?, profile_revision = profile_revision + 1
|
|
19169
19260
|
WHERE project_id = @project_id AND slug = ? AND session_id = ? AND runtime_generation = ?
|
|
19170
19261
|
AND runtime_control IS NULL AND profile_pending_model IS NULL AND profile_pending_effort IS NULL
|
|
19262
|
+
AND (profile_set_at IS NULL OR backend = 'codex')
|
|
19171
19263
|
AND (model IS NOT ? OR effort IS NOT ?)
|
|
19172
19264
|
`);
|
|
19173
19265
|
const beginRuntimeGenerationStmt = scope.prepare(`
|
|
@@ -19498,6 +19590,7 @@ function createStorage(source, projectId) {
|
|
|
19498
19590
|
// never be written without a deadline to fire it.
|
|
19499
19591
|
setSnoozedUntil: (slug, until, prompt = null) => void snoozedUntilStmt.run(until, until === null ? null : prompt, slug),
|
|
19500
19592
|
setSnoozedUntilIfCurrent: (slug, sessionId, generation, until) => snoozedUntilIfCurrentStmt.run(until, slug, sessionId, generation).changes === 1,
|
|
19593
|
+
setPinnedAt: (slug, at) => void pinnedAtStmt.run(at, slug),
|
|
19501
19594
|
setBgSnoozeRestedAtIfCurrent: (slug, sessionId, generation, restedAt) => bgSnoozeRestedAtIfCurrentStmt.run(restedAt, slug, sessionId, generation).changes === 1,
|
|
19502
19595
|
setRecurringPromptIfCurrent: (slug, sessionId, generation, write) => recurringStmt.run(...recurringArgs(write), slug, sessionId, generation).changes === 1,
|
|
19503
19596
|
setRecurringPromptBySlug: (slug, write) => recurringBySlugStmt.run(...recurringArgs(write), slug).changes === 1,
|
|
@@ -19505,7 +19598,7 @@ function createStorage(source, projectId) {
|
|
|
19505
19598
|
resetSignoffNudges: (slug) => void resetNudgesStmt.run(slug),
|
|
19506
19599
|
countParkBump: (slug, anchor) => void countParkBumpStmt.run(anchor, slug),
|
|
19507
19600
|
resetParkBumps: (slug) => void resetParkBumpsStmt.run(slug),
|
|
19508
|
-
armPrWatch: (
|
|
19601
|
+
armPrWatch: (watch2) => void armPrWatchStmt.run(watch2),
|
|
19509
19602
|
listPrWatches: (slug, opts) => (opts?.armedOnly ? armedPrWatchesBySlugStmt : prWatchesBySlugStmt).all(slug),
|
|
19510
19603
|
getPrWatch: (id) => prWatchByIdStmt.get(id),
|
|
19511
19604
|
armedPrWatches: () => armedPrWatchesStmt.all(),
|
|
@@ -19782,6 +19875,7 @@ var init_storage = __esm({
|
|
|
19782
19875
|
snoozed_until TEXT,
|
|
19783
19876
|
snooze_prompt TEXT,
|
|
19784
19877
|
bg_snooze_rested_at TEXT,
|
|
19878
|
+
pinned_at TEXT,
|
|
19785
19879
|
meta TEXT,
|
|
19786
19880
|
seen_at TEXT,
|
|
19787
19881
|
transcript_id TEXT,
|
|
@@ -20196,6 +20290,9 @@ var init_settings = __esm({
|
|
|
20196
20290
|
// 500K tokens: half the 1M window every Claude worker is dispatched with. See the schema for why a
|
|
20197
20291
|
// worker left to compact at 1M is the biggest single reason a Frizz thread out-spends the TUI.
|
|
20198
20292
|
autoCompactWindow: 5e5,
|
|
20293
|
+
// "auto" = the CLI's own tier (1h on a subscription). See the schema and claudePromptCacheEnv for
|
|
20294
|
+
// when "5m" is the cheaper choice.
|
|
20295
|
+
promptCacheTtl: "auto",
|
|
20199
20296
|
// Hidden until asked for — see the schema for why.
|
|
20200
20297
|
projectRail: false
|
|
20201
20298
|
});
|
|
@@ -20345,13 +20442,13 @@ function discoverTranscriptDir(logDir, sessionId, memo3 = strandedLogDirs) {
|
|
|
20345
20442
|
let best;
|
|
20346
20443
|
for (const dir of memo3) best = better(best, dir);
|
|
20347
20444
|
if (best) return best.dir;
|
|
20348
|
-
let
|
|
20445
|
+
let entries2;
|
|
20349
20446
|
try {
|
|
20350
|
-
|
|
20447
|
+
entries2 = readdirSync3(dirname7(logDir), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
20351
20448
|
} catch {
|
|
20352
20449
|
return void 0;
|
|
20353
20450
|
}
|
|
20354
|
-
for (const entry of
|
|
20451
|
+
for (const entry of entries2) best = better(best, join11(dirname7(logDir), entry));
|
|
20355
20452
|
if (!best) return void 0;
|
|
20356
20453
|
memo3.add(best.dir);
|
|
20357
20454
|
return best.dir;
|
|
@@ -21272,10 +21369,10 @@ function boundedJson(value, label, state, depth) {
|
|
|
21272
21369
|
return value.map((entry, index) => boundedJson(entry, `${label}[${index}]`, state, depth + 1));
|
|
21273
21370
|
}
|
|
21274
21371
|
if (!value || typeof value !== "object") throw new ClaudeAgentSdkProtocolError(`${label} contains a non-JSON value`);
|
|
21275
|
-
const
|
|
21276
|
-
if (
|
|
21372
|
+
const entries2 = Object.entries(value);
|
|
21373
|
+
if (entries2.length > 256) throw new ClaudeAgentSdkProtocolError(`${label} contains too many object fields`);
|
|
21277
21374
|
const output = {};
|
|
21278
|
-
for (const [key, entry] of
|
|
21375
|
+
for (const [key, entry] of entries2) {
|
|
21279
21376
|
if (RESERVED_KEYS.has(key) || utf8Bytes(key) > 256 || UNSAFE_TEXT2.test(key)) {
|
|
21280
21377
|
UNSAFE_TEXT2.lastIndex = 0;
|
|
21281
21378
|
throw new ClaudeAgentSdkProtocolError(`${label} contains an invalid object key`);
|
|
@@ -21414,10 +21511,10 @@ var init_claude_broker_diagnostics = __esm({
|
|
|
21414
21511
|
// packages/server/src/detached-daemons.ts
|
|
21415
21512
|
import { existsSync as existsSync4 } from "node:fs";
|
|
21416
21513
|
import { fileURLToPath } from "node:url";
|
|
21417
|
-
function resolveDetachedDaemonEntry(moduleUrl,
|
|
21418
|
-
const candidates = [`./${
|
|
21514
|
+
function resolveDetachedDaemonEntry(moduleUrl, basename14) {
|
|
21515
|
+
const candidates = [`./${basename14}.js`, `./${basename14}.ts`].map((rel) => fileURLToPath(new URL(rel, moduleUrl)));
|
|
21419
21516
|
const found = candidates.find((path) => existsSync4(path));
|
|
21420
|
-
if (!found) throw new Error(`frizz is missing the ${
|
|
21517
|
+
if (!found) throw new Error(`frizz is missing the ${basename14} entry \u2014 looked for ${candidates.join(" and ")}`);
|
|
21421
21518
|
return found;
|
|
21422
21519
|
}
|
|
21423
21520
|
var init_detached_daemons = __esm({
|
|
@@ -22537,8 +22634,8 @@ function createTailStateCache(scope, schema = foldSchemaDigest()) {
|
|
|
22537
22634
|
}
|
|
22538
22635
|
return map;
|
|
22539
22636
|
},
|
|
22540
|
-
put(
|
|
22541
|
-
if (!usable || !upsert ||
|
|
22637
|
+
put(entries2) {
|
|
22638
|
+
if (!usable || !upsert || entries2.length === 0) return;
|
|
22542
22639
|
try {
|
|
22543
22640
|
const write = db.transaction((batch) => {
|
|
22544
22641
|
for (const entry of batch) {
|
|
@@ -22559,7 +22656,7 @@ function createTailStateCache(scope, schema = foldSchemaDigest()) {
|
|
|
22559
22656
|
});
|
|
22560
22657
|
}
|
|
22561
22658
|
});
|
|
22562
|
-
write(
|
|
22659
|
+
write(entries2);
|
|
22563
22660
|
} catch {
|
|
22564
22661
|
}
|
|
22565
22662
|
},
|
|
@@ -24132,7 +24229,7 @@ ${ask}`;
|
|
|
24132
24229
|
}
|
|
24133
24230
|
}
|
|
24134
24231
|
if (cacheDirty.size === 0) return;
|
|
24135
|
-
const
|
|
24232
|
+
const entries2 = [];
|
|
24136
24233
|
try {
|
|
24137
24234
|
for (const key of cacheDirty) {
|
|
24138
24235
|
const foreign = key.startsWith("foreign:");
|
|
@@ -24141,12 +24238,12 @@ ${ask}`;
|
|
|
24141
24238
|
const row = foreign ? null : deps.storage.getSession(key) ?? null;
|
|
24142
24239
|
if (!foreign && !row) continue;
|
|
24143
24240
|
const entry = cacheSnapshot(state, row);
|
|
24144
|
-
if (entry)
|
|
24241
|
+
if (entry) entries2.push(entry);
|
|
24145
24242
|
}
|
|
24146
24243
|
} catch {
|
|
24147
24244
|
}
|
|
24148
24245
|
cacheDirty.clear();
|
|
24149
|
-
tailCache.put(
|
|
24246
|
+
tailCache.put(entries2);
|
|
24150
24247
|
}
|
|
24151
24248
|
function consume(state, backend, onLine) {
|
|
24152
24249
|
let size;
|
|
@@ -25140,9 +25237,9 @@ function stringifyOutput(output) {
|
|
|
25140
25237
|
}
|
|
25141
25238
|
function collectRollouts(dir, out, budget) {
|
|
25142
25239
|
if (budget.n <= 0) return;
|
|
25143
|
-
const
|
|
25144
|
-
const dirs =
|
|
25145
|
-
const files =
|
|
25240
|
+
const entries2 = safeReaddir(dir);
|
|
25241
|
+
const dirs = entries2.filter((e) => e.isDirectory()).sort(descByName);
|
|
25242
|
+
const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).sort(descByName);
|
|
25146
25243
|
for (const d of dirs) {
|
|
25147
25244
|
if (budget.n <= 0) return;
|
|
25148
25245
|
collectRollouts(join19(dir, d.name), out, budget);
|
|
@@ -28715,7 +28812,7 @@ var require_stream = __commonJS({
|
|
|
28715
28812
|
terminateOnDestroy = false;
|
|
28716
28813
|
duplex.destroy(err);
|
|
28717
28814
|
});
|
|
28718
|
-
ws.once("close", function
|
|
28815
|
+
ws.once("close", function close2() {
|
|
28719
28816
|
if (duplex.destroyed) return;
|
|
28720
28817
|
duplex.push(null);
|
|
28721
28818
|
});
|
|
@@ -28730,7 +28827,7 @@ var require_stream = __commonJS({
|
|
|
28730
28827
|
called = true;
|
|
28731
28828
|
callback(err2);
|
|
28732
28829
|
});
|
|
28733
|
-
ws.once("close", function
|
|
28830
|
+
ws.once("close", function close2() {
|
|
28734
28831
|
if (!called) callback(err);
|
|
28735
28832
|
process.nextTick(emitClose, duplex);
|
|
28736
28833
|
});
|
|
@@ -29262,13 +29359,18 @@ function claudeCompactionEnv(settings) {
|
|
|
29262
29359
|
if (window === void 0 || !Number.isInteger(window) || window <= 0) return {};
|
|
29263
29360
|
return { [CLAUDE_AUTO_COMPACT_WINDOW_ENV]: String(window) };
|
|
29264
29361
|
}
|
|
29362
|
+
function claudePromptCacheEnv(settings) {
|
|
29363
|
+
const ttl = settings?.promptCacheTtl;
|
|
29364
|
+
if (ttl !== "5m" && ttl !== "1h") return {};
|
|
29365
|
+
return { [CLAUDE_PROMPT_CACHE_TTL_ENV]: ttl, [CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV]: ttl };
|
|
29366
|
+
}
|
|
29265
29367
|
function claudeCompactionWindowOf(env) {
|
|
29266
29368
|
const raw2 = env?.[CLAUDE_AUTO_COMPACT_WINDOW_ENV];
|
|
29267
29369
|
if (raw2 === void 0) return void 0;
|
|
29268
29370
|
const window = Number(raw2);
|
|
29269
29371
|
return Number.isInteger(window) && window > 0 ? window : void 0;
|
|
29270
29372
|
}
|
|
29271
|
-
var FRIZZ_MCP, WORKER_MAX_WEB_SEARCHES, WORKER_MAX_SUBAGENTS, WORKER_MAX_CONCURRENT_SUBAGENTS, CLAUDE_WORKER_ENV, CLAUDE_AUTO_COMPACT_WINDOW_ENV, WORKER_DISALLOWED_TOOLS;
|
|
29373
|
+
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;
|
|
29272
29374
|
var init_types2 = __esm({
|
|
29273
29375
|
"packages/server/src/backend/types.ts"() {
|
|
29274
29376
|
"use strict";
|
|
@@ -29286,6 +29388,8 @@ var init_types2 = __esm({
|
|
|
29286
29388
|
BASH_MAX_TIMEOUT_MS: String(24 * 60 * 60 * 1e3)
|
|
29287
29389
|
};
|
|
29288
29390
|
CLAUDE_AUTO_COMPACT_WINDOW_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW";
|
|
29391
|
+
CLAUDE_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_PROMPT_CACHE_TTL";
|
|
29392
|
+
CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL";
|
|
29289
29393
|
WORKER_DISALLOWED_TOOLS = ["AskUserQuestion"];
|
|
29290
29394
|
}
|
|
29291
29395
|
});
|
|
@@ -29294,18 +29398,18 @@ var init_types2 = __esm({
|
|
|
29294
29398
|
function tomlString(value) {
|
|
29295
29399
|
return JSON.stringify(value);
|
|
29296
29400
|
}
|
|
29297
|
-
function inlineTable(
|
|
29298
|
-
return `{${
|
|
29401
|
+
function inlineTable(entries2) {
|
|
29402
|
+
return `{${entries2.map(([k, v]) => `${k}=${v}`).join(",")}}`;
|
|
29299
29403
|
}
|
|
29300
29404
|
function serverTable(command, args, env) {
|
|
29301
|
-
const
|
|
29405
|
+
const entries2 = [
|
|
29302
29406
|
["command", tomlString(command)],
|
|
29303
29407
|
["args", `[${args.map(tomlString).join(",")}]`]
|
|
29304
29408
|
];
|
|
29305
29409
|
if (env && Object.keys(env).length > 0) {
|
|
29306
|
-
|
|
29410
|
+
entries2.push(["env", inlineTable(Object.entries(env).map(([k, v]) => [k, tomlString(v)]))]);
|
|
29307
29411
|
}
|
|
29308
|
-
return inlineTable(
|
|
29412
|
+
return inlineTable(entries2);
|
|
29309
29413
|
}
|
|
29310
29414
|
function codexMcpConfigArgs(frizzMcp, nodeBin = process.execPath) {
|
|
29311
29415
|
const args = [];
|
|
@@ -30413,10 +30517,10 @@ function mcpFields(raw2) {
|
|
|
30413
30517
|
if (schema.type !== "object" || schema.properties === null || typeof schema.properties !== "object" || Array.isArray(schema.properties)) {
|
|
30414
30518
|
throw new RpcProtocolError(-32602, "MCP elicitation schema must describe object properties");
|
|
30415
30519
|
}
|
|
30416
|
-
const
|
|
30417
|
-
if (
|
|
30520
|
+
const entries2 = Object.entries(schema.properties).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
30521
|
+
if (entries2.length > 32) throw new RpcProtocolError(-32602, "MCP elicitation has too many fields");
|
|
30418
30522
|
const required = new Set(Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : []);
|
|
30419
|
-
return
|
|
30523
|
+
return entries2.map(([id, field]) => mcpField(id, field, required.has(id)));
|
|
30420
30524
|
}
|
|
30421
30525
|
function userInputFields(questions) {
|
|
30422
30526
|
return questions.map((question) => {
|
|
@@ -32841,14 +32945,14 @@ function sessionsDir2(codexHome) {
|
|
|
32841
32945
|
}
|
|
32842
32946
|
function newestRollouts(dir, out, budget) {
|
|
32843
32947
|
if (budget.n <= 0) return;
|
|
32844
|
-
let
|
|
32948
|
+
let entries2;
|
|
32845
32949
|
try {
|
|
32846
|
-
|
|
32950
|
+
entries2 = readdirSync7(dir, { withFileTypes: true });
|
|
32847
32951
|
} catch {
|
|
32848
32952
|
return;
|
|
32849
32953
|
}
|
|
32850
|
-
const dirs =
|
|
32851
|
-
const files =
|
|
32954
|
+
const dirs = entries2.filter((e) => e.isDirectory()).map((e) => e.name).sort(descByName2);
|
|
32955
|
+
const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
|
|
32852
32956
|
for (const d of dirs) {
|
|
32853
32957
|
if (budget.n <= 0) return;
|
|
32854
32958
|
newestRollouts(join22(dir, d), out, budget);
|
|
@@ -33607,7 +33711,7 @@ function readAwaitingPark(hints) {
|
|
|
33607
33711
|
if (isAwaitingItemKind(h.kind)) {
|
|
33608
33712
|
if (value) items.push({ kind: h.kind, value });
|
|
33609
33713
|
} else if (h.kind === "for") {
|
|
33610
|
-
forMs =
|
|
33714
|
+
forMs = parseAwaitingDurationRaw(value);
|
|
33611
33715
|
}
|
|
33612
33716
|
}
|
|
33613
33717
|
return { items, forMs };
|
|
@@ -33625,9 +33729,13 @@ function parkIsHonoured(park, live) {
|
|
|
33625
33729
|
if (park.forMs === null) return false;
|
|
33626
33730
|
return unaccountedItems(park.items, live).length === 0;
|
|
33627
33731
|
}
|
|
33732
|
+
function parkForMaxMs(park) {
|
|
33733
|
+
if (park.items.length === 0) return AWAITING_FOR_MAX_MS;
|
|
33734
|
+
return park.items.every((i) => i.kind === "pr") ? PR_WATCH_FOR_MAX_MS : AWAITING_FOR_MAX_MS;
|
|
33735
|
+
}
|
|
33628
33736
|
function parkExpiresAt(park, fenceAtMs) {
|
|
33629
33737
|
if (park.forMs === null || !Number.isFinite(fenceAtMs)) return null;
|
|
33630
|
-
return fenceAtMs + Math.min(park.forMs,
|
|
33738
|
+
return fenceAtMs + Math.min(park.forMs, parkForMaxMs(park));
|
|
33631
33739
|
}
|
|
33632
33740
|
function readGithubStatusBook(raw2) {
|
|
33633
33741
|
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) return {};
|
|
@@ -33657,18 +33765,148 @@ var init_awaiting = __esm({
|
|
|
33657
33765
|
}
|
|
33658
33766
|
});
|
|
33659
33767
|
|
|
33768
|
+
// packages/server/src/backend/project-mcp-servers.ts
|
|
33769
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
33770
|
+
import { homedir as homedir10 } from "node:os";
|
|
33771
|
+
import { join as join25 } from "node:path";
|
|
33772
|
+
function readJsonObject(path) {
|
|
33773
|
+
try {
|
|
33774
|
+
const parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
33775
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
33776
|
+
} catch {
|
|
33777
|
+
return void 0;
|
|
33778
|
+
}
|
|
33779
|
+
}
|
|
33780
|
+
function objectField(source, key) {
|
|
33781
|
+
const value = source?.[key];
|
|
33782
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
33783
|
+
}
|
|
33784
|
+
function stringList(source, key) {
|
|
33785
|
+
const value = source?.[key];
|
|
33786
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
33787
|
+
}
|
|
33788
|
+
function stringMap(value) {
|
|
33789
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
33790
|
+
const out = {};
|
|
33791
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
33792
|
+
if (typeof entry === "string") out[key] = entry;
|
|
33793
|
+
}
|
|
33794
|
+
return out;
|
|
33795
|
+
}
|
|
33796
|
+
function expandMcpEnvRefs(value, env) {
|
|
33797
|
+
let unresolved = false;
|
|
33798
|
+
const out = value.replace(ENV_REF, (_match, name, fallback) => {
|
|
33799
|
+
const resolved = env[name];
|
|
33800
|
+
if (resolved !== void 0) return resolved;
|
|
33801
|
+
if (fallback !== void 0) return fallback;
|
|
33802
|
+
unresolved = true;
|
|
33803
|
+
return "";
|
|
33804
|
+
});
|
|
33805
|
+
return unresolved ? void 0 : out;
|
|
33806
|
+
}
|
|
33807
|
+
function expandMap(map, env) {
|
|
33808
|
+
if (!map) return void 0;
|
|
33809
|
+
const out = {};
|
|
33810
|
+
for (const [key, value] of Object.entries(map)) {
|
|
33811
|
+
const expanded = expandMcpEnvRefs(value, env);
|
|
33812
|
+
if (expanded === void 0) return null;
|
|
33813
|
+
out[key] = expanded;
|
|
33814
|
+
}
|
|
33815
|
+
return out;
|
|
33816
|
+
}
|
|
33817
|
+
function normalizeMcpServer(raw2, env) {
|
|
33818
|
+
if (raw2 === null || typeof raw2 !== "object" || Array.isArray(raw2)) return void 0;
|
|
33819
|
+
const record = raw2;
|
|
33820
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
33821
|
+
if (type === "http" || type === "sse") {
|
|
33822
|
+
if (typeof record.url !== "string") return void 0;
|
|
33823
|
+
const url = expandMcpEnvRefs(record.url, env);
|
|
33824
|
+
const headers = expandMap(stringMap(record.headers), env);
|
|
33825
|
+
if (url === void 0 || headers === null) return void 0;
|
|
33826
|
+
return { type, url, ...headers ? { headers } : {} };
|
|
33827
|
+
}
|
|
33828
|
+
if (type !== void 0 && type !== "stdio") return void 0;
|
|
33829
|
+
if (typeof record.command !== "string") return void 0;
|
|
33830
|
+
const command = expandMcpEnvRefs(record.command, env);
|
|
33831
|
+
if (command === void 0) return void 0;
|
|
33832
|
+
const args = [];
|
|
33833
|
+
for (const entry of Array.isArray(record.args) ? record.args : []) {
|
|
33834
|
+
if (typeof entry !== "string") return void 0;
|
|
33835
|
+
const expanded = expandMcpEnvRefs(entry, env);
|
|
33836
|
+
if (expanded === void 0) return void 0;
|
|
33837
|
+
args.push(expanded);
|
|
33838
|
+
}
|
|
33839
|
+
const serverEnv = expandMap(stringMap(record.env), env);
|
|
33840
|
+
if (serverEnv === null) return void 0;
|
|
33841
|
+
return { command, ...args.length ? { args } : {}, ...serverEnv ? { env: serverEnv } : {} };
|
|
33842
|
+
}
|
|
33843
|
+
function collectApproval(layers) {
|
|
33844
|
+
const approval = { all: false, enabled: /* @__PURE__ */ new Set(), disabled: /* @__PURE__ */ new Set() };
|
|
33845
|
+
for (const layer of layers) {
|
|
33846
|
+
if (layer?.enableAllProjectMcpServers === true) approval.all = true;
|
|
33847
|
+
for (const name of stringList(layer, "enabledMcpjsonServers")) approval.enabled.add(name);
|
|
33848
|
+
for (const name of stringList(layer, "disabledMcpjsonServers")) approval.disabled.add(name);
|
|
33849
|
+
}
|
|
33850
|
+
return approval;
|
|
33851
|
+
}
|
|
33852
|
+
function userScopeRemoteServers(account, cwd, env) {
|
|
33853
|
+
const out = {};
|
|
33854
|
+
const maps = [objectField(account, "mcpServers"), objectField(objectField(objectField(account, "projects"), cwd), "mcpServers")];
|
|
33855
|
+
for (const map of maps) {
|
|
33856
|
+
for (const [name, raw2] of Object.entries(map ?? {})) {
|
|
33857
|
+
const server = normalizeMcpServer(raw2, env);
|
|
33858
|
+
if (server && server.type !== void 0 && server.type !== "stdio") out[name] = server;
|
|
33859
|
+
}
|
|
33860
|
+
}
|
|
33861
|
+
return out;
|
|
33862
|
+
}
|
|
33863
|
+
function projectMcpServers(cwd, options = {}) {
|
|
33864
|
+
const env = options.env ?? process.env;
|
|
33865
|
+
const home = options.home ?? homedir10();
|
|
33866
|
+
const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
|
|
33867
|
+
const accountFile = configOverride ? join25(configOverride, ".claude.json") : join25(home, ".claude.json");
|
|
33868
|
+
const userSettingsFile = join25(configOverride ?? join25(home, ".claude"), "settings.json");
|
|
33869
|
+
const account = readJsonObject(accountFile);
|
|
33870
|
+
const out = userScopeRemoteServers(account, cwd, env);
|
|
33871
|
+
const declared = objectField(readJsonObject(join25(cwd, ".mcp.json")), "mcpServers");
|
|
33872
|
+
if (!declared) return out;
|
|
33873
|
+
const approval = collectApproval([
|
|
33874
|
+
readJsonObject(join25(cwd, ".claude", "settings.json")),
|
|
33875
|
+
readJsonObject(join25(cwd, ".claude", "settings.local.json")),
|
|
33876
|
+
readJsonObject(userSettingsFile),
|
|
33877
|
+
objectField(objectField(account, "projects"), cwd)
|
|
33878
|
+
]);
|
|
33879
|
+
for (const [name, raw2] of Object.entries(declared)) {
|
|
33880
|
+
if (approval.disabled.has(name)) continue;
|
|
33881
|
+
if (!approval.all && !approval.enabled.has(name)) continue;
|
|
33882
|
+
const server = normalizeMcpServer(raw2, env);
|
|
33883
|
+
if (server) out[name] = server;
|
|
33884
|
+
}
|
|
33885
|
+
return out;
|
|
33886
|
+
}
|
|
33887
|
+
function workerMcpServers(project, frizz) {
|
|
33888
|
+
return { ...project ?? {}, ...frizz ?? {} };
|
|
33889
|
+
}
|
|
33890
|
+
var ENV_REF;
|
|
33891
|
+
var init_project_mcp_servers = __esm({
|
|
33892
|
+
"packages/server/src/backend/project-mcp-servers.ts"() {
|
|
33893
|
+
"use strict";
|
|
33894
|
+
ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
33895
|
+
}
|
|
33896
|
+
});
|
|
33897
|
+
|
|
33660
33898
|
// packages/server/src/worker-plugin-dir.ts
|
|
33661
33899
|
import { existsSync as existsSync8 } from "node:fs";
|
|
33662
|
-
import { dirname as dirname10, join as
|
|
33900
|
+
import { dirname as dirname10, join as join26 } from "node:path";
|
|
33663
33901
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
33664
33902
|
function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
|
|
33665
33903
|
const override = env.FRIZZ_WORKER_PLUGIN_DIR;
|
|
33666
|
-
if (override && existsSync8(
|
|
33904
|
+
if (override && existsSync8(join26(override, ".claude-plugin", "plugin.json")))
|
|
33667
33905
|
return override;
|
|
33668
33906
|
let current = dirname10(fileURLToPath2(moduleUrl));
|
|
33669
33907
|
for (; ; ) {
|
|
33670
|
-
const candidate =
|
|
33671
|
-
if (existsSync8(
|
|
33908
|
+
const candidate = join26(current, "cc-worker");
|
|
33909
|
+
if (existsSync8(join26(candidate, ".claude-plugin", "plugin.json"))) return candidate;
|
|
33672
33910
|
const parent = dirname10(current);
|
|
33673
33911
|
if (parent === current) return void 0;
|
|
33674
33912
|
current = parent;
|
|
@@ -33697,6 +33935,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
|
|
|
33697
33935
|
AGENT_COMPLETION,
|
|
33698
33936
|
VISUAL_EVIDENCE,
|
|
33699
33937
|
QUALITY_BAR,
|
|
33938
|
+
TOKEN_ECONOMY,
|
|
33700
33939
|
QUESTIONS,
|
|
33701
33940
|
STOP_CRITERION,
|
|
33702
33941
|
lean ? null : TRIVIAL_PROMPTS
|
|
@@ -33706,7 +33945,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
|
|
|
33706
33945
|
out = out.replaceAll("{{FRIZZ_MONITORS_DIR}}", opts.monitorsDir?.trim() || MONITORS_DIR_FALLBACK);
|
|
33707
33946
|
return out;
|
|
33708
33947
|
}
|
|
33709
|
-
var INLINE, INTRO, DEFER, OPENING, ACTIVITY_CAPTIONS, SIGNALS, AGENT_COMPLETION, VISUAL_EVIDENCE, QUALITY_BAR, QUESTIONS, STOP_CRITERION, TRIVIAL_PROMPTS, THREAD_NAME, SCRATCHPAD, BACKEND, SPAWN_THREAD, THREAD_EXECUTION, MONITORS_DIR_FALLBACK;
|
|
33948
|
+
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;
|
|
33710
33949
|
var init_workerPrompt = __esm({
|
|
33711
33950
|
"packages/server/src/workerPrompt.ts"() {
|
|
33712
33951
|
"use strict";
|
|
@@ -33914,7 +34153,14 @@ exactly ONE of them.
|
|
|
33914
34153
|
Register FIRST: this line states the wait, it does not create one. That tool wakes you on CI
|
|
33915
34154
|
turning green or red, and on every later review, approval or comment, for as long as the PR lives.
|
|
33916
34155
|
- \`for:\` \u2014 **REQUIRED**, and a DURATION: \`30s\`, \`15m\`, \`2h\`, \`3d\`. Never an instant. When it runs
|
|
33917
|
-
out frizz brings you back to re-check everything; re-parking is fine and uncapped.
|
|
34156
|
+
out frizz brings you back to re-check everything; re-parking is fine and uncapped. Capped at a day,
|
|
34157
|
+
except on a park naming ONLY \`prs:\`, where it runs to a year.
|
|
34158
|
+
- **A PULL REQUEST IN SOMEONE ELSE'S REPO TAKES MONTHS, SO ASK FOR MONTHS** \u2014 \`for: 180d\`, and give
|
|
34159
|
+
\`mcp__frizz__watch_pr\` the same. It moves on its maintainers' clock, not yours, so a short \`for:\`
|
|
34160
|
+
expires against a PR nothing has touched: a wake carrying no news, and a re-arm, once per expiry
|
|
34161
|
+
until somebody notices. Long is free \u2014 real activity wakes you the moment it lands either way, and
|
|
34162
|
+
the human snoozes or archives the thread when they want it gone. Hours are for a PR you control and
|
|
34163
|
+
a check you expect to settle today.
|
|
33918
34164
|
- \`title:\` \u2014 OPTIONAL, the card's heading: **40 characters or fewer**, sentence case, naming THIS
|
|
33919
34165
|
wait ("Fork-CI approval", "Nightly bench, arm 3 of 3"); longer is trimmed on a word boundary.
|
|
33920
34166
|
Without it the card is headed "Awaiting", which is true of every park and specific to none.
|
|
@@ -34001,6 +34247,19 @@ reads when images are unavailable.`;
|
|
|
34001
34247
|
formatter, a rename sweep) \u2014 run it, then name the files it touched in your handoff.
|
|
34002
34248
|
- Ground every load-bearing claim in code, a command, or a doc you actually read \u2014 never memory. State
|
|
34003
34249
|
plainly what failed, what you skipped, and what you could not verify.`;
|
|
34250
|
+
TOKEN_ECONOMY = `## Every message you send re-reads your whole context
|
|
34251
|
+
|
|
34252
|
+
The API call is the unit of cost, not the tokens you type: each message re-reads your entire context
|
|
34253
|
+
(150k\u2013450k tokens for most of an effort) whether it carries one tool call or ten.
|
|
34254
|
+
|
|
34255
|
+
- **Batch independent tool calls into ONE message.** List privately what you need next; then request
|
|
34256
|
+
every item that does not depend on another's result in the same response.
|
|
34257
|
+
- **One Bash call, several commands.** Chain the steps of one job with \`&&\` / \`;\` and print
|
|
34258
|
+
labelled sections. Use absolute paths; never spend a call on \`cd\` alone.
|
|
34259
|
+
- **Never poll from the turn.** A \`sleep\`-and-check loop is a full re-read every iteration. A
|
|
34260
|
+
background shell, a sub-agent or a registered watch wakes you when the thing is done.
|
|
34261
|
+
- **Finish the turn's whole job before you rest.** Every wake is a new turn on the full context, so
|
|
34262
|
+
do not rest on a step you could take now.`;
|
|
34004
34263
|
QUESTIONS = `## Questions for the human
|
|
34005
34264
|
|
|
34006
34265
|
You run under a dashboard, not a live chat, so your FINAL MESSAGE is the whole interface. Open with 2-4
|
|
@@ -34055,10 +34314,11 @@ fence is answerable only while its message is on screen, so a question written i
|
|
|
34055
34314
|
the transcript scrolls or your context is compacted, while a registration is still there tomorrow. It
|
|
34056
34315
|
takes \`questions\` \u2014 several at once, each with \`question\`, \`kind\` (\`question\` = pick one,
|
|
34057
34316
|
\`multi\` = pick several, no options at all = free text), an optional \`danger\`, and \`options\` each
|
|
34058
|
-
carrying a \`label\`, a one
|
|
34059
|
-
|
|
34060
|
-
|
|
34061
|
-
|
|
34317
|
+
carrying a \`label\`, a \`description\` (one line for a simple fork, or FULL MARKDOWN over several
|
|
34318
|
+
lines \u2014 a list of consequences, a code block, the diff an option would produce, the message that would
|
|
34319
|
+
be posted; it renders inside the option, so the human sees it BEFORE choosing), an optional
|
|
34320
|
+
\`recommended\`, and optional \`followUps\`: questions that become live ONLY if that option is taken,
|
|
34321
|
+
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
|
|
34062
34322
|
it carries no timeout and waits on a person, so keep doing everything that does not depend on the
|
|
34063
34323
|
answer. The answer comes back as its own wake, restating what was asked. Withdraw one you no longer
|
|
34064
34324
|
need with \`mcp__frizz__unask\`, above all when you work the answer out yourself. **ON A THREAD
|
|
@@ -34793,12 +35053,12 @@ var init_claude_broker_client = __esm({
|
|
|
34793
35053
|
});
|
|
34794
35054
|
|
|
34795
35055
|
// packages/server/src/backend/claude-permission-interactions.ts
|
|
34796
|
-
import { homedir as
|
|
35056
|
+
import { homedir as homedir11 } from "node:os";
|
|
34797
35057
|
function clip(text2, max) {
|
|
34798
35058
|
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
34799
35059
|
}
|
|
34800
35060
|
function tildePath(path) {
|
|
34801
|
-
const home =
|
|
35061
|
+
const home = homedir11();
|
|
34802
35062
|
if (!path.startsWith(home)) return path;
|
|
34803
35063
|
const rest = path.slice(home.length);
|
|
34804
35064
|
return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path;
|
|
@@ -35044,7 +35304,7 @@ var init_claude_permission_interactions = __esm({
|
|
|
35044
35304
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
35045
35305
|
function withFrizzThreadSlug(servers, slug) {
|
|
35046
35306
|
const frizz = servers?.[FRIZZ_MCP.name];
|
|
35047
|
-
if (!frizz) return servers;
|
|
35307
|
+
if (!frizz || !("command" in frizz)) return servers;
|
|
35048
35308
|
return { ...servers, [FRIZZ_MCP.name]: { ...frizz, env: { ...frizz.env, FRIZZ_THREAD_SLUG: slug } } };
|
|
35049
35309
|
}
|
|
35050
35310
|
function claudeBrokerBridgeEnabled(env = process.env) {
|
|
@@ -35172,6 +35432,7 @@ function createClaudeAgentBrokerBridge(deps) {
|
|
|
35172
35432
|
// The auto-compact ceiling, from Settings (500K by default). Without it a `[1m]` worker compacts
|
|
35173
35433
|
// only near 1M, re-sending up to 5x a TUI session's conversation on every turn past 200K.
|
|
35174
35434
|
...claudeCompactionEnv(deps.getSettings?.()),
|
|
35435
|
+
...claudePromptCacheEnv(deps.getSettings?.()),
|
|
35175
35436
|
...we?.permDir ? { FRIZZ_PERM_DIR: we.permDir } : {},
|
|
35176
35437
|
// The cc-worker plugin's PreToolUse hook DENIES AskUserQuestion, because without frizz in the
|
|
35177
35438
|
// loop a blocking question freezes a headless worker where nobody can answer it. On the broker path
|
|
@@ -35447,19 +35708,19 @@ var init_claude_agent_broker_bridge = __esm({
|
|
|
35447
35708
|
});
|
|
35448
35709
|
|
|
35449
35710
|
// packages/server/src/backend/auth-status.ts
|
|
35450
|
-
import { join as
|
|
35451
|
-
import { homedir as
|
|
35452
|
-
import { readFileSync as
|
|
35711
|
+
import { join as join27 } from "node:path";
|
|
35712
|
+
import { homedir as homedir12, platform as platform2 } from "node:os";
|
|
35713
|
+
import { readFileSync as readFileSync17, statSync as statSync11 } from "node:fs";
|
|
35453
35714
|
import { execFile as execFile4 } from "node:child_process";
|
|
35454
35715
|
import { promisify as promisify4 } from "node:util";
|
|
35455
35716
|
function claudeConfigDir2() {
|
|
35456
35717
|
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
35457
|
-
return override && override.trim() ? override :
|
|
35718
|
+
return override && override.trim() ? override : join27(homedir12(), ".claude");
|
|
35458
35719
|
}
|
|
35459
35720
|
function claudeFileState(configDir) {
|
|
35460
35721
|
let raw2;
|
|
35461
35722
|
try {
|
|
35462
|
-
raw2 =
|
|
35723
|
+
raw2 = readFileSync17(join27(configDir, ".credentials.json"), "utf8");
|
|
35463
35724
|
} catch (err) {
|
|
35464
35725
|
return err.code === "ENOENT" ? "absent" : "error";
|
|
35465
35726
|
}
|
|
@@ -35492,7 +35753,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
|
|
|
35492
35753
|
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
|
|
35493
35754
|
let raw2;
|
|
35494
35755
|
try {
|
|
35495
|
-
raw2 =
|
|
35756
|
+
raw2 = readFileSync17(join27(codexHome, "auth.json"), "utf8");
|
|
35496
35757
|
} catch (err) {
|
|
35497
35758
|
return err.code === "ENOENT" ? "signed-out" : "unknown";
|
|
35498
35759
|
}
|
|
@@ -35557,7 +35818,7 @@ function asEmail(value) {
|
|
|
35557
35818
|
}
|
|
35558
35819
|
function claudeAccountFile() {
|
|
35559
35820
|
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
35560
|
-
return override && override.trim() ?
|
|
35821
|
+
return override && override.trim() ? join27(override, ".claude.json") : join27(homedir12(), ".claude.json");
|
|
35561
35822
|
}
|
|
35562
35823
|
function readClaudeAccountEmail(path = claudeAccountFile()) {
|
|
35563
35824
|
let stat2;
|
|
@@ -35570,7 +35831,7 @@ function readClaudeAccountEmail(path = claudeAccountFile()) {
|
|
|
35570
35831
|
if (memo3 && memo3.path === path && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
|
|
35571
35832
|
let email;
|
|
35572
35833
|
try {
|
|
35573
|
-
const doc = JSON.parse(
|
|
35834
|
+
const doc = JSON.parse(readFileSync17(path, "utf8"));
|
|
35574
35835
|
email = asEmail(doc?.oauthAccount?.emailAddress);
|
|
35575
35836
|
} catch {
|
|
35576
35837
|
email = void 0;
|
|
@@ -35581,7 +35842,7 @@ function readClaudeAccountEmail(path = claudeAccountFile()) {
|
|
|
35581
35842
|
function readCodexAccountEmail(codexHome = defaultCodexHome()) {
|
|
35582
35843
|
let doc;
|
|
35583
35844
|
try {
|
|
35584
|
-
doc = JSON.parse(
|
|
35845
|
+
doc = JSON.parse(readFileSync17(join27(codexHome, "auth.json"), "utf8"));
|
|
35585
35846
|
} catch {
|
|
35586
35847
|
return void 0;
|
|
35587
35848
|
}
|
|
@@ -35649,8 +35910,8 @@ var init_auth_status = __esm({
|
|
|
35649
35910
|
});
|
|
35650
35911
|
|
|
35651
35912
|
// packages/server/src/dispatch.ts
|
|
35652
|
-
import { closeSync as closeSync12, constants, existsSync as existsSync9, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as
|
|
35653
|
-
import { basename as basename6, join as
|
|
35913
|
+
import { closeSync as closeSync12, constants, existsSync as existsSync9, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync18, realpathSync as realpathSync7, statSync as statSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
|
|
35914
|
+
import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
|
|
35654
35915
|
import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
|
|
35655
35916
|
function fallbackTitle(prompt) {
|
|
35656
35917
|
const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
|
|
@@ -35669,7 +35930,7 @@ function fallbackTitle(prompt) {
|
|
|
35669
35930
|
}
|
|
35670
35931
|
function resolveSlug(frizzDir, base, taken) {
|
|
35671
35932
|
base = ThreadSlug.parse(base);
|
|
35672
|
-
const isTaken = (slug) => existsSync9(
|
|
35933
|
+
const isTaken = (slug) => existsSync9(join28(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
|
|
35673
35934
|
if (!isTaken(base)) return base;
|
|
35674
35935
|
for (let n = 2; ; n++) {
|
|
35675
35936
|
const suffix = `-${n}`;
|
|
@@ -35686,12 +35947,12 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
35686
35947
|
if (!parsed.success) return null;
|
|
35687
35948
|
try {
|
|
35688
35949
|
const projectRoot = realpathSync7(projectDir);
|
|
35689
|
-
const frizzPath =
|
|
35950
|
+
const frizzPath = join28(projectRoot, ".frizz");
|
|
35690
35951
|
const frizzStat = lstatSync3(frizzPath);
|
|
35691
35952
|
if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
|
|
35692
35953
|
const realFrizz = realpathSync7(frizzPath);
|
|
35693
35954
|
if (dirname11(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
|
|
35694
|
-
const path =
|
|
35955
|
+
const path = join28(realFrizz, `${parsed.data}.md`);
|
|
35695
35956
|
const before = lstatSync3(path);
|
|
35696
35957
|
if (!before.isFile() || before.isSymbolicLink()) return null;
|
|
35697
35958
|
const realPath = realpathSync7(path);
|
|
@@ -35702,7 +35963,7 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
35702
35963
|
const fd = openSync13(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
35703
35964
|
try {
|
|
35704
35965
|
openedBefore = fstatSync2(fd);
|
|
35705
|
-
contents =
|
|
35966
|
+
contents = readFileSync18(fd);
|
|
35706
35967
|
openedAfter = fstatSync2(fd);
|
|
35707
35968
|
} finally {
|
|
35708
35969
|
closeSync12(fd);
|
|
@@ -35735,7 +35996,7 @@ function boardAuthorizesAdoption(board, slug) {
|
|
|
35735
35996
|
return !board.errorItems.some((item) => item.file === `${slug}.md`);
|
|
35736
35997
|
}
|
|
35737
35998
|
function ensureSafeDirectDirectory(parent, name) {
|
|
35738
|
-
const path =
|
|
35999
|
+
const path = join28(parent, name);
|
|
35739
36000
|
try {
|
|
35740
36001
|
mkdirSync13(path);
|
|
35741
36002
|
} catch (error) {
|
|
@@ -35765,8 +36026,8 @@ function loadWorkerPrompt(kind = "claude") {
|
|
|
35765
36026
|
function monitorScriptsDir() {
|
|
35766
36027
|
const plugin = workerPluginDir();
|
|
35767
36028
|
if (!plugin) return void 0;
|
|
35768
|
-
const dir =
|
|
35769
|
-
return existsSync9(
|
|
36029
|
+
const dir = join28(plugin, "skills", "gh", "scripts");
|
|
36030
|
+
return existsSync9(join28(dir, "ci-watch.mjs")) ? dir : void 0;
|
|
35770
36031
|
}
|
|
35771
36032
|
function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
35772
36033
|
if (!hookScript || !sessionId) return {};
|
|
@@ -35778,7 +36039,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
35778
36039
|
}
|
|
35779
36040
|
]
|
|
35780
36041
|
});
|
|
35781
|
-
const bashBackgroundHook =
|
|
36042
|
+
const bashBackgroundHook = join28(dirname11(hookScript), "bash-background.mjs");
|
|
35782
36043
|
return {
|
|
35783
36044
|
bypass_hook_trust: true,
|
|
35784
36045
|
hooks: {
|
|
@@ -35804,7 +36065,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
35804
36065
|
}
|
|
35805
36066
|
function scratchpadHookScript() {
|
|
35806
36067
|
const plugin = workerPluginDir();
|
|
35807
|
-
return plugin ?
|
|
36068
|
+
return plugin ? join28(plugin, "hooks", "scratchpad.mjs") : void 0;
|
|
35808
36069
|
}
|
|
35809
36070
|
function composePrompt(sessionId, prompt, kind = "claude") {
|
|
35810
36071
|
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.";
|
|
@@ -35820,12 +36081,12 @@ function scratchpadOrientation(sessionId, kind = "claude") {
|
|
|
35820
36081
|
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.`;
|
|
35821
36082
|
}
|
|
35822
36083
|
function frizzConfigBlock(projectDir) {
|
|
35823
|
-
const path =
|
|
36084
|
+
const path = join28(projectDir, "FRIZZ.md");
|
|
35824
36085
|
let body;
|
|
35825
36086
|
try {
|
|
35826
36087
|
const st = statSync12(path);
|
|
35827
36088
|
if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
|
|
35828
|
-
body =
|
|
36089
|
+
body = readFileSync18(path, "utf8").trim();
|
|
35829
36090
|
} catch {
|
|
35830
36091
|
return "";
|
|
35831
36092
|
}
|
|
@@ -35860,7 +36121,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
|
|
|
35860
36121
|
const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
|
|
35861
36122
|
const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
|
|
35862
36123
|
if (!pluginDir) return void 0;
|
|
35863
|
-
const scriptPath =
|
|
36124
|
+
const scriptPath = join28(pluginDir, "bin", FRIZZ_MCP.script);
|
|
35864
36125
|
if (!existsSync9(scriptPath)) return void 0;
|
|
35865
36126
|
return {
|
|
35866
36127
|
scriptPath,
|
|
@@ -35870,7 +36131,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
|
|
|
35870
36131
|
...slug ? { slug } : {}
|
|
35871
36132
|
};
|
|
35872
36133
|
}
|
|
35873
|
-
function claudeMcpConfig(mcp) {
|
|
36134
|
+
function claudeMcpConfig(mcp, project) {
|
|
35874
36135
|
const mcpServers = {};
|
|
35875
36136
|
const allowedTools = [];
|
|
35876
36137
|
if (mcp) {
|
|
@@ -35878,13 +36139,16 @@ function claudeMcpConfig(mcp) {
|
|
|
35878
36139
|
mcpServers[FRIZZ_MCP.name] = { command: process.execPath, args: [mcp.scriptPath], env };
|
|
35879
36140
|
allowedTools.push(`mcp__${FRIZZ_MCP.name}`);
|
|
35880
36141
|
}
|
|
35881
|
-
return { mcpServers, allowedTools };
|
|
36142
|
+
return { mcpServers: workerMcpServers(project, mcpServers), allowedTools };
|
|
35882
36143
|
}
|
|
35883
|
-
function claudeMcpFlags(mcp) {
|
|
35884
|
-
const { mcpServers, allowedTools } = claudeMcpConfig(mcp);
|
|
35885
|
-
|
|
36144
|
+
function claudeMcpFlags(mcp, project) {
|
|
36145
|
+
const { mcpServers, allowedTools } = claudeMcpConfig(mcp, project);
|
|
36146
|
+
const argv = ["--strict-mcp-config"];
|
|
36147
|
+
if (Object.keys(mcpServers).length === 0) return argv;
|
|
35886
36148
|
const config = JSON.stringify({ mcpServers });
|
|
35887
|
-
|
|
36149
|
+
argv.push("--mcp-config", config);
|
|
36150
|
+
if (allowedTools.length > 0) argv.push(`--allowedTools=${allowedTools.join(",")}`);
|
|
36151
|
+
return argv;
|
|
35888
36152
|
}
|
|
35889
36153
|
function workerDisallowedToolFlags() {
|
|
35890
36154
|
return [`--disallowedTools=${WORKER_DISALLOWED_TOOLS.join(",")}`];
|
|
@@ -35896,7 +36160,7 @@ function buildClaudeCommand(opts) {
|
|
|
35896
36160
|
if (effort.effort) argv.push("--effort", effort.effort);
|
|
35897
36161
|
argv.push(...claudeUltracodeFlags(effort));
|
|
35898
36162
|
if (opts.pluginDir) argv.push("--plugin-dir", opts.pluginDir);
|
|
35899
|
-
argv.push(...claudeMcpFlags(opts.frizzMcp));
|
|
36163
|
+
argv.push(...claudeMcpFlags(opts.frizzMcp, opts.projectMcpServers));
|
|
35900
36164
|
argv.push(...workerDisallowedToolFlags());
|
|
35901
36165
|
const worker = opts.workerPrompt ?? loadWorkerPrompt();
|
|
35902
36166
|
const system = [worker, opts.extraSystemPrompt?.trim()].filter(Boolean).join("\n\n");
|
|
@@ -35929,7 +36193,7 @@ function buildClaudeResumeCommand(opts) {
|
|
|
35929
36193
|
if (effort.effort) argv.push("--effort", effort.effort);
|
|
35930
36194
|
argv.push(...claudeUltracodeFlags(effort));
|
|
35931
36195
|
if (opts.pluginDir) argv.push("--plugin-dir", opts.pluginDir);
|
|
35932
|
-
argv.push(...claudeMcpFlags(opts.frizzMcp));
|
|
36196
|
+
argv.push(...claudeMcpFlags(opts.frizzMcp, opts.projectMcpServers));
|
|
35933
36197
|
argv.push(...workerDisallowedToolFlags());
|
|
35934
36198
|
const worker = opts.workerPrompt ?? loadWorkerPrompt();
|
|
35935
36199
|
const system = [worker, opts.extraSystemPrompt?.trim()].filter(Boolean).join("\n\n");
|
|
@@ -35940,7 +36204,7 @@ function buildClaudeResumeCommand(opts) {
|
|
|
35940
36204
|
}
|
|
35941
36205
|
function createDispatcher(deps) {
|
|
35942
36206
|
const readBoardSource = deps.readBoard ?? readBoard;
|
|
35943
|
-
const frizzDir =
|
|
36207
|
+
const frizzDir = join28(deps.project.dir, ".frizz");
|
|
35944
36208
|
const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
|
|
35945
36209
|
function cleanupPrewrites(built) {
|
|
35946
36210
|
for (const path of new Set(built.prewrite.map((file) => file.path))) {
|
|
@@ -35953,7 +36217,7 @@ function createDispatcher(deps) {
|
|
|
35953
36217
|
function cleanupDispatchFiles(scratchRel, built, sessionId) {
|
|
35954
36218
|
cleanupPrewrites(built);
|
|
35955
36219
|
try {
|
|
35956
|
-
rmSync9(
|
|
36220
|
+
rmSync9(join28(deps.project.dir, scratchRel), { force: true, recursive: true });
|
|
35957
36221
|
} catch {
|
|
35958
36222
|
}
|
|
35959
36223
|
cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
|
|
@@ -36328,6 +36592,7 @@ var init_dispatch = __esm({
|
|
|
36328
36592
|
init_src();
|
|
36329
36593
|
init_logging();
|
|
36330
36594
|
init_project();
|
|
36595
|
+
init_project_mcp_servers();
|
|
36331
36596
|
init_types2();
|
|
36332
36597
|
init_types2();
|
|
36333
36598
|
init_worker_plugin_dir();
|
|
@@ -36385,8 +36650,8 @@ var init_dispatch = __esm({
|
|
|
36385
36650
|
import {
|
|
36386
36651
|
watch as fsWatch
|
|
36387
36652
|
} from "node:fs";
|
|
36388
|
-
import { homedir as
|
|
36389
|
-
import { join as
|
|
36653
|
+
import { homedir as homedir13 } from "node:os";
|
|
36654
|
+
import { join as join29 } from "node:path";
|
|
36390
36655
|
import watcher from "@parcel/watcher";
|
|
36391
36656
|
function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
|
|
36392
36657
|
if (!liveness) return false;
|
|
@@ -36515,7 +36780,7 @@ function safeQuestionAnswer(raw2) {
|
|
|
36515
36780
|
return void 0;
|
|
36516
36781
|
}
|
|
36517
36782
|
}
|
|
36518
|
-
function answersInFlight(rows, lastUserAt) {
|
|
36783
|
+
function answersInFlight(rows, lastUserAt, wakeOnDismissals = false) {
|
|
36519
36784
|
const userAt = lastUserAt ? Date.parse(lastUserAt) : Number.NaN;
|
|
36520
36785
|
const answers = [];
|
|
36521
36786
|
const dismissed = [];
|
|
@@ -36531,7 +36796,8 @@ function answersInFlight(rows, lastUserAt) {
|
|
|
36531
36796
|
const parsed = safeQuestionAnswer(q.answer);
|
|
36532
36797
|
if (parsed) answers.push(parsed);
|
|
36533
36798
|
}
|
|
36534
|
-
|
|
36799
|
+
if (answers.length > 0) return questionAnswerMessage(answers, dismissed);
|
|
36800
|
+
return wakeOnDismissals && dismissed.length > 0 ? questionsCancelledWakeMessage(dismissed.length) : void 0;
|
|
36535
36801
|
}
|
|
36536
36802
|
function hasRegisteredBackgroundPark(tele, armedWatches, nowMs) {
|
|
36537
36803
|
return armedWatches.some((w) => {
|
|
@@ -36681,7 +36947,8 @@ function resolveSessionProfile(row, tele) {
|
|
|
36681
36947
|
const observedAt = tele?.profileAt ? Date.parse(tele.profileAt) : NaN;
|
|
36682
36948
|
const spawnedAt = Date.parse(row.spawned_at);
|
|
36683
36949
|
const setAt = row.profile_set_at ? Date.parse(row.profile_set_at) : NaN;
|
|
36684
|
-
const
|
|
36950
|
+
const operatorSetSticks = (row.backend ?? "claude") !== "codex";
|
|
36951
|
+
const supersededByOperatorSet = Number.isFinite(setAt) && (operatorSetSticks || setAt > observedAt);
|
|
36685
36952
|
const observedIsCurrent = Number.isFinite(observedAt) && Number.isFinite(spawnedAt) && observedAt >= spawnedAt && !supersededByOperatorSet;
|
|
36686
36953
|
const observedModel = tele?.model ? normalizeObservedThreadModel(row.backend ?? "claude", tele.model) ?? tele.model.trim() : void 0;
|
|
36687
36954
|
const model = (!persistedModel || observedIsCurrent ? observedModel : void 0) || persistedModel;
|
|
@@ -36781,7 +37048,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
|
|
|
36781
37048
|
const spec = safeQuestionSpec(q.spec);
|
|
36782
37049
|
if (spec) questions.push({ id: q.id, spec, askedAt: new Date(q.asked_at).toISOString() });
|
|
36783
37050
|
}
|
|
36784
|
-
const inFlightAnswers = answersInFlight(questionRows, rawTele?.lastUserAt);
|
|
37051
|
+
const inFlightAnswers = answersInFlight(questionRows, rawTele?.lastUserAt, row.recurring_on_rest === 1 && Boolean(row.recurring_prompt?.trim()));
|
|
36785
37052
|
const armedWatches = storage.listThreadWatches(row.slug, { armedOnly: true }).map((w) => ({
|
|
36786
37053
|
id: w.id,
|
|
36787
37054
|
kind: w.kind,
|
|
@@ -36866,6 +37133,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
|
|
|
36866
37133
|
// Only meaningful while the snooze is still pending — a prompt without a live deadline is an
|
|
36867
37134
|
// already-delivered (or superseded) bump the row has not been swept clean of yet.
|
|
36868
37135
|
snoozePrompt: snoozedUntil ? row.snooze_prompt ?? void 0 : void 0,
|
|
37136
|
+
pinnedAt: row.pinned_at ?? void 0,
|
|
36869
37137
|
// The event-snooze, as a fact the CHAT can read. `awaitingBackground` still ignores it — that flag
|
|
36870
37138
|
// states whether the thread is waiting, which the snooze does not change — so the suppression is a
|
|
36871
37139
|
// presentation rule the client applies, not a second opinion about the thread's state.
|
|
@@ -37083,7 +37351,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
37083
37351
|
...project.githubRepo ? { githubRepo: project.githubRepo } : {},
|
|
37084
37352
|
projectSlug: findByPath(project.dir)?.slug,
|
|
37085
37353
|
// So the client can expand a `~` a worker wrote in prose (see BoardSnapshot.homeDir).
|
|
37086
|
-
homeDir:
|
|
37354
|
+
homeDir: homedir13()
|
|
37087
37355
|
};
|
|
37088
37356
|
const sessionThreads = buildSessionThreads(assembledAtMs);
|
|
37089
37357
|
armSnoozeWake(sessionThreads, assembledAtMs);
|
|
@@ -37184,7 +37452,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
37184
37452
|
if (parcelSub || stopped) return Promise.resolve();
|
|
37185
37453
|
if (watchSetup) return watchSetup;
|
|
37186
37454
|
const setup = (async () => {
|
|
37187
|
-
const next = await subscribe(
|
|
37455
|
+
const next = await subscribe(join29(project.dir, ".frizz"), () => scheduleRebuild());
|
|
37188
37456
|
if (stopped) {
|
|
37189
37457
|
await next.unsubscribe();
|
|
37190
37458
|
return;
|
|
@@ -37284,6 +37552,9 @@ var init_board = __esm({
|
|
|
37284
37552
|
});
|
|
37285
37553
|
|
|
37286
37554
|
// packages/server/src/wake-store.ts
|
|
37555
|
+
function isQuietWindowExempt(hintKey) {
|
|
37556
|
+
return WAKE_QUIET_EXEMPT_HINT_PREFIXES.some((prefix) => hintKey.startsWith(prefix));
|
|
37557
|
+
}
|
|
37287
37558
|
function delivery(row) {
|
|
37288
37559
|
return {
|
|
37289
37560
|
id: row.id,
|
|
@@ -37334,14 +37605,22 @@ function ensureWakeDeliverySchema(db) {
|
|
|
37334
37605
|
ON wake_delivery(project_id, state, next_attempt_at, created_at);
|
|
37335
37606
|
`);
|
|
37336
37607
|
}
|
|
37337
|
-
function createWakeDeliveryStore(scope) {
|
|
37608
|
+
function createWakeDeliveryStore(scope, options = {}) {
|
|
37338
37609
|
const db = scope.db;
|
|
37339
37610
|
ensureWakeDeliverySchema(db);
|
|
37611
|
+
const quietWindowMs = Math.max(0, options.quietWindowMs ?? WAKE_QUIET_WINDOW_MS);
|
|
37340
37612
|
const byId = scope.prepare("SELECT * FROM wake_delivery WHERE project_id = @project_id AND id = ?");
|
|
37341
37613
|
const all = scope.prepare("SELECT * FROM wake_delivery WHERE project_id = @project_id ORDER BY created_at, id");
|
|
37342
37614
|
const open = scope.prepare(
|
|
37343
37615
|
"SELECT * FROM wake_delivery WHERE project_id = @project_id AND state IN ('pending', 'leased') ORDER BY created_at, id"
|
|
37344
37616
|
);
|
|
37617
|
+
const pendingForStmt = scope.prepare(
|
|
37618
|
+
"SELECT * FROM wake_delivery WHERE project_id = @project_id AND thread_slug = ? AND session_id = ? AND state = 'pending' ORDER BY created_at, id"
|
|
37619
|
+
);
|
|
37620
|
+
const lastHandoffStmt = scope.prepare(`
|
|
37621
|
+
SELECT MAX(MAX(COALESCE(delivered_at, 0), COALESCE(sent_at, 0))) AS at
|
|
37622
|
+
FROM wake_delivery WHERE project_id = @project_id AND thread_slug = ?
|
|
37623
|
+
`);
|
|
37345
37624
|
const insert = scope.prepare(`
|
|
37346
37625
|
INSERT INTO wake_delivery (
|
|
37347
37626
|
project_id, id, thread_slug, session_id, fence_id, hint_key, message, reason, state, attempts,
|
|
@@ -37349,7 +37628,7 @@ function createWakeDeliveryStore(scope) {
|
|
|
37349
37628
|
delivered_at, terminal_at
|
|
37350
37629
|
) VALUES (
|
|
37351
37630
|
@project_id, @id, @slug, @sessionId, @fenceId, @hintKey, @message, @reason, 'pending', 0,
|
|
37352
|
-
@
|
|
37631
|
+
@notBefore, NULL, NULL, NULL, @now, @now, NULL, NULL
|
|
37353
37632
|
)
|
|
37354
37633
|
ON CONFLICT DO NOTHING
|
|
37355
37634
|
`);
|
|
@@ -37376,6 +37655,12 @@ function createWakeDeliveryStore(scope) {
|
|
|
37376
37655
|
last_error = NULL, sent_at = NULL, updated_at = @now
|
|
37377
37656
|
WHERE project_id = @project_id AND id = @id AND state = 'pending' AND next_attempt_at <= @now AND attempts < @maxAttempts
|
|
37378
37657
|
`);
|
|
37658
|
+
const adoptStmt = scope.prepare(`
|
|
37659
|
+
UPDATE wake_delivery SET
|
|
37660
|
+
state = 'leased', attempts = attempts + 1, lease_owner = @owner, lease_until = @leaseUntil,
|
|
37661
|
+
last_error = NULL, sent_at = NULL, updated_at = @now
|
|
37662
|
+
WHERE project_id = @project_id AND id = @id AND state = 'pending' AND attempts < @maxAttempts
|
|
37663
|
+
`);
|
|
37379
37664
|
const deferFailureStmt = scope.prepare(`
|
|
37380
37665
|
UPDATE wake_delivery SET
|
|
37381
37666
|
lease_until = @retryAt, last_error = @error, updated_at = @now
|
|
@@ -37416,7 +37701,12 @@ function createWakeDeliveryStore(scope) {
|
|
|
37416
37701
|
WHERE project_id = @project_id AND id = @id AND state IN ('pending', 'leased')
|
|
37417
37702
|
`);
|
|
37418
37703
|
const enqueueTxn = db.transaction((input, now) => {
|
|
37419
|
-
|
|
37704
|
+
let notBefore = now;
|
|
37705
|
+
if (quietWindowMs > 0 && !isQuietWindowExempt(input.hintKey)) {
|
|
37706
|
+
const lastHandoff = lastHandoffStmt.get(input.slug)?.at ?? 0;
|
|
37707
|
+
if (lastHandoff > 0) notBefore = Math.max(now, lastHandoff + quietWindowMs);
|
|
37708
|
+
}
|
|
37709
|
+
const created = insert.run({ ...input, now, notBefore }).changes === 1;
|
|
37420
37710
|
const row = byId.get(input.id);
|
|
37421
37711
|
if (!row) throw new Error("wake delivery disappeared while it was being enqueued");
|
|
37422
37712
|
if (row.thread_slug !== input.slug || row.session_id !== input.sessionId || row.fence_id !== input.fenceId || row.hint_key !== input.hintKey || row.message !== input.message || row.reason !== input.reason) {
|
|
@@ -37432,6 +37722,10 @@ function createWakeDeliveryStore(scope) {
|
|
|
37432
37722
|
if (claimStmt.run({ id: candidate.id, owner, now, leaseUntil, maxAttempts }).changes !== 1) return void 0;
|
|
37433
37723
|
return delivery(byId.get(candidate.id));
|
|
37434
37724
|
});
|
|
37725
|
+
const adoptTxn = db.transaction((id, owner, now, leaseUntil, maxAttempts) => {
|
|
37726
|
+
if (adoptStmt.run({ id, owner, now, leaseUntil, maxAttempts }).changes !== 1) return void 0;
|
|
37727
|
+
return delivery(byId.get(id));
|
|
37728
|
+
});
|
|
37435
37729
|
return {
|
|
37436
37730
|
enqueue: (input, now) => enqueueTxn.immediate(input, now),
|
|
37437
37731
|
get: (id) => {
|
|
@@ -37440,7 +37734,9 @@ function createWakeDeliveryStore(scope) {
|
|
|
37440
37734
|
},
|
|
37441
37735
|
list: () => all.all().map(delivery),
|
|
37442
37736
|
listOpen: () => open.all().map(delivery),
|
|
37737
|
+
pendingFor: (slug, sessionId) => pendingForStmt.all(slug, sessionId).map(delivery),
|
|
37443
37738
|
claim: (owner, now, leaseUntil, maxAttempts) => claimTxn.immediate(owner, now, leaseUntil, maxAttempts),
|
|
37739
|
+
adopt: (id, owner, now, leaseUntil, maxAttempts) => adoptTxn.immediate(id, owner, now, leaseUntil, maxAttempts),
|
|
37444
37740
|
deferFailure: (id, owner, now, retryAt, error) => deferFailureStmt.run({
|
|
37445
37741
|
id,
|
|
37446
37742
|
owner,
|
|
@@ -37459,10 +37755,12 @@ function createWakeDeliveryStore(scope) {
|
|
|
37459
37755
|
supersede: (id, now, reason) => supersedeStmt.run({ id, now, reason: reason.slice(0, 500) }).changes === 1
|
|
37460
37756
|
};
|
|
37461
37757
|
}
|
|
37462
|
-
var OUTBOX_CAP, WAKE_DELIVERY_TABLES;
|
|
37758
|
+
var WAKE_QUIET_WINDOW_MS, WAKE_QUIET_EXEMPT_HINT_PREFIXES, OUTBOX_CAP, WAKE_DELIVERY_TABLES;
|
|
37463
37759
|
var init_wake_store = __esm({
|
|
37464
37760
|
"packages/server/src/wake-store.ts"() {
|
|
37465
37761
|
"use strict";
|
|
37762
|
+
WAKE_QUIET_WINDOW_MS = 5 * 6e4;
|
|
37763
|
+
WAKE_QUIET_EXEMPT_HINT_PREFIXES = ["answers:", "limit:"];
|
|
37466
37764
|
OUTBOX_CAP = 2e3;
|
|
37467
37765
|
WAKE_DELIVERY_TABLES = ["wake_delivery"];
|
|
37468
37766
|
}
|
|
@@ -37745,9 +38043,9 @@ function createGithubReviewFetcher(deps = {}) {
|
|
|
37745
38043
|
for (const entry of batch.values()) for (const resolve12 of entry.resolve) resolve12({ status: "deferred" });
|
|
37746
38044
|
return;
|
|
37747
38045
|
}
|
|
37748
|
-
const
|
|
37749
|
-
for (let offset = 0; offset <
|
|
37750
|
-
const chunk =
|
|
38046
|
+
const entries2 = [...batch.values()];
|
|
38047
|
+
for (let offset = 0; offset < entries2.length; offset += MAX_REFS_PER_REQUEST) {
|
|
38048
|
+
const chunk = entries2.slice(offset, offset + MAX_REFS_PER_REQUEST);
|
|
37751
38049
|
if (offset > 0 && now() < rateLimitBlockedUntilMs) {
|
|
37752
38050
|
for (const entry of chunk) for (const resolve12 of entry.resolve) resolve12({ status: "deferred" });
|
|
37753
38051
|
continue;
|
|
@@ -37960,11 +38258,11 @@ function failedCheckNames(rollup, runs = [], cap = 8) {
|
|
|
37960
38258
|
return { names: names.slice(0, cap), omitted: Math.max(0, names.length - cap) };
|
|
37961
38259
|
}
|
|
37962
38260
|
function githubWatchStatus(pr, polledAt) {
|
|
37963
|
-
const
|
|
38261
|
+
const entries2 = Array.isArray(pr.rollup) ? pr.rollup.filter((c) => c && typeof c === "object") : [];
|
|
37964
38262
|
let running = 0;
|
|
37965
38263
|
let passed = 0;
|
|
37966
38264
|
let failed = 0;
|
|
37967
|
-
for (const c of
|
|
38265
|
+
for (const c of entries2) {
|
|
37968
38266
|
const status = typeof c.status === "string" ? c.status : void 0;
|
|
37969
38267
|
const state2 = typeof c.state === "string" ? c.state : void 0;
|
|
37970
38268
|
const conclusion = typeof c.conclusion === "string" ? c.conclusion : void 0;
|
|
@@ -37974,19 +38272,19 @@ function githubWatchStatus(pr, polledAt) {
|
|
|
37974
38272
|
else passed++;
|
|
37975
38273
|
}
|
|
37976
38274
|
const state = pr.state === "MERGED" ? "merged" : pr.state === "CLOSED" ? "closed" : "open";
|
|
37977
|
-
const checks =
|
|
38275
|
+
const checks = entries2.length === 0 ? "none" : failed > 0 ? "failing" : running > 0 ? "running" : "passing";
|
|
37978
38276
|
const merge = pr.mergeable === "CONFLICTING" ? "conflicting" : pr.mergeable !== "MERGEABLE" ? "unknown" : checks === "failing" || pr.reviewDecision === "CHANGES_REQUESTED" || pr.reviewDecision === "REVIEW_REQUIRED" ? "blocked" : "mergeable";
|
|
37979
38277
|
return {
|
|
37980
38278
|
checks,
|
|
37981
38279
|
running,
|
|
37982
38280
|
passed,
|
|
37983
38281
|
failed,
|
|
37984
|
-
failing: failedCheckNames(
|
|
38282
|
+
failing: failedCheckNames(entries2, pr.workflowRuns ?? []).names,
|
|
37985
38283
|
merge,
|
|
37986
38284
|
state,
|
|
37987
38285
|
polledAt,
|
|
37988
38286
|
head: pr.head,
|
|
37989
|
-
failureSig: failureSignature(
|
|
38287
|
+
failureSig: failureSignature(entries2)
|
|
37990
38288
|
};
|
|
37991
38289
|
}
|
|
37992
38290
|
function wakeDeliveryId(slug, sessionId, fenceId) {
|
|
@@ -38215,7 +38513,7 @@ function createScheduler(deps) {
|
|
|
38215
38513
|
const confirmGraceMs = Math.max(1, deps.confirmGraceMs ?? 6e4);
|
|
38216
38514
|
const deliveryBatchSize = Math.max(0, deps.deliveryBatchSize ?? 50);
|
|
38217
38515
|
const deliveryOwner = randomUUID14();
|
|
38218
|
-
const outbox = createWakeDeliveryStore(deps.storage.scope);
|
|
38516
|
+
const outbox = createWakeDeliveryStore(deps.storage.scope, { quietWindowMs: deps.wakeQuietWindowMs ?? WAKE_QUIET_WINDOW_MS });
|
|
38219
38517
|
const reviewFailures = /* @__PURE__ */ new Map();
|
|
38220
38518
|
let timer = null;
|
|
38221
38519
|
let activeTick = null;
|
|
@@ -38311,9 +38609,14 @@ function createScheduler(deps) {
|
|
|
38311
38609
|
}
|
|
38312
38610
|
return "superseded";
|
|
38313
38611
|
}
|
|
38314
|
-
function isDeliverableNow(item, context) {
|
|
38612
|
+
function isDeliverableNow(item, context, nowMs) {
|
|
38315
38613
|
if (context === "current-idle") return true;
|
|
38316
|
-
|
|
38614
|
+
if (context !== "current-busy") return false;
|
|
38615
|
+
if (isHeartbeatFenceId(item.fenceId) || isTimerFenceId(item.fenceId) || isCompactFenceId(item.fenceId)) return true;
|
|
38616
|
+
return nowMs - item.createdAt >= MID_TURN_HOLD_MAX_MS;
|
|
38617
|
+
}
|
|
38618
|
+
function heldPastBound(item, context, nowMs) {
|
|
38619
|
+
return context === "current-busy" && item.sentAt === null && item.lastError === WAKE_HOLD_DEFERRAL && nowMs - item.createdAt >= MID_TURN_HOLD_MAX_MS;
|
|
38317
38620
|
}
|
|
38318
38621
|
function activityLabel(a) {
|
|
38319
38622
|
if (a.kind === "comment") return "comment";
|
|
@@ -38517,7 +38820,7 @@ function createScheduler(deps) {
|
|
|
38517
38820
|
if (tele.authFault) continue;
|
|
38518
38821
|
if (tele.apiFault) continue;
|
|
38519
38822
|
const questionRows = deps.storage.listThreadQuestions(row.slug);
|
|
38520
|
-
if (tele.lastFence || tele.pendingQuestion || registeredDoneFence(deps.storage.getThreadDone(row.slug), tele.lastUserAt) !== void 0 || questionRows.some((q) => q.state === "open") || answersInFlight(questionRows, tele.lastUserAt) !== void 0 || deps.storage.listThreadWatches(row.slug, { armedOnly: true }).length > 0) {
|
|
38823
|
+
if (tele.lastFence || tele.pendingQuestion || registeredDoneFence(deps.storage.getThreadDone(row.slug), tele.lastUserAt) !== void 0 || questionRows.some((q) => q.state === "open") || answersInFlight(questionRows, tele.lastUserAt, row.recurring_on_rest === 1 && Boolean(row.recurring_prompt?.trim())) !== void 0 || deps.storage.listThreadWatches(row.slug, { armedOnly: true }).length > 0) {
|
|
38521
38824
|
if ((row.signoff_nudges ?? 0) > 0) deps.storage.resetSignoffNudges(row.slug);
|
|
38522
38825
|
continue;
|
|
38523
38826
|
}
|
|
@@ -38724,12 +39027,27 @@ ${clock}`;
|
|
|
38724
39027
|
}
|
|
38725
39028
|
}
|
|
38726
39029
|
const prWatchPolledAt = /* @__PURE__ */ new Map();
|
|
39030
|
+
function readPrWatchHeld(raw2) {
|
|
39031
|
+
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
39032
|
+
const h = raw2;
|
|
39033
|
+
if (!Array.isArray(h.items)) return void 0;
|
|
39034
|
+
const items = h.items.flatMap((i) => GithubWakeItem.safeParse(i).success ? [i] : []);
|
|
39035
|
+
const c = h.checks && typeof h.checks === "object" ? h.checks : void 0;
|
|
39036
|
+
const checks = c && (c.verdict === "passing" || c.verdict === "failing") ? {
|
|
39037
|
+
verdict: c.verdict,
|
|
39038
|
+
passed: Number(c.passed) || 0,
|
|
39039
|
+
failed: Number(c.failed) || 0,
|
|
39040
|
+
failing: Array.isArray(c.failing) ? c.failing.filter((x) => typeof x === "string") : []
|
|
39041
|
+
} : void 0;
|
|
39042
|
+
return { items, omitted: Number(h.omitted) || 0, ...checks ? { checks } : {} };
|
|
39043
|
+
}
|
|
38727
39044
|
function readPrWatchCursor(raw2) {
|
|
38728
39045
|
try {
|
|
38729
39046
|
const parsed = raw2 ? JSON.parse(raw2) : null;
|
|
38730
39047
|
if (!parsed || typeof parsed !== "object") return { seen: [] };
|
|
38731
39048
|
const seen = Array.isArray(parsed.seen) ? parsed.seen.filter((x) => typeof x === "string") : [];
|
|
38732
|
-
|
|
39049
|
+
const held = readPrWatchHeld(parsed.held);
|
|
39050
|
+
return { seen, checks: typeof parsed.checks === "string" ? parsed.checks : void 0, report: Number(parsed.report) || 0, ...held ? { held } : {} };
|
|
38733
39051
|
} catch {
|
|
38734
39052
|
return { seen: [] };
|
|
38735
39053
|
}
|
|
@@ -38812,7 +39130,7 @@ ${clock}`;
|
|
|
38812
39130
|
}), `pr-watch ${key} ${st.state}`, nowMs);
|
|
38813
39131
|
continue;
|
|
38814
39132
|
}
|
|
38815
|
-
if (waiting) continue;
|
|
39133
|
+
if (waiting && !cursor.held) continue;
|
|
38816
39134
|
const terminal = st && (st.checks === "passing" || st.checks === "failing") ? st.checks : void 0;
|
|
38817
39135
|
const stamp = terminal !== void 0 ? `${st?.head ?? "?"}:${terminal}:${st?.failureSig ?? ""}` : void 0;
|
|
38818
39136
|
const checksChanged = stamp !== void 0 && cursor.checks !== stamp;
|
|
@@ -38829,37 +39147,42 @@ ${clock}`;
|
|
|
38829
39147
|
return Number.isFinite(landed) && landed > w.created_at;
|
|
38830
39148
|
});
|
|
38831
39149
|
const named = fresh.slice(0, REVIEW_STEER_CAP);
|
|
38832
|
-
const
|
|
38833
|
-
|
|
38834
|
-
|
|
38835
|
-
|
|
38836
|
-
|
|
38837
|
-
|
|
38838
|
-
|
|
38839
|
-
...a.at ? { at: a.at } : {},
|
|
38840
|
-
...a.url ? { url: a.url } : {}
|
|
38841
|
-
}))
|
|
38842
|
-
}) : void 0;
|
|
39150
|
+
const freshItems = [...named].reverse().map((a) => ({
|
|
39151
|
+
label: activityLabel(a),
|
|
39152
|
+
actor: a.actor,
|
|
39153
|
+
bot: isBotGithubActor(a),
|
|
39154
|
+
...a.at ? { at: a.at } : {},
|
|
39155
|
+
...a.url ? { url: a.url } : {}
|
|
39156
|
+
}));
|
|
38843
39157
|
const nextCursor = {
|
|
38844
39158
|
seen: acts ? [.../* @__PURE__ */ new Set([...cursor.seen, ...acts.map((a) => a.id)])].slice(-REVIEW_SEEN_CAP) : cursor.seen,
|
|
38845
39159
|
// The STAMP, so the next poll compares against the commit as well as the colour.
|
|
38846
39160
|
checks: stamp ?? cursor.checks,
|
|
38847
39161
|
report: cursor.report ?? 0
|
|
38848
39162
|
};
|
|
38849
|
-
if (!checksChanged &&
|
|
39163
|
+
if (!checksChanged && fresh.length === 0) {
|
|
38850
39164
|
if (JSON.stringify(nextCursor) !== JSON.stringify(cursor)) {
|
|
38851
39165
|
deps.storage.setPrWatchCursor(w.id, JSON.stringify(nextCursor));
|
|
38852
39166
|
}
|
|
38853
39167
|
continue;
|
|
38854
39168
|
}
|
|
38855
39169
|
deps.storage.setSnoozedUntil(row.slug, null);
|
|
39170
|
+
const held = waiting ? cursor.held : void 0;
|
|
39171
|
+
const items = [...held?.items ?? [], ...freshItems];
|
|
39172
|
+
const carried = {
|
|
39173
|
+
items: items.slice(-REVIEW_STEER_CAP),
|
|
39174
|
+
omitted: (held?.omitted ?? 0) + (fresh.length - named.length) + Math.max(0, items.length - REVIEW_STEER_CAP),
|
|
39175
|
+
...checksChanged && terminal ? { checks: { verdict: terminal, passed: st.passed, failed: st.failed, failing: st.failing } } : held?.checks ? { checks: held.checks } : {}
|
|
39176
|
+
};
|
|
39177
|
+
const review = carried.items.length > 0 ? formatGithubWakeSteer({ ref: key, items: carried.items, omitted: carried.omitted }) : void 0;
|
|
38856
39178
|
const report = nextReport(cursor);
|
|
39179
|
+
if (waiting) outbox.supersede(waiting.id, nowMs, `folded into report ${report}, which says this and everything since`);
|
|
38857
39180
|
enqueuePrWatchWake(row, w.id, report, prWatchWakeMessage({
|
|
38858
39181
|
target: key,
|
|
38859
|
-
...
|
|
38860
|
-
...
|
|
38861
|
-
}), `pr-watch ${key}${
|
|
38862
|
-
deps.storage.setPrWatchCursor(w.id, JSON.stringify({ ...nextCursor, report }));
|
|
39182
|
+
...carried.checks ? { checks: carried.checks } : {},
|
|
39183
|
+
...review ? { review } : {}
|
|
39184
|
+
}), `pr-watch ${key}${carried.checks ? ` CI ${carried.checks.verdict}` : ""}${review ? " review" : ""}`, nowMs);
|
|
39185
|
+
deps.storage.setPrWatchCursor(w.id, JSON.stringify({ ...nextCursor, report, held: carried }));
|
|
38863
39186
|
}
|
|
38864
39187
|
}
|
|
38865
39188
|
function nextReport(cursor) {
|
|
@@ -39085,6 +39408,7 @@ ${clock}`;
|
|
|
39085
39408
|
if (tele.authFault) continue;
|
|
39086
39409
|
if (tele.apiFault) continue;
|
|
39087
39410
|
if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug))) continue;
|
|
39411
|
+
if (answersInFlight(deps.storage.listThreadQuestions(row.slug), tele.lastUserAt, true) !== void 0) continue;
|
|
39088
39412
|
const reminder = outbox.get(wakeDeliveryId(row.slug, row.session_id, signoffFenceId(restedAt)));
|
|
39089
39413
|
if (reminder && reminder.state !== "superseded" && reminder.state !== "exhausted") continue;
|
|
39090
39414
|
const fenceId = stopHookFenceId(armed.armedAt, restedAt);
|
|
@@ -39155,11 +39479,8 @@ ${clock}`;
|
|
|
39155
39479
|
const context = deliveryContext(item);
|
|
39156
39480
|
if (context === "confirmed") {
|
|
39157
39481
|
outbox.confirm(item.id, nowMs);
|
|
39158
|
-
|
|
39159
|
-
|
|
39160
|
-
settleRestPrompt(item);
|
|
39161
|
-
settleCompactPrompt(item);
|
|
39162
|
-
settleTimer(item);
|
|
39482
|
+
settleDelivered(item);
|
|
39483
|
+
confirmFrame(item, nowMs);
|
|
39163
39484
|
continue;
|
|
39164
39485
|
}
|
|
39165
39486
|
if (context === "superseded") {
|
|
@@ -39200,7 +39521,7 @@ ${clock}`;
|
|
|
39200
39521
|
}
|
|
39201
39522
|
continue;
|
|
39202
39523
|
}
|
|
39203
|
-
if (context !== "current-idle") continue;
|
|
39524
|
+
if (context !== "current-idle" && !heldPastBound(item, context, nowMs)) continue;
|
|
39204
39525
|
const recovered = outbox.recoverExpired(
|
|
39205
39526
|
item.id,
|
|
39206
39527
|
nowMs,
|
|
@@ -39215,6 +39536,63 @@ ${clock}`;
|
|
|
39215
39536
|
}
|
|
39216
39537
|
}
|
|
39217
39538
|
}
|
|
39539
|
+
function settleDelivered(item) {
|
|
39540
|
+
settleSnooze(item);
|
|
39541
|
+
settleSchedulePrompt(item);
|
|
39542
|
+
settleRestPrompt(item);
|
|
39543
|
+
settleCompactPrompt(item);
|
|
39544
|
+
settleTimer(item);
|
|
39545
|
+
}
|
|
39546
|
+
function adoptCompanions(carrier, claimedAt) {
|
|
39547
|
+
if (isQuestionAnswerFenceId(carrier.fenceId)) return [];
|
|
39548
|
+
const adopted = [];
|
|
39549
|
+
for (const sibling of outbox.pendingFor(carrier.slug, carrier.sessionId)) {
|
|
39550
|
+
if (sibling.id === carrier.id || isQuestionAnswerFenceId(sibling.fenceId)) continue;
|
|
39551
|
+
const context = deliveryContext(sibling);
|
|
39552
|
+
if (context === "confirmed") {
|
|
39553
|
+
outbox.confirm(sibling.id, now());
|
|
39554
|
+
settleDelivered(sibling);
|
|
39555
|
+
continue;
|
|
39556
|
+
}
|
|
39557
|
+
if (context === "superseded") {
|
|
39558
|
+
outbox.supersede(sibling.id, now(), "the exact awaiting fence or session was superseded before delivery");
|
|
39559
|
+
settleSnooze(sibling);
|
|
39560
|
+
continue;
|
|
39561
|
+
}
|
|
39562
|
+
if (!isDeliverableNow(sibling, context, claimedAt)) continue;
|
|
39563
|
+
const leased = outbox.adopt(sibling.id, deliveryOwner, claimedAt, claimedAt + deliveryLeaseMs, maxDeliveryAttempts);
|
|
39564
|
+
if (leased) adopted.push(leased);
|
|
39565
|
+
}
|
|
39566
|
+
return adopted;
|
|
39567
|
+
}
|
|
39568
|
+
function mergedWakeMessage(frame, spokeAt) {
|
|
39569
|
+
const sections = [];
|
|
39570
|
+
for (const part of frame) {
|
|
39571
|
+
const body2 = stripWakeTimeHeader(part.message).trim();
|
|
39572
|
+
const same = sections.find((s) => s.reason === part.reason && s.body === body2);
|
|
39573
|
+
if (same) same.count++;
|
|
39574
|
+
else sections.push({ reason: part.reason, body: body2, count: 1 });
|
|
39575
|
+
}
|
|
39576
|
+
const lead = `Frizz held ${frame.length} wakes for this thread and is delivering them together, oldest first. Each is under its own heading; read all of them before acting on any.`;
|
|
39577
|
+
const body = sections.map((s, i) => `### ${i + 1}. ${s.reason}${s.count > 1 ? ` \u2014 ${s.count} identical events` : ""}
|
|
39578
|
+
|
|
39579
|
+
${s.body}`);
|
|
39580
|
+
return `${lead}
|
|
39581
|
+
|
|
39582
|
+
${body.join("\n\n")}
|
|
39583
|
+
|
|
39584
|
+
${wakeTimeHeader(now(), spokeAt)}`;
|
|
39585
|
+
}
|
|
39586
|
+
function confirmFrame(carrier, nowMs) {
|
|
39587
|
+
if (carrier.sentAt === null) return;
|
|
39588
|
+
for (const d of outbox.listOpen()) {
|
|
39589
|
+
if (d.id === carrier.id || d.slug !== carrier.slug || d.sessionId !== carrier.sessionId) continue;
|
|
39590
|
+
if (d.state !== "leased" || d.sentAt !== carrier.sentAt) continue;
|
|
39591
|
+
if (!outbox.confirm(d.id, nowMs)) continue;
|
|
39592
|
+
settleDelivered(d);
|
|
39593
|
+
log3(`waker: delivered ${d.slug} \u2014 ${d.reason} (in the same frame as: ${carrier.reason})`);
|
|
39594
|
+
}
|
|
39595
|
+
}
|
|
39218
39596
|
async function deliverDue() {
|
|
39219
39597
|
for (let delivered = 0; delivered < deliveryBatchSize; delivered++) {
|
|
39220
39598
|
const claimedAt = now();
|
|
@@ -39224,11 +39602,7 @@ ${clock}`;
|
|
|
39224
39602
|
const context = deliveryContext(item);
|
|
39225
39603
|
if (context === "confirmed") {
|
|
39226
39604
|
outbox.confirm(item.id, now());
|
|
39227
|
-
|
|
39228
|
-
settleSchedulePrompt(item);
|
|
39229
|
-
settleRestPrompt(item);
|
|
39230
|
-
settleCompactPrompt(item);
|
|
39231
|
-
settleTimer(item);
|
|
39605
|
+
settleDelivered(item);
|
|
39232
39606
|
continue;
|
|
39233
39607
|
}
|
|
39234
39608
|
if (context === "superseded") {
|
|
@@ -39236,44 +39610,55 @@ ${clock}`;
|
|
|
39236
39610
|
settleSnooze(item);
|
|
39237
39611
|
continue;
|
|
39238
39612
|
}
|
|
39239
|
-
if (!isDeliverableNow(item, context)) {
|
|
39613
|
+
if (!isDeliverableNow(item, context, claimedAt)) {
|
|
39240
39614
|
const deferredAt = now();
|
|
39241
39615
|
outbox.deferFailure(
|
|
39242
39616
|
item.id,
|
|
39243
39617
|
deliveryOwner,
|
|
39244
39618
|
deferredAt,
|
|
39245
39619
|
deferredAt + Math.max(deliveryLeaseMs, retryDelay(item.attempts)),
|
|
39246
|
-
|
|
39620
|
+
WAKE_HOLD_DEFERRAL
|
|
39247
39621
|
);
|
|
39248
39622
|
continue;
|
|
39249
39623
|
}
|
|
39624
|
+
const companions = adoptCompanions(item, claimedAt);
|
|
39625
|
+
const frame = [...companions, item].sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
|
39626
|
+
const message = companions.length === 0 ? item.message : mergedWakeMessage(frame, deps.tailer.get(item.slug)?.lastAssistantAt);
|
|
39627
|
+
if (companions.length > 0) {
|
|
39628
|
+
log3(`waker: merged ${frame.length} wakes for ${item.slug} into one delivery \u2014 ${frame.map((d) => d.reason).join("; ")}`);
|
|
39629
|
+
}
|
|
39250
39630
|
try {
|
|
39251
|
-
await deps.resume(item.slug,
|
|
39631
|
+
await deps.resume(item.slug, message, item.id);
|
|
39252
39632
|
} catch (error) {
|
|
39253
|
-
const
|
|
39633
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
39254
39634
|
const failedAt = now();
|
|
39255
39635
|
if (error?.terminalDelivery === true) {
|
|
39256
|
-
|
|
39257
|
-
|
|
39258
|
-
|
|
39259
|
-
|
|
39636
|
+
for (const d of frame) {
|
|
39637
|
+
outbox.supersede(d.id, failedAt, message2);
|
|
39638
|
+
settleSnooze(d);
|
|
39639
|
+
settleTimer(d);
|
|
39640
|
+
}
|
|
39641
|
+
log3(`waker: delivery ABANDONED for ${item.slug} (terminal, no retry): ${message2}`);
|
|
39260
39642
|
continue;
|
|
39261
39643
|
}
|
|
39262
|
-
|
|
39263
|
-
|
|
39264
|
-
|
|
39265
|
-
|
|
39266
|
-
|
|
39267
|
-
|
|
39268
|
-
|
|
39269
|
-
|
|
39644
|
+
for (const d of frame) {
|
|
39645
|
+
outbox.deferFailure(
|
|
39646
|
+
d.id,
|
|
39647
|
+
deliveryOwner,
|
|
39648
|
+
failedAt,
|
|
39649
|
+
failedAt + Math.max(deliveryLeaseMs, retryDelay(d.attempts)),
|
|
39650
|
+
message2
|
|
39651
|
+
);
|
|
39652
|
+
}
|
|
39653
|
+
log3(`waker: delivery FAILED for ${item.slug} (attempt ${item.attempts} of ${maxDeliveryAttempts}): ${message2}`);
|
|
39270
39654
|
continue;
|
|
39271
39655
|
}
|
|
39272
39656
|
const runtime = deps.wakeRuntimeState?.(item.slug, item.sessionId) ?? "unknown";
|
|
39273
39657
|
if (runtime !== "unknown") {
|
|
39274
39658
|
const sentAt = now();
|
|
39275
39659
|
if (outbox.markSent(item.id, deliveryOwner, sentAt, sentAt + confirmGraceMs)) {
|
|
39276
|
-
|
|
39660
|
+
for (const d of companions) outbox.markSent(d.id, deliveryOwner, sentAt, sentAt + confirmGraceMs);
|
|
39661
|
+
for (const d of frame) settleSignoffNudge(d);
|
|
39277
39662
|
log3(`waker: sent ${item.slug} \u2014 ${item.reason}; confirming within ${Math.round(confirmGraceMs / 1e3)}s`);
|
|
39278
39663
|
checkpoint("after-delivery", item);
|
|
39279
39664
|
continue;
|
|
@@ -39282,16 +39667,15 @@ ${clock}`;
|
|
|
39282
39667
|
const retried = item.attempts > 1 ? ` (on attempt ${item.attempts})` : "";
|
|
39283
39668
|
log3(`waker: delivered ${item.slug} \u2014 ${item.reason}${retried}`);
|
|
39284
39669
|
checkpoint("after-delivery", item);
|
|
39285
|
-
|
|
39670
|
+
const acked = frame.filter((d) => outbox.acknowledge(d.id, deliveryOwner, now()));
|
|
39671
|
+
if (!acked.includes(item)) {
|
|
39286
39672
|
log3(`waker: delivery acknowledgement lost ownership for ${item.slug}; preserving the authoritative terminal state`);
|
|
39287
|
-
continue;
|
|
39288
39673
|
}
|
|
39289
|
-
|
|
39290
|
-
|
|
39291
|
-
|
|
39292
|
-
|
|
39293
|
-
|
|
39294
|
-
settleSignoffNudge(item);
|
|
39674
|
+
for (const d of acked) {
|
|
39675
|
+
settleDelivered(d);
|
|
39676
|
+
settleSignoffNudge(d);
|
|
39677
|
+
}
|
|
39678
|
+
if (!acked.includes(item)) continue;
|
|
39295
39679
|
const acknowledged = outbox.get(item.id);
|
|
39296
39680
|
if (acknowledged) checkpoint("after-ack", acknowledged);
|
|
39297
39681
|
}
|
|
@@ -39412,7 +39796,7 @@ ${clock}`;
|
|
|
39412
39796
|
}
|
|
39413
39797
|
};
|
|
39414
39798
|
}
|
|
39415
|
-
var execFileAsync4, LIMIT_FENCE_PREFIX, LIMIT_HINT_PREFIX, LIMIT_RESUME_GRACE_MS, LIMIT_RESUME_HEADROOM_PERCENT, LIMIT_HEADROOM_MIN_FAULT_AGE_MS, REPORT_FENCE_PREFIX, REPORT_HINT_PREFIX, SNOOZE_FENCE_PREFIX, SNOOZE_HINT_PREFIX, HEARTBEAT_FENCE_PREFIX, HEARTBEAT_HINT_PREFIX, STOP_HOOK_FENCE_PREFIX, STOP_HOOK_HINT_KEY, COMPACT_FENCE_PREFIX, COMPACT_HINT_KEY, TIMER_FENCE_PREFIX, TIMER_HINT_PREFIX, SHELL_FENCE_PREFIX, PR_WATCH_FENCE_PREFIX, PR_WATCH_EXPIRED_FENCE_PREFIX, OWN_WATCH_EXPIRED_FENCE_PREFIX, QUESTION_ANSWER_FENCE_PREFIX, PR_WATCH_POLL_MS, SIGNOFF_FENCE_PREFIX, SIGNOFF_HINT_KEY, SIGNOFF_NUDGE_MAX, PARK_BUMP_MAX, SIGNOFF_NUDGE_SETTING, PARK_FENCE_PREFIX, GITHUB_STATUS_CAP, REVIEW_SEEN_CAP, REVIEW_STEER_CAP, AWAITING_KEY_OF;
|
|
39799
|
+
var execFileAsync4, LIMIT_FENCE_PREFIX, LIMIT_HINT_PREFIX, LIMIT_RESUME_GRACE_MS, LIMIT_RESUME_HEADROOM_PERCENT, LIMIT_HEADROOM_MIN_FAULT_AGE_MS, REPORT_FENCE_PREFIX, REPORT_HINT_PREFIX, SNOOZE_FENCE_PREFIX, SNOOZE_HINT_PREFIX, HEARTBEAT_FENCE_PREFIX, HEARTBEAT_HINT_PREFIX, STOP_HOOK_FENCE_PREFIX, STOP_HOOK_HINT_KEY, COMPACT_FENCE_PREFIX, COMPACT_HINT_KEY, TIMER_FENCE_PREFIX, TIMER_HINT_PREFIX, SHELL_FENCE_PREFIX, PR_WATCH_FENCE_PREFIX, PR_WATCH_EXPIRED_FENCE_PREFIX, OWN_WATCH_EXPIRED_FENCE_PREFIX, QUESTION_ANSWER_FENCE_PREFIX, PR_WATCH_POLL_MS, SIGNOFF_FENCE_PREFIX, SIGNOFF_HINT_KEY, SIGNOFF_NUDGE_MAX, PARK_BUMP_MAX, SIGNOFF_NUDGE_SETTING, PARK_FENCE_PREFIX, GITHUB_STATUS_CAP, REVIEW_SEEN_CAP, REVIEW_STEER_CAP, AWAITING_KEY_OF, MID_TURN_HOLD_MAX_MS, WAKE_HOLD_DEFERRAL;
|
|
39416
39800
|
var init_scheduler = __esm({
|
|
39417
39801
|
"packages/server/src/scheduler.ts"() {
|
|
39418
39802
|
"use strict";
|
|
@@ -39467,6 +39851,8 @@ var init_scheduler = __esm({
|
|
|
39467
39851
|
timer: "timers",
|
|
39468
39852
|
pr: "prs"
|
|
39469
39853
|
};
|
|
39854
|
+
MID_TURN_HOLD_MAX_MS = 10 * 6e4;
|
|
39855
|
+
WAKE_HOLD_DEFERRAL = "delivery deferred until exact awaiting telemetry is idle and available";
|
|
39470
39856
|
}
|
|
39471
39857
|
});
|
|
39472
39858
|
|
|
@@ -39546,7 +39932,7 @@ var init_resume = __esm({
|
|
|
39546
39932
|
});
|
|
39547
39933
|
|
|
39548
39934
|
// packages/server/src/backend/claude.ts
|
|
39549
|
-
import { join as
|
|
39935
|
+
import { join as join30 } from "node:path";
|
|
39550
39936
|
function toolResultText2(content) {
|
|
39551
39937
|
if (typeof content === "string") return content;
|
|
39552
39938
|
if (!Array.isArray(content)) return "";
|
|
@@ -39615,7 +40001,8 @@ function createClaudeBackend(opts) {
|
|
|
39615
40001
|
pluginDir: workerPluginDir(),
|
|
39616
40002
|
workerPrompt: o.workerContract,
|
|
39617
40003
|
extraSystemPrompt: o.extraSystemPrompt,
|
|
39618
|
-
frizzMcp: o.frizzMcp
|
|
40004
|
+
frizzMcp: o.frizzMcp,
|
|
40005
|
+
projectMcpServers: (opts.resolveProjectMcpServers ?? projectMcpServers)(o.cwd)
|
|
39619
40006
|
});
|
|
39620
40007
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
39621
40008
|
},
|
|
@@ -39630,12 +40017,13 @@ function createClaudeBackend(opts) {
|
|
|
39630
40017
|
pluginDir: workerPluginDir(),
|
|
39631
40018
|
workerPrompt: o.workerContract,
|
|
39632
40019
|
extraSystemPrompt: o.extraSystemPrompt,
|
|
39633
|
-
frizzMcp: o.frizzMcp
|
|
40020
|
+
frizzMcp: o.frizzMcp,
|
|
40021
|
+
projectMcpServers: (opts.resolveProjectMcpServers ?? projectMcpServers)(o.cwd)
|
|
39634
40022
|
});
|
|
39635
40023
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
39636
40024
|
},
|
|
39637
40025
|
transcriptPath(sessionId) {
|
|
39638
|
-
return
|
|
40026
|
+
return join30(opts.logDir, `${sessionId}.jsonl`);
|
|
39639
40027
|
},
|
|
39640
40028
|
parseLine(line) {
|
|
39641
40029
|
return parseClaudeLine(line);
|
|
@@ -39653,6 +40041,7 @@ function createClaudeBackend(opts) {
|
|
|
39653
40041
|
var init_claude = __esm({
|
|
39654
40042
|
"packages/server/src/backend/claude.ts"() {
|
|
39655
40043
|
"use strict";
|
|
40044
|
+
init_project_mcp_servers();
|
|
39656
40045
|
init_dispatch();
|
|
39657
40046
|
init_tailer();
|
|
39658
40047
|
}
|
|
@@ -39805,12 +40194,12 @@ var init_login_utility = __esm({
|
|
|
39805
40194
|
|
|
39806
40195
|
// packages/server/src/backend/codex-app-server-diagnostics.ts
|
|
39807
40196
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as statSync13 } from "node:fs";
|
|
39808
|
-
import { join as
|
|
40197
|
+
import { join as join31 } from "node:path";
|
|
39809
40198
|
function codexDiagnosticLogPath(stateDir, projectId) {
|
|
39810
|
-
return
|
|
40199
|
+
return join31(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
|
|
39811
40200
|
}
|
|
39812
40201
|
function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
|
|
39813
|
-
const dir =
|
|
40202
|
+
const dir = join31(stateDir, "codex-app-server");
|
|
39814
40203
|
const path = codexDiagnosticLogPath(stateDir, projectId);
|
|
39815
40204
|
let ensured = false;
|
|
39816
40205
|
return (event) => {
|
|
@@ -39842,7 +40231,7 @@ var init_codex_app_server_diagnostics = __esm({
|
|
|
39842
40231
|
import { execFile as execFile7 } from "node:child_process";
|
|
39843
40232
|
import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
|
|
39844
40233
|
import { connect as connectSocket } from "node:net";
|
|
39845
|
-
import { join as
|
|
40234
|
+
import { join as join32 } from "node:path";
|
|
39846
40235
|
function parseLsofSocketNames(stdout) {
|
|
39847
40236
|
const referenced = /* @__PURE__ */ new Set();
|
|
39848
40237
|
for (const line of stdout.split("\n")) {
|
|
@@ -39859,7 +40248,7 @@ function sweepStaleSockets(options, deps = {}) {
|
|
|
39859
40248
|
const keep = new Set(options.keep ?? []);
|
|
39860
40249
|
let candidates;
|
|
39861
40250
|
try {
|
|
39862
|
-
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) =>
|
|
40251
|
+
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join32(options.dir, name)).filter((path) => !keep.has(path));
|
|
39863
40252
|
} catch {
|
|
39864
40253
|
return;
|
|
39865
40254
|
}
|
|
@@ -39909,7 +40298,7 @@ var init_stale_socket_sweep = __esm({
|
|
|
39909
40298
|
|
|
39910
40299
|
// packages/server/src/orphan-reaper.ts
|
|
39911
40300
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
39912
|
-
import { readFileSync as
|
|
40301
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
39913
40302
|
import { dirname as dirname12 } from "node:path";
|
|
39914
40303
|
function firstTokenBasename(command) {
|
|
39915
40304
|
const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
|
|
@@ -40176,7 +40565,7 @@ var init_orphan_reaper = __esm({
|
|
|
40176
40565
|
defaultExec = (file, args) => execFileSync5(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
|
|
40177
40566
|
defaultReadEnv = (pid) => {
|
|
40178
40567
|
try {
|
|
40179
|
-
return
|
|
40568
|
+
return readFileSync19(`/proc/${pid}/environ`, "utf8");
|
|
40180
40569
|
} catch {
|
|
40181
40570
|
return null;
|
|
40182
40571
|
}
|
|
@@ -40323,9 +40712,9 @@ var init_thread_hibernation = __esm({
|
|
|
40323
40712
|
});
|
|
40324
40713
|
|
|
40325
40714
|
// packages/server/src/context.ts
|
|
40326
|
-
import { join as
|
|
40715
|
+
import { join as join33 } from "node:path";
|
|
40327
40716
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
40328
|
-
import { homedir as
|
|
40717
|
+
import { homedir as homedir14 } from "node:os";
|
|
40329
40718
|
function reconcileSessions(storage) {
|
|
40330
40719
|
for (const row of storage.allSessions()) {
|
|
40331
40720
|
if (isHeadlessRow(row)) continue;
|
|
@@ -40471,9 +40860,9 @@ async function createContext(opts = {}) {
|
|
|
40471
40860
|
}
|
|
40472
40861
|
}
|
|
40473
40862
|
function createContextUnchecked(opts, resources) {
|
|
40474
|
-
const home = opts.home ??
|
|
40863
|
+
const home = opts.home ?? homedir14();
|
|
40475
40864
|
const project = opts.project ?? resolveProject();
|
|
40476
|
-
const storage = createStorage(opts.database ??
|
|
40865
|
+
const storage = createStorage(opts.database ?? join33(project.stateDir, "ui.db"), project.id);
|
|
40477
40866
|
resources.storage = storage;
|
|
40478
40867
|
const bus = new Bus();
|
|
40479
40868
|
const transcriptChange = new Emitter();
|
|
@@ -40622,7 +41011,8 @@ function createContextUnchecked(opts, resources) {
|
|
|
40622
41011
|
// The frizz worker environment — the SDK equivalent of the CLI argv's --plugin-dir / --mcp-config.
|
|
40623
41012
|
// Computed ONCE here (constant per project) and applied on every broker fork so a broker worker
|
|
40624
41013
|
// gets the frizz sub-agent profiles, the frizz MCP server (the only one frizz mounts), and the
|
|
40625
|
-
// cc-worker hooks.
|
|
41014
|
+
// cc-worker hooks. The project's own `.mcp.json` servers are merged in by the broker daemon at each
|
|
41015
|
+
// fork (project-mcp-servers.ts): under `--strict-mcp-config` nothing mounts that frizz did not hand over.
|
|
40626
41016
|
workerEnv: {
|
|
40627
41017
|
pluginDir: workerPluginDir(),
|
|
40628
41018
|
...claudeMcpConfig(resolveFrizzMcp(frizzMcpTarget)),
|
|
@@ -40765,6 +41155,8 @@ ${wakeDeliveryToken(deliveryId)}`;
|
|
|
40765
41155
|
Date.now(),
|
|
40766
41156
|
(tailer.get(slug)?.subAgents ?? []).some((agent) => agent.state === "running")
|
|
40767
41157
|
)
|
|
41158
|
+
}).then(() => {
|
|
41159
|
+
if (row.exited === 1) storage.setExitedIfCurrent(slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
40768
41160
|
});
|
|
40769
41161
|
}
|
|
40770
41162
|
resumeThread(
|
|
@@ -43523,7 +43915,7 @@ var init_server = __esm({
|
|
|
43523
43915
|
});
|
|
43524
43916
|
|
|
43525
43917
|
// packages/server/src/repair.ts
|
|
43526
|
-
import { existsSync as existsSync11, readFileSync as
|
|
43918
|
+
import { existsSync as existsSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "node:fs";
|
|
43527
43919
|
import { basename as basename7, dirname as dirname13, resolve as resolve7 } from "node:path";
|
|
43528
43920
|
function quoteValue(v) {
|
|
43529
43921
|
if (/^[\w./#:+-]+$/.test(v)) return v;
|
|
@@ -43539,7 +43931,7 @@ function repairThreadFile(frizzDir, file) {
|
|
|
43539
43931
|
if (dirname13(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
|
|
43540
43932
|
if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
|
|
43541
43933
|
if (!existsSync11(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
|
|
43542
|
-
const content =
|
|
43934
|
+
const content = readFileSync20(abs, "utf8");
|
|
43543
43935
|
const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
|
|
43544
43936
|
if (firstLine2?.trim() === "---") {
|
|
43545
43937
|
throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
|
|
@@ -43617,7 +44009,7 @@ var init_edited_files = __esm({
|
|
|
43617
44009
|
});
|
|
43618
44010
|
|
|
43619
44011
|
// cc-worker/hooks/bash-background.mjs
|
|
43620
|
-
import { readFileSync as
|
|
44012
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
43621
44013
|
import { basename as basename8 } from "node:path";
|
|
43622
44014
|
import { pathToFileURL } from "node:url";
|
|
43623
44015
|
function emit(obj) {
|
|
@@ -43766,7 +44158,7 @@ var init_bash_background = __esm({
|
|
|
43766
44158
|
if (isDirectHookExecution(process.argv[1], import.meta.url)) {
|
|
43767
44159
|
try {
|
|
43768
44160
|
const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
|
|
43769
|
-
emit(evaluateBashBackgroundHook(JSON.parse(
|
|
44161
|
+
emit(evaluateBashBackgroundHook(JSON.parse(readFileSync21(0, "utf8")), env));
|
|
43770
44162
|
} catch {
|
|
43771
44163
|
emit({});
|
|
43772
44164
|
}
|
|
@@ -43775,11 +44167,11 @@ var init_bash_background = __esm({
|
|
|
43775
44167
|
});
|
|
43776
44168
|
|
|
43777
44169
|
// packages/server/src/transcript.ts
|
|
43778
|
-
import { closeSync as closeSync13, existsSync as existsSync12, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as
|
|
44170
|
+
import { closeSync as closeSync13, existsSync as existsSync12, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync22, readSync as readSync7, renameSync as renameSync9, statSync as statSync14, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
|
|
43779
44171
|
import { createHash as createHash13 } from "node:crypto";
|
|
43780
44172
|
import { StringDecoder as StringDecoder4 } from "node:string_decoder";
|
|
43781
|
-
import { join as
|
|
43782
|
-
import { homedir as
|
|
44173
|
+
import { join as join34 } from "node:path";
|
|
44174
|
+
import { homedir as homedir15 } from "node:os";
|
|
43783
44175
|
function formatTokens(n) {
|
|
43784
44176
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
43785
44177
|
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
@@ -44329,18 +44721,18 @@ function looksLikeImage(buf, ext) {
|
|
|
44329
44721
|
}
|
|
44330
44722
|
function pruneScreenshotCache() {
|
|
44331
44723
|
try {
|
|
44332
|
-
const
|
|
44333
|
-
if (
|
|
44334
|
-
const byMtime =
|
|
44724
|
+
const entries2 = readdirSync9(SCREENSHOT_CACHE_DIR).filter((n) => !n.startsWith("."));
|
|
44725
|
+
if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
|
|
44726
|
+
const byMtime = entries2.map((n) => {
|
|
44335
44727
|
try {
|
|
44336
|
-
return { n, m: statSync14(
|
|
44728
|
+
return { n, m: statSync14(join34(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
|
|
44337
44729
|
} catch {
|
|
44338
44730
|
return { n, m: 0 };
|
|
44339
44731
|
}
|
|
44340
44732
|
}).sort((a, b) => b.m - a.m);
|
|
44341
44733
|
for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
|
|
44342
44734
|
try {
|
|
44343
|
-
unlinkSync5(
|
|
44735
|
+
unlinkSync5(join34(SCREENSHOT_CACHE_DIR, n));
|
|
44344
44736
|
} catch {
|
|
44345
44737
|
}
|
|
44346
44738
|
}
|
|
@@ -44370,14 +44762,14 @@ function persistBase64Image(mediaType, data, idKey) {
|
|
|
44370
44762
|
const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
|
|
44371
44763
|
if (!ext || !data) return void 0;
|
|
44372
44764
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
44373
|
-
const path =
|
|
44765
|
+
const path = join34(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
|
|
44374
44766
|
try {
|
|
44375
44767
|
if (existsSync12(path)) return path;
|
|
44376
44768
|
if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
|
|
44377
44769
|
const buf = Buffer.from(data, "base64");
|
|
44378
44770
|
if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
|
|
44379
44771
|
mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
44380
|
-
const tmp =
|
|
44772
|
+
const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
44381
44773
|
writeFileSync12(tmp, buf);
|
|
44382
44774
|
renameSync9(tmp, path);
|
|
44383
44775
|
pruneScreenshotCache();
|
|
@@ -44392,14 +44784,14 @@ function persistSentFile(srcPath, idKey) {
|
|
|
44392
44784
|
const outExt = ext === "jpeg" ? "jpg" : ext;
|
|
44393
44785
|
try {
|
|
44394
44786
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
44395
|
-
const dest =
|
|
44787
|
+
const dest = join34(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
|
|
44396
44788
|
if (existsSync12(dest)) return dest;
|
|
44397
44789
|
const size = statSync14(srcPath).size;
|
|
44398
44790
|
if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
|
|
44399
|
-
const buf =
|
|
44791
|
+
const buf = readFileSync22(srcPath);
|
|
44400
44792
|
if (!looksLikeImage(buf, outExt)) return void 0;
|
|
44401
44793
|
mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
44402
|
-
const tmp =
|
|
44794
|
+
const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
44403
44795
|
writeFileSync12(tmp, buf);
|
|
44404
44796
|
renameSync9(tmp, dest);
|
|
44405
44797
|
pruneScreenshotCache();
|
|
@@ -44837,7 +45229,7 @@ function readTranscript(project, sessionId) {
|
|
|
44837
45229
|
}
|
|
44838
45230
|
function verifyIncrementalParse(path, identityPrefix, incremental) {
|
|
44839
45231
|
try {
|
|
44840
|
-
const fresh = parseTranscript(
|
|
45232
|
+
const fresh = parseTranscript(readFileSync22(path, "utf8"), identityPrefix);
|
|
44841
45233
|
const a = JSON.stringify(incremental);
|
|
44842
45234
|
const b = JSON.stringify(fresh);
|
|
44843
45235
|
if (a !== b) {
|
|
@@ -44857,16 +45249,16 @@ function verifyIncrementalParse(path, identityPrefix, incremental) {
|
|
|
44857
45249
|
}
|
|
44858
45250
|
}
|
|
44859
45251
|
function logDirOf(project) {
|
|
44860
|
-
return
|
|
45252
|
+
return join34(homedir15(), ".claude", "projects", project.cwdSlug);
|
|
44861
45253
|
}
|
|
44862
45254
|
function resolveTranscriptPath(project, sessionId) {
|
|
44863
|
-
const path =
|
|
45255
|
+
const path = join34(logDirOf(project), `${sessionId}.jsonl`);
|
|
44864
45256
|
try {
|
|
44865
45257
|
if (statSync14(path).size > 0) return path;
|
|
44866
45258
|
} catch {
|
|
44867
45259
|
}
|
|
44868
45260
|
const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
|
|
44869
|
-
return stranded ?
|
|
45261
|
+
return stranded ? join34(stranded, `${sessionId}.jsonl`) : path;
|
|
44870
45262
|
}
|
|
44871
45263
|
function projectCodexTranscript(raw2, identityPrefix = "codex") {
|
|
44872
45264
|
const out = [];
|
|
@@ -45395,10 +45787,10 @@ function wrappedInvocations(source) {
|
|
|
45395
45787
|
let open = nameStart + name.length;
|
|
45396
45788
|
while (/\s/.test(source[open] ?? "")) open++;
|
|
45397
45789
|
if (source[open] !== "(") continue;
|
|
45398
|
-
const
|
|
45399
|
-
if (
|
|
45400
|
-
out.push({ name, args: source.slice(open + 1,
|
|
45401
|
-
i =
|
|
45790
|
+
const close2 = matchingParen(source, open);
|
|
45791
|
+
if (close2 === -1) break;
|
|
45792
|
+
out.push({ name, args: source.slice(open + 1, close2).trim() });
|
|
45793
|
+
i = close2;
|
|
45402
45794
|
}
|
|
45403
45795
|
return out;
|
|
45404
45796
|
}
|
|
@@ -46074,7 +46466,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
|
|
|
46074
46466
|
}
|
|
46075
46467
|
function readCodexTranscriptFile(absPath, nativeId = absPath) {
|
|
46076
46468
|
try {
|
|
46077
|
-
return parseCodexTranscript(
|
|
46469
|
+
return parseCodexTranscript(readFileSync22(absPath, "utf8"), `codex:${nativeId}`);
|
|
46078
46470
|
} catch {
|
|
46079
46471
|
return [];
|
|
46080
46472
|
}
|
|
@@ -46172,7 +46564,7 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
|
|
|
46172
46564
|
function readTranscriptFile(absPath) {
|
|
46173
46565
|
try {
|
|
46174
46566
|
const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
|
|
46175
|
-
return parseTranscript(
|
|
46567
|
+
return parseTranscript(readFileSync22(absPath, "utf8"), `claude-file:${pathKey}`);
|
|
46176
46568
|
} catch {
|
|
46177
46569
|
return [];
|
|
46178
46570
|
}
|
|
@@ -46271,9 +46663,9 @@ var init_open_external = __esm({
|
|
|
46271
46663
|
|
|
46272
46664
|
// packages/server/src/local-file.ts
|
|
46273
46665
|
import { spawn as spawn6 } from "node:child_process";
|
|
46274
|
-
import { readFileSync as
|
|
46275
|
-
import { homedir as
|
|
46276
|
-
import { isAbsolute as isAbsolute4, join as
|
|
46666
|
+
import { readFileSync as readFileSync23, realpathSync as realpathSync8, statSync as statSync15 } from "node:fs";
|
|
46667
|
+
import { homedir as homedir16 } from "node:os";
|
|
46668
|
+
import { isAbsolute as isAbsolute4, join as join35, resolve as resolve8, sep as sep2 } from "node:path";
|
|
46277
46669
|
function isUnder(real, root) {
|
|
46278
46670
|
let rootReal;
|
|
46279
46671
|
try {
|
|
@@ -46300,10 +46692,10 @@ function resolveLocalFile(rawPath, roots) {
|
|
|
46300
46692
|
}
|
|
46301
46693
|
return real;
|
|
46302
46694
|
}
|
|
46303
|
-
function resolveOpenableFile(raw2, projectDir, roots, home =
|
|
46695
|
+
function resolveOpenableFile(raw2, projectDir, roots, home = homedir16()) {
|
|
46304
46696
|
const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
|
|
46305
46697
|
if (!trimmed) return null;
|
|
46306
|
-
const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ?
|
|
46698
|
+
const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join35(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve8(projectDir, trimmed);
|
|
46307
46699
|
try {
|
|
46308
46700
|
return resolveLocalFile(abs, roots);
|
|
46309
46701
|
} catch {
|
|
@@ -46312,7 +46704,7 @@ function resolveOpenableFile(raw2, projectDir, roots, home = homedir15()) {
|
|
|
46312
46704
|
}
|
|
46313
46705
|
function readLocalTextFile(rawPath, roots) {
|
|
46314
46706
|
const path = resolveLocalFile(rawPath, roots);
|
|
46315
|
-
const bytes =
|
|
46707
|
+
const bytes = readFileSync23(path);
|
|
46316
46708
|
if (bytes.subarray(0, 8192).includes(0)) throw new Error("Local file is not a text file");
|
|
46317
46709
|
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path, text: bytes.toString("utf8"), truncated: false };
|
|
46318
46710
|
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
@@ -46323,12 +46715,19 @@ function readLocalMarkdown(rawPath, roots) {
|
|
|
46323
46715
|
if (!MARKDOWN_FILE_EXT.test(rawPath.trim())) throw new Error("Local file is not a Markdown file");
|
|
46324
46716
|
const path = resolveLocalFile(rawPath, roots);
|
|
46325
46717
|
if (!MARKDOWN_FILE_EXT.test(path)) throw new Error("Local file is not a Markdown file");
|
|
46326
|
-
const bytes =
|
|
46718
|
+
const bytes = readFileSync23(path);
|
|
46327
46719
|
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path, markdown: bytes.toString("utf8"), truncated: false };
|
|
46328
46720
|
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
46329
46721
|
const lastBreak = head.lastIndexOf(10);
|
|
46330
46722
|
return { path, markdown: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
|
|
46331
46723
|
}
|
|
46724
|
+
function resolveWatchableLocalFile(rawPath, roots) {
|
|
46725
|
+
const path = resolveLocalFile(rawPath, roots);
|
|
46726
|
+
if (MARKDOWN_FILE_EXT.test(rawPath.trim()) && !MARKDOWN_FILE_EXT.test(path)) {
|
|
46727
|
+
throw new Error("Local file is not a Markdown file");
|
|
46728
|
+
}
|
|
46729
|
+
return path;
|
|
46730
|
+
}
|
|
46332
46731
|
function defaultSpawn(command, args, options) {
|
|
46333
46732
|
return spawn6(command, [...args], options);
|
|
46334
46733
|
}
|
|
@@ -47022,12 +47421,12 @@ var init_directory_picker = __esm({
|
|
|
47022
47421
|
});
|
|
47023
47422
|
|
|
47024
47423
|
// packages/server/src/router.ts
|
|
47025
|
-
import { readFileSync as
|
|
47026
|
-
import { join as
|
|
47424
|
+
import { readFileSync as readFileSync24, statSync as statSync17 } from "node:fs";
|
|
47425
|
+
import { join as join36, resolve as resolve9 } from "node:path";
|
|
47027
47426
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
47028
47427
|
import { basename as basename9, dirname as dirname14 } from "node:path";
|
|
47029
47428
|
import { existsSync as existsSync13, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
|
|
47030
|
-
import { homedir as
|
|
47429
|
+
import { homedir as homedir17 } from "node:os";
|
|
47031
47430
|
function validateGithubDispatchProfile(input) {
|
|
47032
47431
|
validateThreadProfile(input.backend, input.model, input.effort);
|
|
47033
47432
|
}
|
|
@@ -47122,6 +47521,10 @@ function holdOps(ops) {
|
|
|
47122
47521
|
state: op.state
|
|
47123
47522
|
}));
|
|
47124
47523
|
}
|
|
47524
|
+
function cutOffHold(telemetry) {
|
|
47525
|
+
if (telemetry?.turn !== "in-flight") return void 0;
|
|
47526
|
+
return { turnInFlight: true, cutOff: true, unobservable: false, subAgents: [], subAgentCount: 0, bgShells: [], bgShellCount: 0 };
|
|
47527
|
+
}
|
|
47125
47528
|
async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker) {
|
|
47126
47529
|
const binding = adoptionRuntimeBinding(storage, row);
|
|
47127
47530
|
if (binding.kind === "conflict") {
|
|
@@ -47130,7 +47533,7 @@ async function completeRegisteredThread(storage, row, terminateLive, runtime = c
|
|
|
47130
47533
|
const appServerCodex = isAppServerCodexRow(row);
|
|
47131
47534
|
const brokerClaude = isBrokerClaudeRow(row);
|
|
47132
47535
|
const live = brokerClaude ? claudeBroker?.isDaemonAlive(row.session_id) ?? false : appServerCodex ? appServerCodexTurnLive(codex, row) : false;
|
|
47133
|
-
const hold =
|
|
47536
|
+
const hold = terminateLive ? void 0 : live ? completionConfirmationHold(telemetry) : cutOffHold(telemetry);
|
|
47134
47537
|
if (hold) return { needsConfirmation: true, hold };
|
|
47135
47538
|
if (live) {
|
|
47136
47539
|
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
|
|
@@ -47183,10 +47586,10 @@ function projectCard(entry, stale) {
|
|
|
47183
47586
|
iconIsCustom: entry.iconSource === "custom" ? true : void 0
|
|
47184
47587
|
};
|
|
47185
47588
|
}
|
|
47186
|
-
function addProjectAtPath(input) {
|
|
47589
|
+
function addProjectAtPath(input, home = homedir17()) {
|
|
47187
47590
|
const typed = input.trim();
|
|
47188
47591
|
if (!typed) throw new Error("Enter a folder path.");
|
|
47189
|
-
const expanded = typed === "~" || typed.startsWith("~/") ?
|
|
47592
|
+
const expanded = typed === "~" || typed.startsWith("~/") ? join36(home, typed.slice(1)) : typed;
|
|
47190
47593
|
const absolute = resolve9(expanded);
|
|
47191
47594
|
let stats;
|
|
47192
47595
|
try {
|
|
@@ -47195,12 +47598,13 @@ function addProjectAtPath(input) {
|
|
|
47195
47598
|
throw new Error(`No folder at ${absolute}`);
|
|
47196
47599
|
}
|
|
47197
47600
|
if (!stats.isDirectory()) throw new Error(`That is a file, not a folder: ${absolute}`);
|
|
47198
|
-
const root =
|
|
47199
|
-
|
|
47601
|
+
const root = chosenProjectRoot(absolute, home);
|
|
47602
|
+
if (isHomeDirectory(root, home)) throw new Error("The home folder cannot be a project \u2014 choose a folder inside it.");
|
|
47603
|
+
const id = ensureProjectIdFile(root, home, existingProjectId(root));
|
|
47200
47604
|
const remoteOwner = resolveProjectLabel(root)?.split("/")[0];
|
|
47201
|
-
let registered = registerProject({ dir: root, id, remoteOwner });
|
|
47605
|
+
let registered = registerProject({ dir: root, id, remoteOwner }, home);
|
|
47202
47606
|
if (registered.action === "duplicate") {
|
|
47203
|
-
registered = registerProject({ dir: root, id: writeProjectIdFile(root, randomUUID16()), remoteOwner });
|
|
47607
|
+
registered = registerProject({ dir: root, id: writeProjectIdFile(root, randomUUID16()), remoteOwner }, home);
|
|
47204
47608
|
}
|
|
47205
47609
|
if (!registered.entry) throw new Error("Could not register that folder.");
|
|
47206
47610
|
return projectCard(registered.entry, false);
|
|
@@ -47231,7 +47635,7 @@ function storeProjectIcon(id, name, bytes) {
|
|
|
47231
47635
|
function setProjectIconFromFile(id, file) {
|
|
47232
47636
|
let bytes;
|
|
47233
47637
|
try {
|
|
47234
|
-
bytes =
|
|
47638
|
+
bytes = readFileSync24(file);
|
|
47235
47639
|
} catch {
|
|
47236
47640
|
throw new Error("That file could not be read.");
|
|
47237
47641
|
}
|
|
@@ -47239,7 +47643,7 @@ function setProjectIconFromFile(id, file) {
|
|
|
47239
47643
|
return storeProjectIcon(id, file, bytes);
|
|
47240
47644
|
}
|
|
47241
47645
|
function createRouter(ctx) {
|
|
47242
|
-
const frizzDir =
|
|
47646
|
+
const frizzDir = join36(ctx.project.dir, ".frizz");
|
|
47243
47647
|
const openRoots = openableFileRoots(ctx.project);
|
|
47244
47648
|
function isAutoTitledSession(slug) {
|
|
47245
47649
|
return ctx.storage.getSession(slug)?.title_auto === 1;
|
|
@@ -47390,6 +47794,9 @@ function createRouter(ctx) {
|
|
|
47390
47794
|
if (row.claude_runtime !== "broker" || !ctx.claudeBroker) {
|
|
47391
47795
|
return blocked("Steering a sub-agent needs the Claude session broker; this thread predates it.");
|
|
47392
47796
|
}
|
|
47797
|
+
if (ctx.tailer.get(slug)?.turn === "in-flight") {
|
|
47798
|
+
return blocked("The thread is working on its own turn right now, and a steer sent mid-turn is delivered to the thread instead of this sub-agent. The box comes back when the thread rests.");
|
|
47799
|
+
}
|
|
47393
47800
|
return { sessionId: row.session_id };
|
|
47394
47801
|
}
|
|
47395
47802
|
function codexShellTarget(slug, id) {
|
|
@@ -47644,9 +48051,10 @@ function createRouter(ctx) {
|
|
|
47644
48051
|
//
|
|
47645
48052
|
// WHY THE GATE IS STRICT. Measured live: addressing a child that has ALREADY SETTLED does not
|
|
47646
48053
|
// error and does not vanish — the CLI falls the message back onto the MAIN thread, where the
|
|
47647
|
-
// parent obeys it as if the operator had typed it into the thread composer.
|
|
47648
|
-
//
|
|
47649
|
-
//
|
|
48054
|
+
// parent obeys it as if the operator had typed it into the thread composer. A steer sent while
|
|
48055
|
+
// the parent's own turn is IN FLIGHT misdelivers the same way (absorbed into that turn — see the
|
|
48056
|
+
// predicate). So an ungated steer is not a no-op, it is a misdelivery. `subAgentSteerable` is the
|
|
48057
|
+
// single predicate that decides, and the drawer's prompt box is rendered off the same answer.
|
|
47650
48058
|
subAgentSteer: mutation({
|
|
47651
48059
|
input: external_exports.object({ slug: ThreadSlug, id: external_exports.string(), message: external_exports.string().min(1), deliveryId: external_exports.string().optional() }).strict(),
|
|
47652
48060
|
output: external_exports.object({ delivered: external_exports.boolean() }),
|
|
@@ -47915,6 +48323,7 @@ ${gapNote}` : input.message;
|
|
|
47915
48323
|
mayHaveLiveBackgroundWork(ctx.tailer.get(input.slug))
|
|
47916
48324
|
)
|
|
47917
48325
|
});
|
|
48326
|
+
if (row.exited === 1) ctx.storage.setExitedIfCurrent(input.slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
47918
48327
|
if (input.freshProcess) retireOutstandingDeliveries(ctx.storage, input.slug);
|
|
47919
48328
|
const preempted = input.interrupt === true && bridge.interruptTurn({ threadSlug: input.slug, sessionId: row.session_id });
|
|
47920
48329
|
if (input.deliveryId) {
|
|
@@ -48191,6 +48600,19 @@ ${gapNote}` : input.message;
|
|
|
48191
48600
|
ctx.board.refresh();
|
|
48192
48601
|
}
|
|
48193
48602
|
}),
|
|
48603
|
+
// Pin/unpin: the human lifts a thread out of the rail's band system into the pinned band at the
|
|
48604
|
+
// very top (or drops it back in). Same editability gate as the snooze, but NO archived refusal —
|
|
48605
|
+
// the pin deliberately outranks Done, so a pinned thread that finishes stays pinned until unpinned.
|
|
48606
|
+
setThreadPinned: mutation({
|
|
48607
|
+
input: SetThreadPinnedInput,
|
|
48608
|
+
handler: async ({ input }) => {
|
|
48609
|
+
currentOwnedSession(input.slug, input.sessionId);
|
|
48610
|
+
const thread = (await ctx.board.snapshot()).threads.find((candidate) => candidate.id === input.slug);
|
|
48611
|
+
if (!thread || thread.kind !== "session" || thread.foreign) throw new Error(`thread ${input.slug} is not editable`);
|
|
48612
|
+
ctx.storage.setPinnedAt(input.slug, input.pinned ? (/* @__PURE__ */ new Date()).toISOString() : null);
|
|
48613
|
+
ctx.board.refresh();
|
|
48614
|
+
}
|
|
48615
|
+
}),
|
|
48194
48616
|
// Re-read the worker plugin closure INTO the live session: hooks, skills, agent profiles and MCP
|
|
48195
48617
|
// servers, without restarting the process. This is `/reload-plugins` driven from the board.
|
|
48196
48618
|
//
|
|
@@ -48239,7 +48661,7 @@ ${gapNote}` : input.message;
|
|
|
48239
48661
|
throw new Error("This thread moved on; reopen it and try again");
|
|
48240
48662
|
}
|
|
48241
48663
|
if (input.stopHook && input.prompt?.trim() && autonomousGoal(row) === void 0) {
|
|
48242
|
-
cancelQuestionsForAutonomy(input.slug);
|
|
48664
|
+
if (cancelQuestionsForAutonomy(input.slug) > 0) ctx.scheduler.kick();
|
|
48243
48665
|
}
|
|
48244
48666
|
ctx.board.refresh();
|
|
48245
48667
|
}
|
|
@@ -48271,7 +48693,7 @@ ${gapNote}` : input.message;
|
|
|
48271
48693
|
throw new Error(`thread ${input.slug} could not be updated`);
|
|
48272
48694
|
}
|
|
48273
48695
|
if (input.stopHook && input.prompt?.trim() && autonomousGoal(row) === void 0) {
|
|
48274
|
-
cancelQuestionsForAutonomy(input.slug);
|
|
48696
|
+
if (cancelQuestionsForAutonomy(input.slug) > 0) ctx.scheduler.kick();
|
|
48275
48697
|
}
|
|
48276
48698
|
ctx.board.refresh();
|
|
48277
48699
|
return { replaced };
|
|
@@ -48431,14 +48853,19 @@ ${gapNote}` : input.message;
|
|
|
48431
48853
|
const armed = ctx.storage.listPrWatches(input.slug, { armedOnly: true });
|
|
48432
48854
|
const existing = armed.find((w) => w.owner === ref.owner && w.repo === ref.repo && w.number === ref.number);
|
|
48433
48855
|
const target = `${ref.owner}/${ref.repo}#${ref.number}`;
|
|
48434
|
-
if (existing)
|
|
48856
|
+
if (existing) {
|
|
48857
|
+
const expiresAt = new Date(existing.expires_at ?? Date.now()).toISOString();
|
|
48858
|
+
return { id: existing.id, target, alreadyArmed: true, expiresAt, watches: armedPrWatchViews(input.slug) };
|
|
48859
|
+
}
|
|
48435
48860
|
if (armed.length >= PR_WATCH_MAX_ARMED) {
|
|
48436
48861
|
throw new Error(`this thread already watches ${armed.length} pull requests (the limit is ${PR_WATCH_MAX_ARMED}) \u2014 drop one first`);
|
|
48437
48862
|
}
|
|
48438
|
-
const
|
|
48439
|
-
if (
|
|
48440
|
-
throw new Error(`\`for: ${input.for}\` is not a duration \u2014 give one like \`
|
|
48863
|
+
const asked = input.for === void 0 ? PR_WATCH_DEFAULT_FOR_MS : parseAwaitingDurationRaw(input.for);
|
|
48864
|
+
if (asked === null) {
|
|
48865
|
+
throw new Error(`\`for: ${input.for}\` is not a duration \u2014 give one like \`2h\`, \`3d\` or \`180d\` (max 365d)`);
|
|
48441
48866
|
}
|
|
48867
|
+
const forMs = Math.min(asked, PR_WATCH_FOR_MAX_MS);
|
|
48868
|
+
const clampedFrom = input.for !== void 0 && asked > PR_WATCH_FOR_MAX_MS ? { clampedFrom: input.for } : {};
|
|
48442
48869
|
const probe = await ctx.probePr(ref);
|
|
48443
48870
|
if (!probe.ok) {
|
|
48444
48871
|
throw new Error(
|
|
@@ -48450,7 +48877,7 @@ ${gapNote}` : input.message;
|
|
|
48450
48877
|
ctx.storage.clearThreadDone(input.slug);
|
|
48451
48878
|
ctx.storage.armPrWatch({ id, slug: input.slug, owner: ref.owner, repo: ref.repo, number: ref.number, createdAtMs: now, expiresAtMs: now + forMs });
|
|
48452
48879
|
ctx.board.refresh();
|
|
48453
|
-
return { id, target, alreadyArmed: false, watches: armedPrWatchViews(input.slug) };
|
|
48880
|
+
return { id, target, alreadyArmed: false, expiresAt: new Date(now + forMs).toISOString(), ...clampedFrom, watches: armedPrWatchViews(input.slug) };
|
|
48454
48881
|
}
|
|
48455
48882
|
}),
|
|
48456
48883
|
dropOwnPrWatch: mutation({
|
|
@@ -48495,16 +48922,18 @@ ${gapNote}` : input.message;
|
|
|
48495
48922
|
if (armed.length >= OWN_WATCH_MAX_ARMED) {
|
|
48496
48923
|
throw new Error(`this thread already holds ${armed.length} watches (the limit is ${OWN_WATCH_MAX_ARMED}) \u2014 drop one first`);
|
|
48497
48924
|
}
|
|
48498
|
-
const
|
|
48499
|
-
if (
|
|
48925
|
+
const asked = parseAwaitingDurationRaw(input.for);
|
|
48926
|
+
if (asked === null) {
|
|
48500
48927
|
throw new Error(`\`for: ${input.for}\` is not a duration \u2014 give one like \`30m\`, \`2h\` or \`3d\` (max 24h)`);
|
|
48501
48928
|
}
|
|
48929
|
+
const forMs = Math.min(asked, AWAITING_FOR_MAX_MS);
|
|
48930
|
+
const clampedFrom = asked > AWAITING_FOR_MAX_MS ? { clampedFrom: input.for } : {};
|
|
48502
48931
|
const now = Date.now();
|
|
48503
48932
|
const id = `wch_${randomUUID16().replace(/-/g, "").slice(0, 12)}`;
|
|
48504
48933
|
ctx.storage.clearThreadDone(input.slug);
|
|
48505
48934
|
ctx.storage.armThreadWatch({ id, slug: input.slug, kind: input.kind, target, createdAtMs: now, expiresAtMs: now + forMs });
|
|
48506
48935
|
ctx.board.refresh();
|
|
48507
|
-
return { id, kind: input.kind, target, alreadyArmed: false, watches: armedOwnWatchViews(input.slug) };
|
|
48936
|
+
return { id, kind: input.kind, target, alreadyArmed: false, ...clampedFrom, watches: armedOwnWatchViews(input.slug) };
|
|
48508
48937
|
}
|
|
48509
48938
|
}),
|
|
48510
48939
|
dropOwnWatch: mutation({
|
|
@@ -48547,12 +48976,6 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
48547
48976
|
}
|
|
48548
48977
|
const faults = input.questions.flatMap((q) => askedQuestionFaults(q));
|
|
48549
48978
|
if (faults.length > 0) throw new Error(faults.join("\n"));
|
|
48550
|
-
const open = ctx.storage.listThreadQuestions(input.slug, { openOnly: true });
|
|
48551
|
-
if (open.length + input.questions.length > ASK_MAX_OPEN) {
|
|
48552
|
-
throw new Error(
|
|
48553
|
-
`this thread already has ${open.length} unanswered question(s); the limit is ${ASK_MAX_OPEN}. Withdraw the ones you no longer need answered with \`unask\`, or decide them yourself.`
|
|
48554
|
-
);
|
|
48555
|
-
}
|
|
48556
48979
|
const now = Date.now();
|
|
48557
48980
|
const registered = input.questions.map((spec) => {
|
|
48558
48981
|
const id = `qst_${randomUUID16().replace(/-/g, "").slice(0, 12)}`;
|
|
@@ -48787,12 +49210,13 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
48787
49210
|
output: external_exports.object({ path: external_exports.string(), markdown: external_exports.string(), truncated: external_exports.boolean() }),
|
|
48788
49211
|
handler: async ({ input }) => readLocalMarkdown(input.path, openRoots)
|
|
48789
49212
|
}),
|
|
48790
|
-
// A
|
|
48791
|
-
//
|
|
49213
|
+
// A file's SOURCE, for the fullscreen page's file viewer. The SAME openable roots as the Markdown
|
|
49214
|
+
// reader — see readLocalTextFile for why the project-directory-only gate this replaced refused 41%
|
|
49215
|
+
// of the rail's own rows (a worker's checkout is very often a worktree outside the project dir).
|
|
48792
49216
|
localFile: query({
|
|
48793
49217
|
input: external_exports.object({ path: external_exports.string().max(4096) }).strict(),
|
|
48794
49218
|
output: external_exports.object({ path: external_exports.string(), text: external_exports.string(), truncated: external_exports.boolean() }),
|
|
48795
|
-
handler: async ({ input }) => readLocalTextFile(input.path,
|
|
49219
|
+
handler: async ({ input }) => readLocalTextFile(input.path, openRoots)
|
|
48796
49220
|
}),
|
|
48797
49221
|
// Batch-classify path REFERENCES (as they appear in inline code) → their canonical openable path, or
|
|
48798
49222
|
// null when a candidate doesn't resolve to a real file under the openable roots. The client renders
|
|
@@ -49191,8 +49615,8 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
49191
49615
|
* Register a directory as a project, from the grid's phantom card.
|
|
49192
49616
|
*
|
|
49193
49617
|
* The same authority as running `frizz` in that directory, and strictly less: this registers and
|
|
49194
|
-
* resolves an id, it dispatches nothing.
|
|
49195
|
-
*
|
|
49618
|
+
* resolves an id, it dispatches nothing. The root comes from chosenProjectRoot — a folder inside
|
|
49619
|
+
* a checkout adds the checkout, but an adopted plain-directory ancestor never captures the pick.
|
|
49196
49620
|
*/
|
|
49197
49621
|
projectAdd: mutation({
|
|
49198
49622
|
input: external_exports.object({ path: external_exports.string().min(1) }),
|
|
@@ -49573,7 +49997,7 @@ var init_compression = __esm({
|
|
|
49573
49997
|
});
|
|
49574
49998
|
|
|
49575
49999
|
// packages/server/src/local-image.ts
|
|
49576
|
-
import { readFileSync as
|
|
50000
|
+
import { readFileSync as readFileSync25, realpathSync as realpathSync9, statSync as statSync18 } from "node:fs";
|
|
49577
50001
|
import { extname as extname2, isAbsolute as isAbsolute5 } from "node:path";
|
|
49578
50002
|
function resolveLocalImage(rawPath) {
|
|
49579
50003
|
if (!rawPath || !isAbsolute5(rawPath)) return { status: 400 };
|
|
@@ -49587,7 +50011,7 @@ function resolveLocalImage(rawPath) {
|
|
|
49587
50011
|
}
|
|
49588
50012
|
try {
|
|
49589
50013
|
if (!statSync18(real).isFile()) return { status: 404 };
|
|
49590
|
-
return { status: 200, contentType, body:
|
|
50014
|
+
return { status: 200, contentType, body: readFileSync25(real) };
|
|
49591
50015
|
} catch {
|
|
49592
50016
|
return { status: 404 };
|
|
49593
50017
|
}
|
|
@@ -49607,9 +50031,9 @@ var init_local_image = __esm({
|
|
|
49607
50031
|
});
|
|
49608
50032
|
|
|
49609
50033
|
// packages/server/src/project-icon.ts
|
|
49610
|
-
import { readFileSync as
|
|
49611
|
-
import { homedir as
|
|
49612
|
-
import { basename as basename10, dirname as dirname15, extname as extname3, join as
|
|
50034
|
+
import { readFileSync as readFileSync26, readdirSync as readdirSync10, realpathSync as realpathSync10, statSync as statSync19 } from "node:fs";
|
|
50035
|
+
import { homedir as homedir18 } from "node:os";
|
|
50036
|
+
import { basename as basename10, dirname as dirname15, extname as extname3, join as join37, relative as relative2, sep as sep3 } from "node:path";
|
|
49613
50037
|
function normalizeStem(stem) {
|
|
49614
50038
|
const sized = stem.toLowerCase().replace(/[@-]\d+(\.\d+)?x(\d+)?$/u, "").replace(/[-_]\d{2,4}$/u, "");
|
|
49615
50039
|
const normalized = sized.replace(VARIANT_SUFFIX, "");
|
|
@@ -49627,7 +50051,7 @@ function sizeScore(dimensions) {
|
|
|
49627
50051
|
return Math.max(0, Math.min(40, Math.round(Math.log2(edge / 24) * 8)));
|
|
49628
50052
|
}
|
|
49629
50053
|
function under(root, relativeDirectory) {
|
|
49630
|
-
return relativeDirectory ?
|
|
50054
|
+
return relativeDirectory ? join37(root, relativeDirectory) : root;
|
|
49631
50055
|
}
|
|
49632
50056
|
function listFiles(directory) {
|
|
49633
50057
|
try {
|
|
@@ -49649,19 +50073,19 @@ function iconDirectories(root) {
|
|
|
49649
50073
|
for (const asset of ASSET_DIRECTORIES) directories.push(under(root, asset));
|
|
49650
50074
|
for (const host of HOST_DIRECTORIES) {
|
|
49651
50075
|
if (!children2.has(host)) continue;
|
|
49652
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
50076
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join37(root, host), asset));
|
|
49653
50077
|
}
|
|
49654
50078
|
for (const extra of EXTRA_DIRECTORIES) directories.push(under(root, extra));
|
|
49655
50079
|
for (const container of WORKSPACE_CONTAINERS) {
|
|
49656
50080
|
if (!children2.has(container)) continue;
|
|
49657
50081
|
let members;
|
|
49658
50082
|
try {
|
|
49659
|
-
members = readdirSync10(
|
|
50083
|
+
members = readdirSync10(join37(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
49660
50084
|
} catch {
|
|
49661
50085
|
continue;
|
|
49662
50086
|
}
|
|
49663
50087
|
for (const member of members) {
|
|
49664
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
50088
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join37(root, container, member), asset));
|
|
49665
50089
|
}
|
|
49666
50090
|
}
|
|
49667
50091
|
return [...new Set(directories)];
|
|
@@ -49669,13 +50093,13 @@ function iconDirectories(root) {
|
|
|
49669
50093
|
function manifestIcons(manifestPath) {
|
|
49670
50094
|
let parsed;
|
|
49671
50095
|
try {
|
|
49672
|
-
parsed = JSON.parse(
|
|
50096
|
+
parsed = JSON.parse(readFileSync26(manifestPath, "utf8"));
|
|
49673
50097
|
} catch {
|
|
49674
50098
|
return [];
|
|
49675
50099
|
}
|
|
49676
50100
|
if (!Array.isArray(parsed?.icons)) return [];
|
|
49677
50101
|
const directory = dirname15(manifestPath);
|
|
49678
|
-
return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) =>
|
|
50102
|
+
return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) => join37(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
|
|
49679
50103
|
}
|
|
49680
50104
|
function projectIconCandidates(root) {
|
|
49681
50105
|
const projectStem = normalizeStem(basename10(root)).normalized;
|
|
@@ -49694,7 +50118,7 @@ function projectIconCandidates(root) {
|
|
|
49694
50118
|
for (const name of listFiles(directory)) {
|
|
49695
50119
|
const extension2 = extname3(name).toLowerCase();
|
|
49696
50120
|
if (MANIFEST_NAMES.includes(name.toLowerCase())) {
|
|
49697
|
-
for (const icon of manifestIcons(
|
|
50121
|
+
for (const icon of manifestIcons(join37(directory, name))) {
|
|
49698
50122
|
if (MEASURABLE_IMAGE_EXTENSIONS.has(extname3(icon).toLowerCase())) {
|
|
49699
50123
|
fromManifest.add(icon);
|
|
49700
50124
|
files.push({ path: icon, directory: dirname15(icon) });
|
|
@@ -49705,7 +50129,7 @@ function projectIconCandidates(root) {
|
|
|
49705
50129
|
if (!MEASURABLE_IMAGE_EXTENSIONS.has(extension2)) continue;
|
|
49706
50130
|
const stem = name.slice(0, name.length - extension2.length);
|
|
49707
50131
|
if (normalizeStem(stem).normalized === "favicon") iconHomes.add(directory);
|
|
49708
|
-
files.push({ path:
|
|
50132
|
+
files.push({ path: join37(directory, name), directory });
|
|
49709
50133
|
if (files.length >= MAX_CANDIDATES) break;
|
|
49710
50134
|
}
|
|
49711
50135
|
}
|
|
@@ -49747,7 +50171,7 @@ function iconMediaType(path) {
|
|
|
49747
50171
|
}
|
|
49748
50172
|
function resolveProjectIconResponse(id, options = {}) {
|
|
49749
50173
|
if (!id) return { status: 400 };
|
|
49750
|
-
const home = options.home ??
|
|
50174
|
+
const home = options.home ?? homedir18();
|
|
49751
50175
|
const entry = readRegistry(home).projects.find((project) => project.id === id);
|
|
49752
50176
|
if (!entry) return { status: 404 };
|
|
49753
50177
|
const path = resolveProjectIcon(id, (root) => detectProjectIcon(root)?.path, { home });
|
|
@@ -49770,7 +50194,7 @@ function resolveProjectIconResponse(id, options = {}) {
|
|
|
49770
50194
|
});
|
|
49771
50195
|
if (!roots.some((root) => real === root || real.startsWith(root + sep3))) return { status: 404 };
|
|
49772
50196
|
try {
|
|
49773
|
-
return { status: 200, contentType, body:
|
|
50197
|
+
return { status: 200, contentType, body: readFileSync26(real) };
|
|
49774
50198
|
} catch {
|
|
49775
50199
|
return { status: 404 };
|
|
49776
50200
|
}
|
|
@@ -49882,8 +50306,8 @@ var init_project_icon = __esm({
|
|
|
49882
50306
|
});
|
|
49883
50307
|
|
|
49884
50308
|
// packages/server/src/local-visualization.ts
|
|
49885
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
49886
|
-
import { join as
|
|
50309
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync27, realpathSync as realpathSync11, statSync as statSync20 } from "node:fs";
|
|
50310
|
+
import { join as join38, sep as sep4 } from "node:path";
|
|
49887
50311
|
function children(path, pattern) {
|
|
49888
50312
|
try {
|
|
49889
50313
|
return readdirSync11(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
|
|
@@ -49895,7 +50319,7 @@ function isUnder2(path, root) {
|
|
|
49895
50319
|
return path === root || path.startsWith(root.endsWith(sep4) ? root : root + sep4);
|
|
49896
50320
|
}
|
|
49897
50321
|
function resolveFragment(projectDir, sessionId, file) {
|
|
49898
|
-
const base =
|
|
50322
|
+
const base = join38(projectDir, ".codex", "visualizations");
|
|
49899
50323
|
let projectReal;
|
|
49900
50324
|
let baseReal;
|
|
49901
50325
|
try {
|
|
@@ -49906,12 +50330,12 @@ function resolveFragment(projectDir, sessionId, file) {
|
|
|
49906
50330
|
}
|
|
49907
50331
|
if (!isUnder2(baseReal, projectReal)) return null;
|
|
49908
50332
|
for (const year of children(base, YEAR_PART)) {
|
|
49909
|
-
const yearDir =
|
|
50333
|
+
const yearDir = join38(base, year);
|
|
49910
50334
|
for (const month of children(yearDir, DATE_PART)) {
|
|
49911
|
-
const monthDir =
|
|
50335
|
+
const monthDir = join38(yearDir, month);
|
|
49912
50336
|
for (const day of children(monthDir, DATE_PART)) {
|
|
49913
|
-
const sessionRoot =
|
|
49914
|
-
const candidate =
|
|
50337
|
+
const sessionRoot = join38(baseReal, year, month, day, sessionId);
|
|
50338
|
+
const candidate = join38(monthDir, day, sessionId, file);
|
|
49915
50339
|
try {
|
|
49916
50340
|
const real = realpathSync11(candidate);
|
|
49917
50341
|
if (isUnder2(real, sessionRoot) && statSync20(real).isFile()) return real;
|
|
@@ -49997,7 +50421,7 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
|
|
|
49997
50421
|
try {
|
|
49998
50422
|
const size = statSync20(path).size;
|
|
49999
50423
|
if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
|
|
50000
|
-
fragment =
|
|
50424
|
+
fragment = readFileSync27(path, "utf8");
|
|
50001
50425
|
} catch {
|
|
50002
50426
|
return { status: 404 };
|
|
50003
50427
|
}
|
|
@@ -50040,7 +50464,7 @@ var init_local_visualization = __esm({
|
|
|
50040
50464
|
|
|
50041
50465
|
// packages/server/src/app.ts
|
|
50042
50466
|
import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync14 } from "node:fs";
|
|
50043
|
-
import { join as
|
|
50467
|
+
import { join as join39 } from "node:path";
|
|
50044
50468
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
50045
50469
|
function createApp(ctx, options = {}) {
|
|
50046
50470
|
const app = new Hono2();
|
|
@@ -50144,10 +50568,10 @@ function createApp(ctx, options = {}) {
|
|
|
50144
50568
|
if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
|
|
50145
50569
|
const ext = `.${attachmentExtension(name)}`;
|
|
50146
50570
|
const buf = Buffer.from(body.data, "base64");
|
|
50147
|
-
const dir =
|
|
50571
|
+
const dir = join39(ctx.project.stateDir, "attachments");
|
|
50148
50572
|
mkdirSync17(dir, { recursive: true });
|
|
50149
50573
|
const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
|
|
50150
|
-
const path =
|
|
50574
|
+
const path = join39(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
|
|
50151
50575
|
writeFileSync14(path, buf);
|
|
50152
50576
|
return c.json({ path });
|
|
50153
50577
|
});
|
|
@@ -50634,6 +51058,7 @@ function createAppSocketServer(deps) {
|
|
|
50634
51058
|
const maxOutputBufferBytes = deps.maxOutputBufferBytes ?? APP_SOCKET_MAX_OUTPUT_BUFFER_BYTES;
|
|
50635
51059
|
const maxConnections = boundedCount(deps.maxConnections, APP_SOCKET_MAX_CONNECTIONS);
|
|
50636
51060
|
const maxSubscriptions = deps.maxSubscriptionsPerConnection ?? APP_SOCKET_MAX_SUBSCRIPTIONS;
|
|
51061
|
+
const maxFileWatches = boundedCount(deps.maxFileWatchesPerConnection, APP_SOCKET_MAX_FILE_WATCHES);
|
|
50637
51062
|
const maxMessagesPerWindow = deps.maxMessagesPerWindow ?? APP_SOCKET_MAX_MESSAGES_PER_WINDOW;
|
|
50638
51063
|
const messageWindowMs = deps.messageWindowMs ?? APP_SOCKET_MESSAGE_WINDOW_MS;
|
|
50639
51064
|
const maxBufferedKeyframeEvents = deps.maxBufferedKeyframeEvents ?? APP_SOCKET_MAX_BUFFERED_KEYFRAME_EVENTS;
|
|
@@ -50673,6 +51098,55 @@ function createAppSocketServer(deps) {
|
|
|
50673
51098
|
let pendingUpgrades = 0;
|
|
50674
51099
|
const socketOrigins = /* @__PURE__ */ new WeakMap();
|
|
50675
51100
|
const lastSig = /* @__PURE__ */ new Map();
|
|
51101
|
+
const fileWatches = /* @__PURE__ */ new WeakMap();
|
|
51102
|
+
let fileWatchCount = 0;
|
|
51103
|
+
function subscribeFile(ws, path) {
|
|
51104
|
+
let held = fileWatches.get(ws);
|
|
51105
|
+
if (!held) {
|
|
51106
|
+
held = /* @__PURE__ */ new Map();
|
|
51107
|
+
fileWatches.set(ws, held);
|
|
51108
|
+
}
|
|
51109
|
+
if (held.has(path)) return;
|
|
51110
|
+
if (held.size >= maxFileWatches) {
|
|
51111
|
+
closeSocket(ws, 1008, "subscription limit exceeded");
|
|
51112
|
+
return;
|
|
51113
|
+
}
|
|
51114
|
+
const watchFile = deps.watchFile;
|
|
51115
|
+
if (!watchFile) return;
|
|
51116
|
+
let release;
|
|
51117
|
+
try {
|
|
51118
|
+
release = watchFile(path, () => {
|
|
51119
|
+
sendMsg(ws, { t: "file-changed", path });
|
|
51120
|
+
});
|
|
51121
|
+
} catch {
|
|
51122
|
+
return;
|
|
51123
|
+
}
|
|
51124
|
+
held.set(path, release);
|
|
51125
|
+
fileWatchCount++;
|
|
51126
|
+
}
|
|
51127
|
+
function unsubscribeFile(ws, path) {
|
|
51128
|
+
const held = fileWatches.get(ws);
|
|
51129
|
+
const release = held?.get(path);
|
|
51130
|
+
if (!held || !release) return;
|
|
51131
|
+
held.delete(path);
|
|
51132
|
+
fileWatchCount--;
|
|
51133
|
+
try {
|
|
51134
|
+
release();
|
|
51135
|
+
} catch {
|
|
51136
|
+
}
|
|
51137
|
+
}
|
|
51138
|
+
function dropFileWatches(ws) {
|
|
51139
|
+
const held = fileWatches.get(ws);
|
|
51140
|
+
if (!held) return;
|
|
51141
|
+
fileWatches.delete(ws);
|
|
51142
|
+
for (const release of held.values()) {
|
|
51143
|
+
fileWatchCount--;
|
|
51144
|
+
try {
|
|
51145
|
+
release();
|
|
51146
|
+
} catch {
|
|
51147
|
+
}
|
|
51148
|
+
}
|
|
51149
|
+
}
|
|
50676
51150
|
function closeSocket(ws, code, reason) {
|
|
50677
51151
|
if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) return;
|
|
50678
51152
|
try {
|
|
@@ -51145,6 +51619,11 @@ function createAppSocketServer(deps) {
|
|
|
51145
51619
|
return;
|
|
51146
51620
|
}
|
|
51147
51621
|
const msg = parsed.data;
|
|
51622
|
+
if (msg.topic === "file") {
|
|
51623
|
+
if (msg.t === "sub") subscribeFile(ws, msg.path);
|
|
51624
|
+
else unsubscribeFile(ws, msg.path);
|
|
51625
|
+
return;
|
|
51626
|
+
}
|
|
51148
51627
|
if (msg.t === "sub") {
|
|
51149
51628
|
if (registry.isSubscribed(ws, msg.slug)) return;
|
|
51150
51629
|
if (registry.slugsFor(ws).length >= maxSubscriptions) {
|
|
@@ -51165,6 +51644,7 @@ function createAppSocketServer(deps) {
|
|
|
51165
51644
|
}
|
|
51166
51645
|
clearInterval(heartbeat);
|
|
51167
51646
|
cancelPendingSubscriptionPush(ws);
|
|
51647
|
+
dropFileWatches(ws);
|
|
51168
51648
|
const terminationTimer = terminationTimers.get(ws);
|
|
51169
51649
|
if (terminationTimer) clearTimeout(terminationTimer);
|
|
51170
51650
|
terminationTimers.delete(ws);
|
|
@@ -51288,10 +51768,13 @@ function createAppSocketServer(deps) {
|
|
|
51288
51768
|
},
|
|
51289
51769
|
get pendingTranscriptRefreshes() {
|
|
51290
51770
|
return pendingTranscriptRefreshes.size;
|
|
51771
|
+
},
|
|
51772
|
+
get fileWatchCount() {
|
|
51773
|
+
return fileWatchCount;
|
|
51291
51774
|
}
|
|
51292
51775
|
};
|
|
51293
51776
|
}
|
|
51294
|
-
var WS_PATH, SubscriptionRegistry, HEARTBEAT_MS, APP_SOCKET_MAX_MESSAGE_BYTES, APP_SOCKET_MAX_LOGICAL_FRAME_BYTES, APP_SOCKET_MAX_OUTPUT_BUFFER_BYTES, APP_SOCKET_MAX_CONNECTIONS, APP_SOCKET_MAX_SUBSCRIPTIONS, APP_SOCKET_MAX_MESSAGES_PER_WINDOW, APP_SOCKET_MESSAGE_WINDOW_MS, APP_SOCKET_MAX_BUFFERED_KEYFRAME_EVENTS, APP_SOCKET_MAX_TRANSCRIPT_READS_PER_ORIGIN, APP_SOCKET_MAX_TRANSCRIPT_READS_OVERALL, APP_SOCKET_TRANSCRIPT_READ_WINDOW_MS, APP_SOCKET_MAX_TRANSCRIPT_CACHE_ENTRIES, APP_SOCKET_MAX_TRANSCRIPT_CACHE_BYTES;
|
|
51777
|
+
var WS_PATH, SubscriptionRegistry, HEARTBEAT_MS, APP_SOCKET_MAX_MESSAGE_BYTES, APP_SOCKET_MAX_LOGICAL_FRAME_BYTES, APP_SOCKET_MAX_OUTPUT_BUFFER_BYTES, APP_SOCKET_MAX_CONNECTIONS, APP_SOCKET_MAX_SUBSCRIPTIONS, APP_SOCKET_MAX_FILE_WATCHES, APP_SOCKET_MAX_MESSAGES_PER_WINDOW, APP_SOCKET_MESSAGE_WINDOW_MS, APP_SOCKET_MAX_BUFFERED_KEYFRAME_EVENTS, APP_SOCKET_MAX_TRANSCRIPT_READS_PER_ORIGIN, APP_SOCKET_MAX_TRANSCRIPT_READS_OVERALL, APP_SOCKET_TRANSCRIPT_READ_WINDOW_MS, APP_SOCKET_MAX_TRANSCRIPT_CACHE_ENTRIES, APP_SOCKET_MAX_TRANSCRIPT_CACHE_BYTES;
|
|
51295
51778
|
var init_app_socket = __esm({
|
|
51296
51779
|
"packages/server/src/app-socket.ts"() {
|
|
51297
51780
|
"use strict";
|
|
@@ -51374,6 +51857,7 @@ var init_app_socket = __esm({
|
|
|
51374
51857
|
APP_SOCKET_MAX_OUTPUT_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
51375
51858
|
APP_SOCKET_MAX_CONNECTIONS = 64;
|
|
51376
51859
|
APP_SOCKET_MAX_SUBSCRIPTIONS = 32;
|
|
51860
|
+
APP_SOCKET_MAX_FILE_WATCHES = 16;
|
|
51377
51861
|
APP_SOCKET_MAX_MESSAGES_PER_WINDOW = 120;
|
|
51378
51862
|
APP_SOCKET_MESSAGE_WINDOW_MS = 1e3;
|
|
51379
51863
|
APP_SOCKET_MAX_BUFFERED_KEYFRAME_EVENTS = 256;
|
|
@@ -51385,11 +51869,60 @@ var init_app_socket = __esm({
|
|
|
51385
51869
|
}
|
|
51386
51870
|
});
|
|
51387
51871
|
|
|
51872
|
+
// packages/server/src/local-file-watch.ts
|
|
51873
|
+
import { watch } from "node:fs";
|
|
51874
|
+
import { basename as basename11, dirname as dirname16 } from "node:path";
|
|
51875
|
+
function close(path) {
|
|
51876
|
+
const entry = entries.get(path);
|
|
51877
|
+
if (!entry) return;
|
|
51878
|
+
entries.delete(path);
|
|
51879
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
51880
|
+
try {
|
|
51881
|
+
entry.watcher.close();
|
|
51882
|
+
} catch {
|
|
51883
|
+
}
|
|
51884
|
+
}
|
|
51885
|
+
function watchLocalFile(path, onChange) {
|
|
51886
|
+
let entry = entries.get(path);
|
|
51887
|
+
if (!entry) {
|
|
51888
|
+
const name = basename11(path);
|
|
51889
|
+
const watcher2 = watch(dirname16(path), { persistent: false }, (_event, changed) => {
|
|
51890
|
+
if (changed !== null && changed !== void 0 && changed.toString() !== name) return;
|
|
51891
|
+
const current = entries.get(path);
|
|
51892
|
+
if (!current) return;
|
|
51893
|
+
if (current.timer) clearTimeout(current.timer);
|
|
51894
|
+
current.timer = setTimeout(() => {
|
|
51895
|
+
current.timer = null;
|
|
51896
|
+
for (const listener of current.listeners) listener();
|
|
51897
|
+
}, SETTLE_MS);
|
|
51898
|
+
current.timer.unref?.();
|
|
51899
|
+
});
|
|
51900
|
+
entry = { watcher: watcher2, listeners: /* @__PURE__ */ new Set(), timer: null };
|
|
51901
|
+
watcher2.on("error", () => close(path));
|
|
51902
|
+
entries.set(path, entry);
|
|
51903
|
+
}
|
|
51904
|
+
entry.listeners.add(onChange);
|
|
51905
|
+
return () => {
|
|
51906
|
+
const current = entries.get(path);
|
|
51907
|
+
if (!current) return;
|
|
51908
|
+
current.listeners.delete(onChange);
|
|
51909
|
+
if (current.listeners.size === 0) close(path);
|
|
51910
|
+
};
|
|
51911
|
+
}
|
|
51912
|
+
var SETTLE_MS, entries;
|
|
51913
|
+
var init_local_file_watch = __esm({
|
|
51914
|
+
"packages/server/src/local-file-watch.ts"() {
|
|
51915
|
+
"use strict";
|
|
51916
|
+
SETTLE_MS = 50;
|
|
51917
|
+
entries = /* @__PURE__ */ new Map();
|
|
51918
|
+
}
|
|
51919
|
+
});
|
|
51920
|
+
|
|
51388
51921
|
// packages/server/src/boot-progress.ts
|
|
51389
|
-
import { mkdirSync as mkdirSync18, readFileSync as
|
|
51390
|
-
import { basename as
|
|
51922
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync28, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
|
|
51923
|
+
import { basename as basename12, dirname as dirname17, join as join40 } from "node:path";
|
|
51391
51924
|
function bootProgressPath(stateDir) {
|
|
51392
|
-
return
|
|
51925
|
+
return join40(stateDir, BOOT_PROGRESS_NAME);
|
|
51393
51926
|
}
|
|
51394
51927
|
function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
51395
51928
|
if (!stateDir) {
|
|
@@ -51409,8 +51942,8 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
51409
51942
|
lastAt = now;
|
|
51410
51943
|
step++;
|
|
51411
51944
|
try {
|
|
51412
|
-
mkdirSync18(
|
|
51413
|
-
const temp =
|
|
51945
|
+
mkdirSync18(dirname17(path), { recursive: true, mode: 448 });
|
|
51946
|
+
const temp = join40(dirname17(path), `.${basename12(path)}.${process.pid}.tmp`);
|
|
51414
51947
|
writeFileSync15(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
|
|
51415
51948
|
`, "utf8");
|
|
51416
51949
|
renameSync10(temp, path);
|
|
@@ -51963,17 +52496,17 @@ var init_legacy_project_db = __esm({
|
|
|
51963
52496
|
|
|
51964
52497
|
// packages/server/src/frizz-db.ts
|
|
51965
52498
|
import { existsSync as existsSync14, mkdirSync as mkdirSync19, readdirSync as readdirSync12, renameSync as renameSync11, statSync as statSync21 } from "node:fs";
|
|
51966
|
-
import { basename as
|
|
52499
|
+
import { basename as basename13, dirname as dirname18, join as join41 } from "node:path";
|
|
51967
52500
|
function frizzDatabaseLocation(options) {
|
|
51968
52501
|
if (options.stateDir !== void 0) {
|
|
51969
|
-
const projectsDir =
|
|
51970
|
-
if (
|
|
51971
|
-
return { path: options.path ??
|
|
52502
|
+
const projectsDir = dirname18(options.stateDir);
|
|
52503
|
+
if (basename13(projectsDir) === "projects") {
|
|
52504
|
+
return { path: options.path ?? join41(dirname18(projectsDir), "ui.db"), projectsDir };
|
|
51972
52505
|
}
|
|
51973
|
-
return { path: options.path ??
|
|
52506
|
+
return { path: options.path ?? join41(options.stateDir, "ui.db"), projectsDir: void 0 };
|
|
51974
52507
|
}
|
|
51975
52508
|
const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
|
|
51976
|
-
return { path: options.path ??
|
|
52509
|
+
return { path: options.path ?? join41(data, "ui.db"), projectsDir: join41(data, "projects") };
|
|
51977
52510
|
}
|
|
51978
52511
|
function ensureFrizzSchema(db) {
|
|
51979
52512
|
db.exec(`
|
|
@@ -51992,7 +52525,7 @@ function ensureFrizzSchema(db) {
|
|
|
51992
52525
|
}
|
|
51993
52526
|
function openFrizzDatabase(options = {}) {
|
|
51994
52527
|
const { path, projectsDir } = frizzDatabaseLocation(options);
|
|
51995
|
-
mkdirSync19(
|
|
52528
|
+
mkdirSync19(dirname18(path), { recursive: true });
|
|
51996
52529
|
const db = new sqlite_default(path);
|
|
51997
52530
|
db.pragma("busy_timeout = 5000");
|
|
51998
52531
|
db.pragma("journal_mode = WAL");
|
|
@@ -52004,10 +52537,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
52004
52537
|
);
|
|
52005
52538
|
for (const projectId of retiredLegacyProjectIds(projectsDir)) {
|
|
52006
52539
|
if (alreadyImported.has(projectId)) continue;
|
|
52007
|
-
const dir =
|
|
52540
|
+
const dir = join41(projectsDir, projectId);
|
|
52008
52541
|
try {
|
|
52009
|
-
renameSync11(
|
|
52010
|
-
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId,
|
|
52542
|
+
renameSync11(join41(dir, "ui.db.imported"), join41(dir, "ui.db"));
|
|
52543
|
+
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join41(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
|
|
52011
52544
|
alreadyImported.add(projectId);
|
|
52012
52545
|
log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
|
|
52013
52546
|
} catch (error) {
|
|
@@ -52017,7 +52550,7 @@ function openFrizzDatabase(options = {}) {
|
|
|
52017
52550
|
}
|
|
52018
52551
|
for (const projectId of legacyProjectIds(projectsDir)) {
|
|
52019
52552
|
if (alreadyImported.has(projectId)) continue;
|
|
52020
|
-
const legacyPath =
|
|
52553
|
+
const legacyPath = join41(projectsDir, projectId, "ui.db");
|
|
52021
52554
|
try {
|
|
52022
52555
|
const rows = importLegacyProject(db, projectId, legacyPath);
|
|
52023
52556
|
imported.push({ projectId, rows });
|
|
@@ -52050,7 +52583,7 @@ function projectIdsWithFile(projectsDir, file) {
|
|
|
52050
52583
|
if (!existsSync14(projectsDir)) return [];
|
|
52051
52584
|
return readdirSync12(projectsDir).filter((name) => {
|
|
52052
52585
|
try {
|
|
52053
|
-
return statSync21(
|
|
52586
|
+
return statSync21(join41(projectsDir, name, file)).isFile();
|
|
52054
52587
|
} catch {
|
|
52055
52588
|
return false;
|
|
52056
52589
|
}
|
|
@@ -52060,7 +52593,7 @@ function legacyProjectIds(projectsDir) {
|
|
|
52060
52593
|
return projectIdsWithFile(projectsDir, "ui.db");
|
|
52061
52594
|
}
|
|
52062
52595
|
function retiredLegacyProjectIds(projectsDir) {
|
|
52063
|
-
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync14(
|
|
52596
|
+
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync14(join41(projectsDir, id, "ui.db")));
|
|
52064
52597
|
}
|
|
52065
52598
|
function tableColumns(db, schema, table) {
|
|
52066
52599
|
return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
|
|
@@ -52213,8 +52746,8 @@ var init_tenant_prime = __esm({
|
|
|
52213
52746
|
|
|
52214
52747
|
// packages/server/src/project-teardown.ts
|
|
52215
52748
|
import { rmSync as rmSync12 } from "node:fs";
|
|
52216
|
-
import { homedir as
|
|
52217
|
-
import { dirname as
|
|
52749
|
+
import { homedir as homedir19 } from "node:os";
|
|
52750
|
+
import { dirname as dirname19, resolve as resolve10 } from "node:path";
|
|
52218
52751
|
async function stopProjectWorkers(ctx) {
|
|
52219
52752
|
let stopped = 0;
|
|
52220
52753
|
for (const row of ctx.storage.allSessions()) {
|
|
@@ -52229,12 +52762,12 @@ async function stopProjectWorkers(ctx) {
|
|
|
52229
52762
|
}
|
|
52230
52763
|
return stopped;
|
|
52231
52764
|
}
|
|
52232
|
-
function deleteProjectState(projectId, home =
|
|
52765
|
+
function deleteProjectState(projectId, home = homedir19()) {
|
|
52233
52766
|
const target = projectStateDir(projectId, home);
|
|
52234
52767
|
if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
|
|
52235
52768
|
throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
|
|
52236
52769
|
}
|
|
52237
|
-
if (
|
|
52770
|
+
if (dirname19(resolve10(target)) !== resolve10(dirname19(projectStateDir("x", home)))) {
|
|
52238
52771
|
throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
|
|
52239
52772
|
}
|
|
52240
52773
|
rmSync12(target, { recursive: true, force: true });
|
|
@@ -52262,11 +52795,11 @@ __export(index_exports, {
|
|
|
52262
52795
|
unknownProjectPage: () => unknownProjectPage
|
|
52263
52796
|
});
|
|
52264
52797
|
import { createServer } from "node:http";
|
|
52265
|
-
import { readFileSync as
|
|
52266
|
-
import { dirname as
|
|
52798
|
+
import { readFileSync as readFileSync29, existsSync as existsSync15 } from "node:fs";
|
|
52799
|
+
import { dirname as dirname20, join as join42, resolve as resolve11, extname as extname4, normalize } from "node:path";
|
|
52267
52800
|
function readServerAddressHolder(path) {
|
|
52268
52801
|
try {
|
|
52269
|
-
const value = JSON.parse(
|
|
52802
|
+
const value = JSON.parse(readFileSync29(path, "utf8"));
|
|
52270
52803
|
if (typeof value?.pid !== "number") return void 0;
|
|
52271
52804
|
return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
|
|
52272
52805
|
} catch {
|
|
@@ -52366,11 +52899,11 @@ function createShutdownSignalHandler(options) {
|
|
|
52366
52899
|
}
|
|
52367
52900
|
function serveStatic(distDir, req, res) {
|
|
52368
52901
|
const rel = normalize((req.url ?? "/").split("?")[0]).replace(/^(\.\.[/\\])+/, "");
|
|
52369
|
-
let file =
|
|
52370
|
-
if (!file.startsWith(distDir)) file =
|
|
52371
|
-
if (!existsSync15(file)) file =
|
|
52902
|
+
let file = join42(distDir, rel === "/" ? "index.html" : rel);
|
|
52903
|
+
if (!file.startsWith(distDir)) file = join42(distDir, "index.html");
|
|
52904
|
+
if (!existsSync15(file)) file = join42(distDir, "index.html");
|
|
52372
52905
|
try {
|
|
52373
|
-
const body =
|
|
52906
|
+
const body = readFileSync29(file);
|
|
52374
52907
|
res.writeHead(200, { "content-type": MIME[extname4(file)] ?? "application/octet-stream" });
|
|
52375
52908
|
res.end(body);
|
|
52376
52909
|
} catch {
|
|
@@ -52472,7 +53005,11 @@ async function startServer(opts = {}) {
|
|
|
52472
53005
|
// The /ws producer is the one the live UI actually renders, so a dead owner has to reach it
|
|
52473
53006
|
// too — projecting only the RPC is the exact half-fix the × already had to correct once.
|
|
52474
53007
|
(slug) => c.tailer.ownerGone?.(slug) ?? false
|
|
52475
|
-
)
|
|
53008
|
+
),
|
|
53009
|
+
// A reader's live watch on the file it shows. Gated as the read is (one set of openable roots for
|
|
53010
|
+
// both readers, plus the Markdown reader's canonical-extension check) and watched by CANONICAL
|
|
53011
|
+
// path, so a symlinked `.md` and its target are one watch.
|
|
53012
|
+
watchFile: (path, onChange) => watchLocalFile(resolveWatchableLocalFile(path, openableFileRoots(c.project)), onChange)
|
|
52476
53013
|
});
|
|
52477
53014
|
let githubInit;
|
|
52478
53015
|
let terminal;
|
|
@@ -52616,7 +53153,7 @@ async function startServer(opts = {}) {
|
|
|
52616
53153
|
if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
|
|
52617
53154
|
if (statusPath && statusIdentity) {
|
|
52618
53155
|
try {
|
|
52619
|
-
runtime.removeStatus(serverAddressPathForStateDir(
|
|
53156
|
+
runtime.removeStatus(serverAddressPathForStateDir(dirname20(statusPath)), statusIdentity);
|
|
52620
53157
|
} catch {
|
|
52621
53158
|
}
|
|
52622
53159
|
}
|
|
@@ -52745,7 +53282,7 @@ async function startServer(opts = {}) {
|
|
|
52745
53282
|
}
|
|
52746
53283
|
statusPath = serverLockPathFor(ctx.project);
|
|
52747
53284
|
const webRoot = resolve11(import.meta.dirname, "..", "..", "web");
|
|
52748
|
-
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) :
|
|
53285
|
+
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join42(webRoot, "dist");
|
|
52749
53286
|
startupPhase = "Vite";
|
|
52750
53287
|
if (opts.dev) {
|
|
52751
53288
|
try {
|
|
@@ -52821,7 +53358,7 @@ async function startServer(opts = {}) {
|
|
|
52821
53358
|
if (vite) {
|
|
52822
53359
|
vite.middlewares(req, res, () => {
|
|
52823
53360
|
try {
|
|
52824
|
-
const html =
|
|
53361
|
+
const html = readFileSync29(join42(webRoot, "index.html"), "utf8");
|
|
52825
53362
|
void vite.transformIndexHtml(url, html).then((out) => {
|
|
52826
53363
|
res.writeHead(200, { "content-type": "text/html" });
|
|
52827
53364
|
res.end(out);
|
|
@@ -52975,6 +53512,8 @@ var init_index = __esm({
|
|
|
52975
53512
|
init_app_socket();
|
|
52976
53513
|
init_shutdown();
|
|
52977
53514
|
init_project();
|
|
53515
|
+
init_local_file();
|
|
53516
|
+
init_local_file_watch();
|
|
52978
53517
|
init_project_launch();
|
|
52979
53518
|
init_frizz_paths();
|
|
52980
53519
|
init_project_identity();
|
|
@@ -53026,7 +53565,7 @@ var init_index = __esm({
|
|
|
53026
53565
|
}
|
|
53027
53566
|
};
|
|
53028
53567
|
isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
|
|
53029
|
-
serverLockPathFor = (project) =>
|
|
53568
|
+
serverLockPathFor = (project) => join42(project.stateDir, "server.lock");
|
|
53030
53569
|
MIME = {
|
|
53031
53570
|
".html": "text/html",
|
|
53032
53571
|
".js": "text/javascript",
|
|
@@ -53045,7 +53584,7 @@ init_project_launch();
|
|
|
53045
53584
|
init_shutdown();
|
|
53046
53585
|
init_logging();
|
|
53047
53586
|
import { existsSync as existsSync16 } from "node:fs";
|
|
53048
|
-
import { join as
|
|
53587
|
+
import { join as join43 } from "node:path";
|
|
53049
53588
|
process.on("uncaughtException", (error) => {
|
|
53050
53589
|
log.error("dev-child", `uncaught exception: ${error instanceof Error ? error.stack ?? error.message : error}`);
|
|
53051
53590
|
process.exit(1);
|
|
@@ -53085,7 +53624,7 @@ try {
|
|
|
53085
53624
|
];
|
|
53086
53625
|
if (!existsSync16(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
|
|
53087
53626
|
for (const [name, directory, requiredFile] of required) {
|
|
53088
|
-
if (!directory || !existsSync16(
|
|
53627
|
+
if (!directory || !existsSync16(join43(directory, requiredFile)))
|
|
53089
53628
|
throw new Error(`stable artifact launch is missing verified ${name}`);
|
|
53090
53629
|
}
|
|
53091
53630
|
}
|