frizz 0.9.0 → 0.10.0
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 +869 -333
- 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-XNji7D_c.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-DNBrcJfV.js → abnfDiagram-VRR7QNED-gPeb-B7g.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-BsgSgexh.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-SXVydqly.js → architectureDiagram-ZJ3FMSHR-DV69ktbl.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-CgI1GSdx.js → blockDiagram-677ZJIJ3-D8vw5eim.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-KDmJm2tG.js → c4Diagram-LMCZKHZV--9PLGn-L.js} +1 -1
- package/web-dist/assets/channel-ClLnncxh.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-j3eOjq4Z.js → chunk-32BRIVSS-D4BSk0FZ.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-DZHfa78H.js → chunk-52WLFC77-WNcmTkcR.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-CiB5uo24.js → chunk-C7G6YPKG-BDlcLa5W.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-B35Inyjz.js → chunk-EX3LRPZG-CFnqFt7s.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-D2sRvM5U.js → chunk-FWX5IMBZ-Ci9LgOOg.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-D247PDv6.js → chunk-HOUHSVGY-BTD_X5OP.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-Bu3Tb6Ec.js → chunk-ICXQ74PX-D2Y3Ys5K.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-C_lQy4on.js → chunk-MOJQB5TN-C-GDt7vC.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-DU5ZBedR.js → chunk-OGEWGWER-DvnPoUnz.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-DWRT7ztS.js → chunk-PUDLZKDR-C3AymM-j.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-CCX4AK_n.js → chunk-Q4XR5HBZ-DmyBNAl6.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-VbixbkDG.js → chunk-V7JOEXUC-DTuPUeRa.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-PJvChxMA.js → chunk-VAUOI2AC-CcW8PviB.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-BjsMfxj8.js → chunk-VR4S4FIN-0tm6n83G.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-Bcb97r3w.js → chunk-WYO6CB5R-CH3ADqeI.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-CT2sZlhT.js → chunk-ZGVPDNZ5-BHFnlpXH.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-CwLnPgud.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-CwLnPgud.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-dUpO4u0w.js → cynefin-VYW2F7L2-DtFCLOIN.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-BQZ3UIsx.js → cynefinDiagram-TSTJHNR4-llFTHp2q.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-K-oO8w51.js → dagre-VKFMJZFB-CvBRVl_k.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-Cr47GJ_S.js → diagram-FQU43EPY-D1QXo6ri.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-BbWzSkoZ.js → diagram-G47NLZAW-2pZew-Bk.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-BGJJBgGM.js → diagram-NH7WQ7WH-Ch9JRwGN.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-2W7aunEN.js → diagram-OA4YK3LP-BIbrLf02.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-D-4ITFI6.js → diagram-WEI45ONY-D3KtL2s-.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-CxUcx103.js → ebnfDiagram-CCIWWBDH-D829sKG_.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-Cf_5SWAE.js → erDiagram-Q63AITRT-Cj3Grovg.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-CzZyv3HA.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-rQ7PRoGL.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-CVZI8Ywa.js → ganttDiagram-NO4QXBWP-CGHaXF-g.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-BsiGpR9l.js → gitGraph-TEB2WS4Q-BbxeaotX.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DVym_4l1.js → gitGraphDiagram-IHSO6WYX-C4WHE0cI.js} +1 -1
- package/web-dist/assets/index-BpCFdr7Z.css +1 -0
- package/web-dist/assets/index-CiaKj-0V.js +506 -0
- package/web-dist/assets/{info-DKCQHKI2-DYcsKFv-.js → info-DKCQHKI2-3fKpn7Qr.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-hjVxlrTT.js → infoDiagram-FWYZ7A6U-CDQl4JMv.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-B0DKOPSS.js → ishikawaDiagram-FXEZZL3T-CDKjMW-u.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-Cohibc5O.js → journeyDiagram-5HDEW3XC-BOsabu1Z.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-_6Zr31vM.js → kanban-definition-HUTT4EX6-KTuh-6eN.js} +1 -1
- package/web-dist/assets/{line-B14EZI6M.js → line-KaxbYSN3.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-DHkc6cqw.js → mermaid-parser.core-B53huyyd.js} +3 -3
- package/web-dist/assets/{mermaid.core-DuTvdsKf.js → mermaid.core-Bz6B5ouC.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DPRF9pW2.js → mindmap-definition-LN4V7U3C-Bb7T7B3a.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-Ci8oCcGM.js → packet-7NZHBO7P-w3lynC8r.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-Cr3ctC6e.js → pegDiagram-2B236MQR-nQ7c2Z03.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-EHgA307y.js → pie-RZYD4A2V-B3Yko5Wb.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-UW6Bmu6F.js → pieDiagram-ENE6RG2P-CN4Lp5HG.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-D6tAsRj9.js → quadrantDiagram-ABIIQ3AL-3QTB92dH.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-CAQqojcd.js → radar-I7S5WNFK-8ZsXt0FI.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-BZsAOTrm.js → railroad-3IZDKUUU-C4IKxzEZ.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-g2si5iUv.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-BikkLG6y.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-Dv4rDehz.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-bSpff55H.js → railroadDiagram-RFXS5EU6-DrGnmjpD.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-DIAYvXoB.js → requirementDiagram-TGXJPOKE-Bh9XNuxa.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-DEnsSz3F.js → sankeyDiagram-HTMAVEWB-CXy3ra5-.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-DsG7eb9R.js → sequenceDiagram-DBY2YBRQ-B7ESCT52.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-DT3XNwjo.js → stateDiagram-2N3HPSRC-DFr9k5Xv.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BQUd0MEa.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-B5hZwgRo.js → swimlanes-5IMT3BWC-B2RKoz1q.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-GWn4zXxj.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-BVTsjkfu.js → timeline-definition-FHXFAJF6-KpTqaG5g.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-M6HmP4bB.js → treeView-QDETBFTQ-oB1a6ikv.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-C60TppfC.js → treemap-6X3UGDF4-DO8OvvZg.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-Dv1y3g4S.js → vennDiagram-L72KCM5P-BgTjK4_q.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-D0lMJMWt.js → wardley-OPB4EBWU-CGyjn939.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-B1E5NtZY.js → wardleyDiagram-EHGQE667-CM_HxruS.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-oA91YOvh.js → xychartDiagram-FW5EYKEG-vamHu0jN.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"
|
|
@@ -19498,6 +19589,7 @@ function createStorage(source, projectId) {
|
|
|
19498
19589
|
// never be written without a deadline to fire it.
|
|
19499
19590
|
setSnoozedUntil: (slug, until, prompt = null) => void snoozedUntilStmt.run(until, until === null ? null : prompt, slug),
|
|
19500
19591
|
setSnoozedUntilIfCurrent: (slug, sessionId, generation, until) => snoozedUntilIfCurrentStmt.run(until, slug, sessionId, generation).changes === 1,
|
|
19592
|
+
setPinnedAt: (slug, at) => void pinnedAtStmt.run(at, slug),
|
|
19501
19593
|
setBgSnoozeRestedAtIfCurrent: (slug, sessionId, generation, restedAt) => bgSnoozeRestedAtIfCurrentStmt.run(restedAt, slug, sessionId, generation).changes === 1,
|
|
19502
19594
|
setRecurringPromptIfCurrent: (slug, sessionId, generation, write) => recurringStmt.run(...recurringArgs(write), slug, sessionId, generation).changes === 1,
|
|
19503
19595
|
setRecurringPromptBySlug: (slug, write) => recurringBySlugStmt.run(...recurringArgs(write), slug).changes === 1,
|
|
@@ -19505,7 +19597,7 @@ function createStorage(source, projectId) {
|
|
|
19505
19597
|
resetSignoffNudges: (slug) => void resetNudgesStmt.run(slug),
|
|
19506
19598
|
countParkBump: (slug, anchor) => void countParkBumpStmt.run(anchor, slug),
|
|
19507
19599
|
resetParkBumps: (slug) => void resetParkBumpsStmt.run(slug),
|
|
19508
|
-
armPrWatch: (
|
|
19600
|
+
armPrWatch: (watch2) => void armPrWatchStmt.run(watch2),
|
|
19509
19601
|
listPrWatches: (slug, opts) => (opts?.armedOnly ? armedPrWatchesBySlugStmt : prWatchesBySlugStmt).all(slug),
|
|
19510
19602
|
getPrWatch: (id) => prWatchByIdStmt.get(id),
|
|
19511
19603
|
armedPrWatches: () => armedPrWatchesStmt.all(),
|
|
@@ -19782,6 +19874,7 @@ var init_storage = __esm({
|
|
|
19782
19874
|
snoozed_until TEXT,
|
|
19783
19875
|
snooze_prompt TEXT,
|
|
19784
19876
|
bg_snooze_rested_at TEXT,
|
|
19877
|
+
pinned_at TEXT,
|
|
19785
19878
|
meta TEXT,
|
|
19786
19879
|
seen_at TEXT,
|
|
19787
19880
|
transcript_id TEXT,
|
|
@@ -20196,6 +20289,9 @@ var init_settings = __esm({
|
|
|
20196
20289
|
// 500K tokens: half the 1M window every Claude worker is dispatched with. See the schema for why a
|
|
20197
20290
|
// worker left to compact at 1M is the biggest single reason a Frizz thread out-spends the TUI.
|
|
20198
20291
|
autoCompactWindow: 5e5,
|
|
20292
|
+
// "auto" = the CLI's own tier (1h on a subscription). See the schema and claudePromptCacheEnv for
|
|
20293
|
+
// when "5m" is the cheaper choice.
|
|
20294
|
+
promptCacheTtl: "auto",
|
|
20199
20295
|
// Hidden until asked for — see the schema for why.
|
|
20200
20296
|
projectRail: false
|
|
20201
20297
|
});
|
|
@@ -20345,13 +20441,13 @@ function discoverTranscriptDir(logDir, sessionId, memo3 = strandedLogDirs) {
|
|
|
20345
20441
|
let best;
|
|
20346
20442
|
for (const dir of memo3) best = better(best, dir);
|
|
20347
20443
|
if (best) return best.dir;
|
|
20348
|
-
let
|
|
20444
|
+
let entries2;
|
|
20349
20445
|
try {
|
|
20350
|
-
|
|
20446
|
+
entries2 = readdirSync3(dirname7(logDir), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
20351
20447
|
} catch {
|
|
20352
20448
|
return void 0;
|
|
20353
20449
|
}
|
|
20354
|
-
for (const entry of
|
|
20450
|
+
for (const entry of entries2) best = better(best, join11(dirname7(logDir), entry));
|
|
20355
20451
|
if (!best) return void 0;
|
|
20356
20452
|
memo3.add(best.dir);
|
|
20357
20453
|
return best.dir;
|
|
@@ -21272,10 +21368,10 @@ function boundedJson(value, label, state, depth) {
|
|
|
21272
21368
|
return value.map((entry, index) => boundedJson(entry, `${label}[${index}]`, state, depth + 1));
|
|
21273
21369
|
}
|
|
21274
21370
|
if (!value || typeof value !== "object") throw new ClaudeAgentSdkProtocolError(`${label} contains a non-JSON value`);
|
|
21275
|
-
const
|
|
21276
|
-
if (
|
|
21371
|
+
const entries2 = Object.entries(value);
|
|
21372
|
+
if (entries2.length > 256) throw new ClaudeAgentSdkProtocolError(`${label} contains too many object fields`);
|
|
21277
21373
|
const output = {};
|
|
21278
|
-
for (const [key, entry] of
|
|
21374
|
+
for (const [key, entry] of entries2) {
|
|
21279
21375
|
if (RESERVED_KEYS.has(key) || utf8Bytes(key) > 256 || UNSAFE_TEXT2.test(key)) {
|
|
21280
21376
|
UNSAFE_TEXT2.lastIndex = 0;
|
|
21281
21377
|
throw new ClaudeAgentSdkProtocolError(`${label} contains an invalid object key`);
|
|
@@ -21414,10 +21510,10 @@ var init_claude_broker_diagnostics = __esm({
|
|
|
21414
21510
|
// packages/server/src/detached-daemons.ts
|
|
21415
21511
|
import { existsSync as existsSync4 } from "node:fs";
|
|
21416
21512
|
import { fileURLToPath } from "node:url";
|
|
21417
|
-
function resolveDetachedDaemonEntry(moduleUrl,
|
|
21418
|
-
const candidates = [`./${
|
|
21513
|
+
function resolveDetachedDaemonEntry(moduleUrl, basename14) {
|
|
21514
|
+
const candidates = [`./${basename14}.js`, `./${basename14}.ts`].map((rel) => fileURLToPath(new URL(rel, moduleUrl)));
|
|
21419
21515
|
const found = candidates.find((path) => existsSync4(path));
|
|
21420
|
-
if (!found) throw new Error(`frizz is missing the ${
|
|
21516
|
+
if (!found) throw new Error(`frizz is missing the ${basename14} entry \u2014 looked for ${candidates.join(" and ")}`);
|
|
21421
21517
|
return found;
|
|
21422
21518
|
}
|
|
21423
21519
|
var init_detached_daemons = __esm({
|
|
@@ -22537,8 +22633,8 @@ function createTailStateCache(scope, schema = foldSchemaDigest()) {
|
|
|
22537
22633
|
}
|
|
22538
22634
|
return map;
|
|
22539
22635
|
},
|
|
22540
|
-
put(
|
|
22541
|
-
if (!usable || !upsert ||
|
|
22636
|
+
put(entries2) {
|
|
22637
|
+
if (!usable || !upsert || entries2.length === 0) return;
|
|
22542
22638
|
try {
|
|
22543
22639
|
const write = db.transaction((batch) => {
|
|
22544
22640
|
for (const entry of batch) {
|
|
@@ -22559,7 +22655,7 @@ function createTailStateCache(scope, schema = foldSchemaDigest()) {
|
|
|
22559
22655
|
});
|
|
22560
22656
|
}
|
|
22561
22657
|
});
|
|
22562
|
-
write(
|
|
22658
|
+
write(entries2);
|
|
22563
22659
|
} catch {
|
|
22564
22660
|
}
|
|
22565
22661
|
},
|
|
@@ -24132,7 +24228,7 @@ ${ask}`;
|
|
|
24132
24228
|
}
|
|
24133
24229
|
}
|
|
24134
24230
|
if (cacheDirty.size === 0) return;
|
|
24135
|
-
const
|
|
24231
|
+
const entries2 = [];
|
|
24136
24232
|
try {
|
|
24137
24233
|
for (const key of cacheDirty) {
|
|
24138
24234
|
const foreign = key.startsWith("foreign:");
|
|
@@ -24141,12 +24237,12 @@ ${ask}`;
|
|
|
24141
24237
|
const row = foreign ? null : deps.storage.getSession(key) ?? null;
|
|
24142
24238
|
if (!foreign && !row) continue;
|
|
24143
24239
|
const entry = cacheSnapshot(state, row);
|
|
24144
|
-
if (entry)
|
|
24240
|
+
if (entry) entries2.push(entry);
|
|
24145
24241
|
}
|
|
24146
24242
|
} catch {
|
|
24147
24243
|
}
|
|
24148
24244
|
cacheDirty.clear();
|
|
24149
|
-
tailCache.put(
|
|
24245
|
+
tailCache.put(entries2);
|
|
24150
24246
|
}
|
|
24151
24247
|
function consume(state, backend, onLine) {
|
|
24152
24248
|
let size;
|
|
@@ -25140,9 +25236,9 @@ function stringifyOutput(output) {
|
|
|
25140
25236
|
}
|
|
25141
25237
|
function collectRollouts(dir, out, budget) {
|
|
25142
25238
|
if (budget.n <= 0) return;
|
|
25143
|
-
const
|
|
25144
|
-
const dirs =
|
|
25145
|
-
const files =
|
|
25239
|
+
const entries2 = safeReaddir(dir);
|
|
25240
|
+
const dirs = entries2.filter((e) => e.isDirectory()).sort(descByName);
|
|
25241
|
+
const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).sort(descByName);
|
|
25146
25242
|
for (const d of dirs) {
|
|
25147
25243
|
if (budget.n <= 0) return;
|
|
25148
25244
|
collectRollouts(join19(dir, d.name), out, budget);
|
|
@@ -28715,7 +28811,7 @@ var require_stream = __commonJS({
|
|
|
28715
28811
|
terminateOnDestroy = false;
|
|
28716
28812
|
duplex.destroy(err);
|
|
28717
28813
|
});
|
|
28718
|
-
ws.once("close", function
|
|
28814
|
+
ws.once("close", function close2() {
|
|
28719
28815
|
if (duplex.destroyed) return;
|
|
28720
28816
|
duplex.push(null);
|
|
28721
28817
|
});
|
|
@@ -28730,7 +28826,7 @@ var require_stream = __commonJS({
|
|
|
28730
28826
|
called = true;
|
|
28731
28827
|
callback(err2);
|
|
28732
28828
|
});
|
|
28733
|
-
ws.once("close", function
|
|
28829
|
+
ws.once("close", function close2() {
|
|
28734
28830
|
if (!called) callback(err);
|
|
28735
28831
|
process.nextTick(emitClose, duplex);
|
|
28736
28832
|
});
|
|
@@ -29262,13 +29358,18 @@ function claudeCompactionEnv(settings) {
|
|
|
29262
29358
|
if (window === void 0 || !Number.isInteger(window) || window <= 0) return {};
|
|
29263
29359
|
return { [CLAUDE_AUTO_COMPACT_WINDOW_ENV]: String(window) };
|
|
29264
29360
|
}
|
|
29361
|
+
function claudePromptCacheEnv(settings) {
|
|
29362
|
+
const ttl = settings?.promptCacheTtl;
|
|
29363
|
+
if (ttl !== "5m" && ttl !== "1h") return {};
|
|
29364
|
+
return { [CLAUDE_PROMPT_CACHE_TTL_ENV]: ttl, [CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV]: ttl };
|
|
29365
|
+
}
|
|
29265
29366
|
function claudeCompactionWindowOf(env) {
|
|
29266
29367
|
const raw2 = env?.[CLAUDE_AUTO_COMPACT_WINDOW_ENV];
|
|
29267
29368
|
if (raw2 === void 0) return void 0;
|
|
29268
29369
|
const window = Number(raw2);
|
|
29269
29370
|
return Number.isInteger(window) && window > 0 ? window : void 0;
|
|
29270
29371
|
}
|
|
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;
|
|
29372
|
+
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
29373
|
var init_types2 = __esm({
|
|
29273
29374
|
"packages/server/src/backend/types.ts"() {
|
|
29274
29375
|
"use strict";
|
|
@@ -29286,6 +29387,8 @@ var init_types2 = __esm({
|
|
|
29286
29387
|
BASH_MAX_TIMEOUT_MS: String(24 * 60 * 60 * 1e3)
|
|
29287
29388
|
};
|
|
29288
29389
|
CLAUDE_AUTO_COMPACT_WINDOW_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW";
|
|
29390
|
+
CLAUDE_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_PROMPT_CACHE_TTL";
|
|
29391
|
+
CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL";
|
|
29289
29392
|
WORKER_DISALLOWED_TOOLS = ["AskUserQuestion"];
|
|
29290
29393
|
}
|
|
29291
29394
|
});
|
|
@@ -29294,18 +29397,18 @@ var init_types2 = __esm({
|
|
|
29294
29397
|
function tomlString(value) {
|
|
29295
29398
|
return JSON.stringify(value);
|
|
29296
29399
|
}
|
|
29297
|
-
function inlineTable(
|
|
29298
|
-
return `{${
|
|
29400
|
+
function inlineTable(entries2) {
|
|
29401
|
+
return `{${entries2.map(([k, v]) => `${k}=${v}`).join(",")}}`;
|
|
29299
29402
|
}
|
|
29300
29403
|
function serverTable(command, args, env) {
|
|
29301
|
-
const
|
|
29404
|
+
const entries2 = [
|
|
29302
29405
|
["command", tomlString(command)],
|
|
29303
29406
|
["args", `[${args.map(tomlString).join(",")}]`]
|
|
29304
29407
|
];
|
|
29305
29408
|
if (env && Object.keys(env).length > 0) {
|
|
29306
|
-
|
|
29409
|
+
entries2.push(["env", inlineTable(Object.entries(env).map(([k, v]) => [k, tomlString(v)]))]);
|
|
29307
29410
|
}
|
|
29308
|
-
return inlineTable(
|
|
29411
|
+
return inlineTable(entries2);
|
|
29309
29412
|
}
|
|
29310
29413
|
function codexMcpConfigArgs(frizzMcp, nodeBin = process.execPath) {
|
|
29311
29414
|
const args = [];
|
|
@@ -30413,10 +30516,10 @@ function mcpFields(raw2) {
|
|
|
30413
30516
|
if (schema.type !== "object" || schema.properties === null || typeof schema.properties !== "object" || Array.isArray(schema.properties)) {
|
|
30414
30517
|
throw new RpcProtocolError(-32602, "MCP elicitation schema must describe object properties");
|
|
30415
30518
|
}
|
|
30416
|
-
const
|
|
30417
|
-
if (
|
|
30519
|
+
const entries2 = Object.entries(schema.properties).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
30520
|
+
if (entries2.length > 32) throw new RpcProtocolError(-32602, "MCP elicitation has too many fields");
|
|
30418
30521
|
const required = new Set(Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : []);
|
|
30419
|
-
return
|
|
30522
|
+
return entries2.map(([id, field]) => mcpField(id, field, required.has(id)));
|
|
30420
30523
|
}
|
|
30421
30524
|
function userInputFields(questions) {
|
|
30422
30525
|
return questions.map((question) => {
|
|
@@ -32841,14 +32944,14 @@ function sessionsDir2(codexHome) {
|
|
|
32841
32944
|
}
|
|
32842
32945
|
function newestRollouts(dir, out, budget) {
|
|
32843
32946
|
if (budget.n <= 0) return;
|
|
32844
|
-
let
|
|
32947
|
+
let entries2;
|
|
32845
32948
|
try {
|
|
32846
|
-
|
|
32949
|
+
entries2 = readdirSync7(dir, { withFileTypes: true });
|
|
32847
32950
|
} catch {
|
|
32848
32951
|
return;
|
|
32849
32952
|
}
|
|
32850
|
-
const dirs =
|
|
32851
|
-
const files =
|
|
32953
|
+
const dirs = entries2.filter((e) => e.isDirectory()).map((e) => e.name).sort(descByName2);
|
|
32954
|
+
const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
|
|
32852
32955
|
for (const d of dirs) {
|
|
32853
32956
|
if (budget.n <= 0) return;
|
|
32854
32957
|
newestRollouts(join22(dir, d), out, budget);
|
|
@@ -33607,7 +33710,7 @@ function readAwaitingPark(hints) {
|
|
|
33607
33710
|
if (isAwaitingItemKind(h.kind)) {
|
|
33608
33711
|
if (value) items.push({ kind: h.kind, value });
|
|
33609
33712
|
} else if (h.kind === "for") {
|
|
33610
|
-
forMs =
|
|
33713
|
+
forMs = parseAwaitingDurationRaw(value);
|
|
33611
33714
|
}
|
|
33612
33715
|
}
|
|
33613
33716
|
return { items, forMs };
|
|
@@ -33625,9 +33728,13 @@ function parkIsHonoured(park, live) {
|
|
|
33625
33728
|
if (park.forMs === null) return false;
|
|
33626
33729
|
return unaccountedItems(park.items, live).length === 0;
|
|
33627
33730
|
}
|
|
33731
|
+
function parkForMaxMs(park) {
|
|
33732
|
+
if (park.items.length === 0) return AWAITING_FOR_MAX_MS;
|
|
33733
|
+
return park.items.every((i) => i.kind === "pr") ? PR_WATCH_FOR_MAX_MS : AWAITING_FOR_MAX_MS;
|
|
33734
|
+
}
|
|
33628
33735
|
function parkExpiresAt(park, fenceAtMs) {
|
|
33629
33736
|
if (park.forMs === null || !Number.isFinite(fenceAtMs)) return null;
|
|
33630
|
-
return fenceAtMs + Math.min(park.forMs,
|
|
33737
|
+
return fenceAtMs + Math.min(park.forMs, parkForMaxMs(park));
|
|
33631
33738
|
}
|
|
33632
33739
|
function readGithubStatusBook(raw2) {
|
|
33633
33740
|
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) return {};
|
|
@@ -33657,18 +33764,148 @@ var init_awaiting = __esm({
|
|
|
33657
33764
|
}
|
|
33658
33765
|
});
|
|
33659
33766
|
|
|
33767
|
+
// packages/server/src/backend/project-mcp-servers.ts
|
|
33768
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
33769
|
+
import { homedir as homedir10 } from "node:os";
|
|
33770
|
+
import { join as join25 } from "node:path";
|
|
33771
|
+
function readJsonObject(path) {
|
|
33772
|
+
try {
|
|
33773
|
+
const parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
33774
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
33775
|
+
} catch {
|
|
33776
|
+
return void 0;
|
|
33777
|
+
}
|
|
33778
|
+
}
|
|
33779
|
+
function objectField(source, key) {
|
|
33780
|
+
const value = source?.[key];
|
|
33781
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
33782
|
+
}
|
|
33783
|
+
function stringList(source, key) {
|
|
33784
|
+
const value = source?.[key];
|
|
33785
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
33786
|
+
}
|
|
33787
|
+
function stringMap(value) {
|
|
33788
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
33789
|
+
const out = {};
|
|
33790
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
33791
|
+
if (typeof entry === "string") out[key] = entry;
|
|
33792
|
+
}
|
|
33793
|
+
return out;
|
|
33794
|
+
}
|
|
33795
|
+
function expandMcpEnvRefs(value, env) {
|
|
33796
|
+
let unresolved = false;
|
|
33797
|
+
const out = value.replace(ENV_REF, (_match, name, fallback) => {
|
|
33798
|
+
const resolved = env[name];
|
|
33799
|
+
if (resolved !== void 0) return resolved;
|
|
33800
|
+
if (fallback !== void 0) return fallback;
|
|
33801
|
+
unresolved = true;
|
|
33802
|
+
return "";
|
|
33803
|
+
});
|
|
33804
|
+
return unresolved ? void 0 : out;
|
|
33805
|
+
}
|
|
33806
|
+
function expandMap(map, env) {
|
|
33807
|
+
if (!map) return void 0;
|
|
33808
|
+
const out = {};
|
|
33809
|
+
for (const [key, value] of Object.entries(map)) {
|
|
33810
|
+
const expanded = expandMcpEnvRefs(value, env);
|
|
33811
|
+
if (expanded === void 0) return null;
|
|
33812
|
+
out[key] = expanded;
|
|
33813
|
+
}
|
|
33814
|
+
return out;
|
|
33815
|
+
}
|
|
33816
|
+
function normalizeMcpServer(raw2, env) {
|
|
33817
|
+
if (raw2 === null || typeof raw2 !== "object" || Array.isArray(raw2)) return void 0;
|
|
33818
|
+
const record = raw2;
|
|
33819
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
33820
|
+
if (type === "http" || type === "sse") {
|
|
33821
|
+
if (typeof record.url !== "string") return void 0;
|
|
33822
|
+
const url = expandMcpEnvRefs(record.url, env);
|
|
33823
|
+
const headers = expandMap(stringMap(record.headers), env);
|
|
33824
|
+
if (url === void 0 || headers === null) return void 0;
|
|
33825
|
+
return { type, url, ...headers ? { headers } : {} };
|
|
33826
|
+
}
|
|
33827
|
+
if (type !== void 0 && type !== "stdio") return void 0;
|
|
33828
|
+
if (typeof record.command !== "string") return void 0;
|
|
33829
|
+
const command = expandMcpEnvRefs(record.command, env);
|
|
33830
|
+
if (command === void 0) return void 0;
|
|
33831
|
+
const args = [];
|
|
33832
|
+
for (const entry of Array.isArray(record.args) ? record.args : []) {
|
|
33833
|
+
if (typeof entry !== "string") return void 0;
|
|
33834
|
+
const expanded = expandMcpEnvRefs(entry, env);
|
|
33835
|
+
if (expanded === void 0) return void 0;
|
|
33836
|
+
args.push(expanded);
|
|
33837
|
+
}
|
|
33838
|
+
const serverEnv = expandMap(stringMap(record.env), env);
|
|
33839
|
+
if (serverEnv === null) return void 0;
|
|
33840
|
+
return { command, ...args.length ? { args } : {}, ...serverEnv ? { env: serverEnv } : {} };
|
|
33841
|
+
}
|
|
33842
|
+
function collectApproval(layers) {
|
|
33843
|
+
const approval = { all: false, enabled: /* @__PURE__ */ new Set(), disabled: /* @__PURE__ */ new Set() };
|
|
33844
|
+
for (const layer of layers) {
|
|
33845
|
+
if (layer?.enableAllProjectMcpServers === true) approval.all = true;
|
|
33846
|
+
for (const name of stringList(layer, "enabledMcpjsonServers")) approval.enabled.add(name);
|
|
33847
|
+
for (const name of stringList(layer, "disabledMcpjsonServers")) approval.disabled.add(name);
|
|
33848
|
+
}
|
|
33849
|
+
return approval;
|
|
33850
|
+
}
|
|
33851
|
+
function userScopeRemoteServers(account, cwd, env) {
|
|
33852
|
+
const out = {};
|
|
33853
|
+
const maps = [objectField(account, "mcpServers"), objectField(objectField(objectField(account, "projects"), cwd), "mcpServers")];
|
|
33854
|
+
for (const map of maps) {
|
|
33855
|
+
for (const [name, raw2] of Object.entries(map ?? {})) {
|
|
33856
|
+
const server = normalizeMcpServer(raw2, env);
|
|
33857
|
+
if (server && server.type !== void 0 && server.type !== "stdio") out[name] = server;
|
|
33858
|
+
}
|
|
33859
|
+
}
|
|
33860
|
+
return out;
|
|
33861
|
+
}
|
|
33862
|
+
function projectMcpServers(cwd, options = {}) {
|
|
33863
|
+
const env = options.env ?? process.env;
|
|
33864
|
+
const home = options.home ?? homedir10();
|
|
33865
|
+
const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
|
|
33866
|
+
const accountFile = configOverride ? join25(configOverride, ".claude.json") : join25(home, ".claude.json");
|
|
33867
|
+
const userSettingsFile = join25(configOverride ?? join25(home, ".claude"), "settings.json");
|
|
33868
|
+
const account = readJsonObject(accountFile);
|
|
33869
|
+
const out = userScopeRemoteServers(account, cwd, env);
|
|
33870
|
+
const declared = objectField(readJsonObject(join25(cwd, ".mcp.json")), "mcpServers");
|
|
33871
|
+
if (!declared) return out;
|
|
33872
|
+
const approval = collectApproval([
|
|
33873
|
+
readJsonObject(join25(cwd, ".claude", "settings.json")),
|
|
33874
|
+
readJsonObject(join25(cwd, ".claude", "settings.local.json")),
|
|
33875
|
+
readJsonObject(userSettingsFile),
|
|
33876
|
+
objectField(objectField(account, "projects"), cwd)
|
|
33877
|
+
]);
|
|
33878
|
+
for (const [name, raw2] of Object.entries(declared)) {
|
|
33879
|
+
if (approval.disabled.has(name)) continue;
|
|
33880
|
+
if (!approval.all && !approval.enabled.has(name)) continue;
|
|
33881
|
+
const server = normalizeMcpServer(raw2, env);
|
|
33882
|
+
if (server) out[name] = server;
|
|
33883
|
+
}
|
|
33884
|
+
return out;
|
|
33885
|
+
}
|
|
33886
|
+
function workerMcpServers(project, frizz) {
|
|
33887
|
+
return { ...project ?? {}, ...frizz ?? {} };
|
|
33888
|
+
}
|
|
33889
|
+
var ENV_REF;
|
|
33890
|
+
var init_project_mcp_servers = __esm({
|
|
33891
|
+
"packages/server/src/backend/project-mcp-servers.ts"() {
|
|
33892
|
+
"use strict";
|
|
33893
|
+
ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
33894
|
+
}
|
|
33895
|
+
});
|
|
33896
|
+
|
|
33660
33897
|
// packages/server/src/worker-plugin-dir.ts
|
|
33661
33898
|
import { existsSync as existsSync8 } from "node:fs";
|
|
33662
|
-
import { dirname as dirname10, join as
|
|
33899
|
+
import { dirname as dirname10, join as join26 } from "node:path";
|
|
33663
33900
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
33664
33901
|
function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
|
|
33665
33902
|
const override = env.FRIZZ_WORKER_PLUGIN_DIR;
|
|
33666
|
-
if (override && existsSync8(
|
|
33903
|
+
if (override && existsSync8(join26(override, ".claude-plugin", "plugin.json")))
|
|
33667
33904
|
return override;
|
|
33668
33905
|
let current = dirname10(fileURLToPath2(moduleUrl));
|
|
33669
33906
|
for (; ; ) {
|
|
33670
|
-
const candidate =
|
|
33671
|
-
if (existsSync8(
|
|
33907
|
+
const candidate = join26(current, "cc-worker");
|
|
33908
|
+
if (existsSync8(join26(candidate, ".claude-plugin", "plugin.json"))) return candidate;
|
|
33672
33909
|
const parent = dirname10(current);
|
|
33673
33910
|
if (parent === current) return void 0;
|
|
33674
33911
|
current = parent;
|
|
@@ -33697,6 +33934,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
|
|
|
33697
33934
|
AGENT_COMPLETION,
|
|
33698
33935
|
VISUAL_EVIDENCE,
|
|
33699
33936
|
QUALITY_BAR,
|
|
33937
|
+
TOKEN_ECONOMY,
|
|
33700
33938
|
QUESTIONS,
|
|
33701
33939
|
STOP_CRITERION,
|
|
33702
33940
|
lean ? null : TRIVIAL_PROMPTS
|
|
@@ -33706,7 +33944,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
|
|
|
33706
33944
|
out = out.replaceAll("{{FRIZZ_MONITORS_DIR}}", opts.monitorsDir?.trim() || MONITORS_DIR_FALLBACK);
|
|
33707
33945
|
return out;
|
|
33708
33946
|
}
|
|
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;
|
|
33947
|
+
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
33948
|
var init_workerPrompt = __esm({
|
|
33711
33949
|
"packages/server/src/workerPrompt.ts"() {
|
|
33712
33950
|
"use strict";
|
|
@@ -33914,7 +34152,14 @@ exactly ONE of them.
|
|
|
33914
34152
|
Register FIRST: this line states the wait, it does not create one. That tool wakes you on CI
|
|
33915
34153
|
turning green or red, and on every later review, approval or comment, for as long as the PR lives.
|
|
33916
34154
|
- \`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.
|
|
34155
|
+
out frizz brings you back to re-check everything; re-parking is fine and uncapped. Capped at a day,
|
|
34156
|
+
except on a park naming ONLY \`prs:\`, where it runs to a year.
|
|
34157
|
+
- **A PULL REQUEST IN SOMEONE ELSE'S REPO TAKES MONTHS, SO ASK FOR MONTHS** \u2014 \`for: 180d\`, and give
|
|
34158
|
+
\`mcp__frizz__watch_pr\` the same. It moves on its maintainers' clock, not yours, so a short \`for:\`
|
|
34159
|
+
expires against a PR nothing has touched: a wake carrying no news, and a re-arm, once per expiry
|
|
34160
|
+
until somebody notices. Long is free \u2014 real activity wakes you the moment it lands either way, and
|
|
34161
|
+
the human snoozes or archives the thread when they want it gone. Hours are for a PR you control and
|
|
34162
|
+
a check you expect to settle today.
|
|
33918
34163
|
- \`title:\` \u2014 OPTIONAL, the card's heading: **40 characters or fewer**, sentence case, naming THIS
|
|
33919
34164
|
wait ("Fork-CI approval", "Nightly bench, arm 3 of 3"); longer is trimmed on a word boundary.
|
|
33920
34165
|
Without it the card is headed "Awaiting", which is true of every park and specific to none.
|
|
@@ -34001,6 +34246,19 @@ reads when images are unavailable.`;
|
|
|
34001
34246
|
formatter, a rename sweep) \u2014 run it, then name the files it touched in your handoff.
|
|
34002
34247
|
- Ground every load-bearing claim in code, a command, or a doc you actually read \u2014 never memory. State
|
|
34003
34248
|
plainly what failed, what you skipped, and what you could not verify.`;
|
|
34249
|
+
TOKEN_ECONOMY = `## Every message you send re-reads your whole context
|
|
34250
|
+
|
|
34251
|
+
The API call is the unit of cost, not the tokens you type: each message re-reads your entire context
|
|
34252
|
+
(150k\u2013450k tokens for most of an effort) whether it carries one tool call or ten.
|
|
34253
|
+
|
|
34254
|
+
- **Batch independent tool calls into ONE message.** List privately what you need next; then request
|
|
34255
|
+
every item that does not depend on another's result in the same response.
|
|
34256
|
+
- **One Bash call, several commands.** Chain the steps of one job with \`&&\` / \`;\` and print
|
|
34257
|
+
labelled sections. Use absolute paths; never spend a call on \`cd\` alone.
|
|
34258
|
+
- **Never poll from the turn.** A \`sleep\`-and-check loop is a full re-read every iteration. A
|
|
34259
|
+
background shell, a sub-agent or a registered watch wakes you when the thing is done.
|
|
34260
|
+
- **Finish the turn's whole job before you rest.** Every wake is a new turn on the full context, so
|
|
34261
|
+
do not rest on a step you could take now.`;
|
|
34004
34262
|
QUESTIONS = `## Questions for the human
|
|
34005
34263
|
|
|
34006
34264
|
You run under a dashboard, not a live chat, so your FINAL MESSAGE is the whole interface. Open with 2-4
|
|
@@ -34055,10 +34313,11 @@ fence is answerable only while its message is on screen, so a question written i
|
|
|
34055
34313
|
the transcript scrolls or your context is compacted, while a registration is still there tomorrow. It
|
|
34056
34314
|
takes \`questions\` \u2014 several at once, each with \`question\`, \`kind\` (\`question\` = pick one,
|
|
34057
34315
|
\`multi\` = pick several, no options at all = free text), an optional \`danger\`, and \`options\` each
|
|
34058
|
-
carrying a \`label\`, a one
|
|
34059
|
-
|
|
34060
|
-
|
|
34061
|
-
|
|
34316
|
+
carrying a \`label\`, a \`description\` (one line for a simple fork, or FULL MARKDOWN over several
|
|
34317
|
+
lines \u2014 a list of consequences, a code block, the diff an option would produce, the message that would
|
|
34318
|
+
be posted; it renders inside the option, so the human sees it BEFORE choosing), an optional
|
|
34319
|
+
\`recommended\`, and optional \`followUps\`: questions that become live ONLY if that option is taken,
|
|
34320
|
+
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
34321
|
it carries no timeout and waits on a person, so keep doing everything that does not depend on the
|
|
34063
34322
|
answer. The answer comes back as its own wake, restating what was asked. Withdraw one you no longer
|
|
34064
34323
|
need with \`mcp__frizz__unask\`, above all when you work the answer out yourself. **ON A THREAD
|
|
@@ -34793,12 +35052,12 @@ var init_claude_broker_client = __esm({
|
|
|
34793
35052
|
});
|
|
34794
35053
|
|
|
34795
35054
|
// packages/server/src/backend/claude-permission-interactions.ts
|
|
34796
|
-
import { homedir as
|
|
35055
|
+
import { homedir as homedir11 } from "node:os";
|
|
34797
35056
|
function clip(text2, max) {
|
|
34798
35057
|
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
34799
35058
|
}
|
|
34800
35059
|
function tildePath(path) {
|
|
34801
|
-
const home =
|
|
35060
|
+
const home = homedir11();
|
|
34802
35061
|
if (!path.startsWith(home)) return path;
|
|
34803
35062
|
const rest = path.slice(home.length);
|
|
34804
35063
|
return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path;
|
|
@@ -35044,7 +35303,7 @@ var init_claude_permission_interactions = __esm({
|
|
|
35044
35303
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
35045
35304
|
function withFrizzThreadSlug(servers, slug) {
|
|
35046
35305
|
const frizz = servers?.[FRIZZ_MCP.name];
|
|
35047
|
-
if (!frizz) return servers;
|
|
35306
|
+
if (!frizz || !("command" in frizz)) return servers;
|
|
35048
35307
|
return { ...servers, [FRIZZ_MCP.name]: { ...frizz, env: { ...frizz.env, FRIZZ_THREAD_SLUG: slug } } };
|
|
35049
35308
|
}
|
|
35050
35309
|
function claudeBrokerBridgeEnabled(env = process.env) {
|
|
@@ -35172,6 +35431,7 @@ function createClaudeAgentBrokerBridge(deps) {
|
|
|
35172
35431
|
// The auto-compact ceiling, from Settings (500K by default). Without it a `[1m]` worker compacts
|
|
35173
35432
|
// only near 1M, re-sending up to 5x a TUI session's conversation on every turn past 200K.
|
|
35174
35433
|
...claudeCompactionEnv(deps.getSettings?.()),
|
|
35434
|
+
...claudePromptCacheEnv(deps.getSettings?.()),
|
|
35175
35435
|
...we?.permDir ? { FRIZZ_PERM_DIR: we.permDir } : {},
|
|
35176
35436
|
// The cc-worker plugin's PreToolUse hook DENIES AskUserQuestion, because without frizz in the
|
|
35177
35437
|
// loop a blocking question freezes a headless worker where nobody can answer it. On the broker path
|
|
@@ -35447,19 +35707,19 @@ var init_claude_agent_broker_bridge = __esm({
|
|
|
35447
35707
|
});
|
|
35448
35708
|
|
|
35449
35709
|
// packages/server/src/backend/auth-status.ts
|
|
35450
|
-
import { join as
|
|
35451
|
-
import { homedir as
|
|
35452
|
-
import { readFileSync as
|
|
35710
|
+
import { join as join27 } from "node:path";
|
|
35711
|
+
import { homedir as homedir12, platform as platform2 } from "node:os";
|
|
35712
|
+
import { readFileSync as readFileSync17, statSync as statSync11 } from "node:fs";
|
|
35453
35713
|
import { execFile as execFile4 } from "node:child_process";
|
|
35454
35714
|
import { promisify as promisify4 } from "node:util";
|
|
35455
35715
|
function claudeConfigDir2() {
|
|
35456
35716
|
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
35457
|
-
return override && override.trim() ? override :
|
|
35717
|
+
return override && override.trim() ? override : join27(homedir12(), ".claude");
|
|
35458
35718
|
}
|
|
35459
35719
|
function claudeFileState(configDir) {
|
|
35460
35720
|
let raw2;
|
|
35461
35721
|
try {
|
|
35462
|
-
raw2 =
|
|
35722
|
+
raw2 = readFileSync17(join27(configDir, ".credentials.json"), "utf8");
|
|
35463
35723
|
} catch (err) {
|
|
35464
35724
|
return err.code === "ENOENT" ? "absent" : "error";
|
|
35465
35725
|
}
|
|
@@ -35492,7 +35752,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
|
|
|
35492
35752
|
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
|
|
35493
35753
|
let raw2;
|
|
35494
35754
|
try {
|
|
35495
|
-
raw2 =
|
|
35755
|
+
raw2 = readFileSync17(join27(codexHome, "auth.json"), "utf8");
|
|
35496
35756
|
} catch (err) {
|
|
35497
35757
|
return err.code === "ENOENT" ? "signed-out" : "unknown";
|
|
35498
35758
|
}
|
|
@@ -35557,7 +35817,7 @@ function asEmail(value) {
|
|
|
35557
35817
|
}
|
|
35558
35818
|
function claudeAccountFile() {
|
|
35559
35819
|
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
35560
|
-
return override && override.trim() ?
|
|
35820
|
+
return override && override.trim() ? join27(override, ".claude.json") : join27(homedir12(), ".claude.json");
|
|
35561
35821
|
}
|
|
35562
35822
|
function readClaudeAccountEmail(path = claudeAccountFile()) {
|
|
35563
35823
|
let stat2;
|
|
@@ -35570,7 +35830,7 @@ function readClaudeAccountEmail(path = claudeAccountFile()) {
|
|
|
35570
35830
|
if (memo3 && memo3.path === path && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
|
|
35571
35831
|
let email;
|
|
35572
35832
|
try {
|
|
35573
|
-
const doc = JSON.parse(
|
|
35833
|
+
const doc = JSON.parse(readFileSync17(path, "utf8"));
|
|
35574
35834
|
email = asEmail(doc?.oauthAccount?.emailAddress);
|
|
35575
35835
|
} catch {
|
|
35576
35836
|
email = void 0;
|
|
@@ -35581,7 +35841,7 @@ function readClaudeAccountEmail(path = claudeAccountFile()) {
|
|
|
35581
35841
|
function readCodexAccountEmail(codexHome = defaultCodexHome()) {
|
|
35582
35842
|
let doc;
|
|
35583
35843
|
try {
|
|
35584
|
-
doc = JSON.parse(
|
|
35844
|
+
doc = JSON.parse(readFileSync17(join27(codexHome, "auth.json"), "utf8"));
|
|
35585
35845
|
} catch {
|
|
35586
35846
|
return void 0;
|
|
35587
35847
|
}
|
|
@@ -35649,8 +35909,8 @@ var init_auth_status = __esm({
|
|
|
35649
35909
|
});
|
|
35650
35910
|
|
|
35651
35911
|
// 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
|
|
35912
|
+
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";
|
|
35913
|
+
import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
|
|
35654
35914
|
import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
|
|
35655
35915
|
function fallbackTitle(prompt) {
|
|
35656
35916
|
const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
|
|
@@ -35669,7 +35929,7 @@ function fallbackTitle(prompt) {
|
|
|
35669
35929
|
}
|
|
35670
35930
|
function resolveSlug(frizzDir, base, taken) {
|
|
35671
35931
|
base = ThreadSlug.parse(base);
|
|
35672
|
-
const isTaken = (slug) => existsSync9(
|
|
35932
|
+
const isTaken = (slug) => existsSync9(join28(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
|
|
35673
35933
|
if (!isTaken(base)) return base;
|
|
35674
35934
|
for (let n = 2; ; n++) {
|
|
35675
35935
|
const suffix = `-${n}`;
|
|
@@ -35686,12 +35946,12 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
35686
35946
|
if (!parsed.success) return null;
|
|
35687
35947
|
try {
|
|
35688
35948
|
const projectRoot = realpathSync7(projectDir);
|
|
35689
|
-
const frizzPath =
|
|
35949
|
+
const frizzPath = join28(projectRoot, ".frizz");
|
|
35690
35950
|
const frizzStat = lstatSync3(frizzPath);
|
|
35691
35951
|
if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
|
|
35692
35952
|
const realFrizz = realpathSync7(frizzPath);
|
|
35693
35953
|
if (dirname11(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
|
|
35694
|
-
const path =
|
|
35954
|
+
const path = join28(realFrizz, `${parsed.data}.md`);
|
|
35695
35955
|
const before = lstatSync3(path);
|
|
35696
35956
|
if (!before.isFile() || before.isSymbolicLink()) return null;
|
|
35697
35957
|
const realPath = realpathSync7(path);
|
|
@@ -35702,7 +35962,7 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
35702
35962
|
const fd = openSync13(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
35703
35963
|
try {
|
|
35704
35964
|
openedBefore = fstatSync2(fd);
|
|
35705
|
-
contents =
|
|
35965
|
+
contents = readFileSync18(fd);
|
|
35706
35966
|
openedAfter = fstatSync2(fd);
|
|
35707
35967
|
} finally {
|
|
35708
35968
|
closeSync12(fd);
|
|
@@ -35735,7 +35995,7 @@ function boardAuthorizesAdoption(board, slug) {
|
|
|
35735
35995
|
return !board.errorItems.some((item) => item.file === `${slug}.md`);
|
|
35736
35996
|
}
|
|
35737
35997
|
function ensureSafeDirectDirectory(parent, name) {
|
|
35738
|
-
const path =
|
|
35998
|
+
const path = join28(parent, name);
|
|
35739
35999
|
try {
|
|
35740
36000
|
mkdirSync13(path);
|
|
35741
36001
|
} catch (error) {
|
|
@@ -35765,8 +36025,8 @@ function loadWorkerPrompt(kind = "claude") {
|
|
|
35765
36025
|
function monitorScriptsDir() {
|
|
35766
36026
|
const plugin = workerPluginDir();
|
|
35767
36027
|
if (!plugin) return void 0;
|
|
35768
|
-
const dir =
|
|
35769
|
-
return existsSync9(
|
|
36028
|
+
const dir = join28(plugin, "skills", "gh", "scripts");
|
|
36029
|
+
return existsSync9(join28(dir, "ci-watch.mjs")) ? dir : void 0;
|
|
35770
36030
|
}
|
|
35771
36031
|
function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
35772
36032
|
if (!hookScript || !sessionId) return {};
|
|
@@ -35778,7 +36038,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
35778
36038
|
}
|
|
35779
36039
|
]
|
|
35780
36040
|
});
|
|
35781
|
-
const bashBackgroundHook =
|
|
36041
|
+
const bashBackgroundHook = join28(dirname11(hookScript), "bash-background.mjs");
|
|
35782
36042
|
return {
|
|
35783
36043
|
bypass_hook_trust: true,
|
|
35784
36044
|
hooks: {
|
|
@@ -35804,7 +36064,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
35804
36064
|
}
|
|
35805
36065
|
function scratchpadHookScript() {
|
|
35806
36066
|
const plugin = workerPluginDir();
|
|
35807
|
-
return plugin ?
|
|
36067
|
+
return plugin ? join28(plugin, "hooks", "scratchpad.mjs") : void 0;
|
|
35808
36068
|
}
|
|
35809
36069
|
function composePrompt(sessionId, prompt, kind = "claude") {
|
|
35810
36070
|
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 +36080,12 @@ function scratchpadOrientation(sessionId, kind = "claude") {
|
|
|
35820
36080
|
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
36081
|
}
|
|
35822
36082
|
function frizzConfigBlock(projectDir) {
|
|
35823
|
-
const path =
|
|
36083
|
+
const path = join28(projectDir, "FRIZZ.md");
|
|
35824
36084
|
let body;
|
|
35825
36085
|
try {
|
|
35826
36086
|
const st = statSync12(path);
|
|
35827
36087
|
if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
|
|
35828
|
-
body =
|
|
36088
|
+
body = readFileSync18(path, "utf8").trim();
|
|
35829
36089
|
} catch {
|
|
35830
36090
|
return "";
|
|
35831
36091
|
}
|
|
@@ -35860,7 +36120,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
|
|
|
35860
36120
|
const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
|
|
35861
36121
|
const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
|
|
35862
36122
|
if (!pluginDir) return void 0;
|
|
35863
|
-
const scriptPath =
|
|
36123
|
+
const scriptPath = join28(pluginDir, "bin", FRIZZ_MCP.script);
|
|
35864
36124
|
if (!existsSync9(scriptPath)) return void 0;
|
|
35865
36125
|
return {
|
|
35866
36126
|
scriptPath,
|
|
@@ -35870,7 +36130,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
|
|
|
35870
36130
|
...slug ? { slug } : {}
|
|
35871
36131
|
};
|
|
35872
36132
|
}
|
|
35873
|
-
function claudeMcpConfig(mcp) {
|
|
36133
|
+
function claudeMcpConfig(mcp, project) {
|
|
35874
36134
|
const mcpServers = {};
|
|
35875
36135
|
const allowedTools = [];
|
|
35876
36136
|
if (mcp) {
|
|
@@ -35878,13 +36138,16 @@ function claudeMcpConfig(mcp) {
|
|
|
35878
36138
|
mcpServers[FRIZZ_MCP.name] = { command: process.execPath, args: [mcp.scriptPath], env };
|
|
35879
36139
|
allowedTools.push(`mcp__${FRIZZ_MCP.name}`);
|
|
35880
36140
|
}
|
|
35881
|
-
return { mcpServers, allowedTools };
|
|
36141
|
+
return { mcpServers: workerMcpServers(project, mcpServers), allowedTools };
|
|
35882
36142
|
}
|
|
35883
|
-
function claudeMcpFlags(mcp) {
|
|
35884
|
-
const { mcpServers, allowedTools } = claudeMcpConfig(mcp);
|
|
35885
|
-
|
|
36143
|
+
function claudeMcpFlags(mcp, project) {
|
|
36144
|
+
const { mcpServers, allowedTools } = claudeMcpConfig(mcp, project);
|
|
36145
|
+
const argv = ["--strict-mcp-config"];
|
|
36146
|
+
if (Object.keys(mcpServers).length === 0) return argv;
|
|
35886
36147
|
const config = JSON.stringify({ mcpServers });
|
|
35887
|
-
|
|
36148
|
+
argv.push("--mcp-config", config);
|
|
36149
|
+
if (allowedTools.length > 0) argv.push(`--allowedTools=${allowedTools.join(",")}`);
|
|
36150
|
+
return argv;
|
|
35888
36151
|
}
|
|
35889
36152
|
function workerDisallowedToolFlags() {
|
|
35890
36153
|
return [`--disallowedTools=${WORKER_DISALLOWED_TOOLS.join(",")}`];
|
|
@@ -35896,7 +36159,7 @@ function buildClaudeCommand(opts) {
|
|
|
35896
36159
|
if (effort.effort) argv.push("--effort", effort.effort);
|
|
35897
36160
|
argv.push(...claudeUltracodeFlags(effort));
|
|
35898
36161
|
if (opts.pluginDir) argv.push("--plugin-dir", opts.pluginDir);
|
|
35899
|
-
argv.push(...claudeMcpFlags(opts.frizzMcp));
|
|
36162
|
+
argv.push(...claudeMcpFlags(opts.frizzMcp, opts.projectMcpServers));
|
|
35900
36163
|
argv.push(...workerDisallowedToolFlags());
|
|
35901
36164
|
const worker = opts.workerPrompt ?? loadWorkerPrompt();
|
|
35902
36165
|
const system = [worker, opts.extraSystemPrompt?.trim()].filter(Boolean).join("\n\n");
|
|
@@ -35929,7 +36192,7 @@ function buildClaudeResumeCommand(opts) {
|
|
|
35929
36192
|
if (effort.effort) argv.push("--effort", effort.effort);
|
|
35930
36193
|
argv.push(...claudeUltracodeFlags(effort));
|
|
35931
36194
|
if (opts.pluginDir) argv.push("--plugin-dir", opts.pluginDir);
|
|
35932
|
-
argv.push(...claudeMcpFlags(opts.frizzMcp));
|
|
36195
|
+
argv.push(...claudeMcpFlags(opts.frizzMcp, opts.projectMcpServers));
|
|
35933
36196
|
argv.push(...workerDisallowedToolFlags());
|
|
35934
36197
|
const worker = opts.workerPrompt ?? loadWorkerPrompt();
|
|
35935
36198
|
const system = [worker, opts.extraSystemPrompt?.trim()].filter(Boolean).join("\n\n");
|
|
@@ -35940,7 +36203,7 @@ function buildClaudeResumeCommand(opts) {
|
|
|
35940
36203
|
}
|
|
35941
36204
|
function createDispatcher(deps) {
|
|
35942
36205
|
const readBoardSource = deps.readBoard ?? readBoard;
|
|
35943
|
-
const frizzDir =
|
|
36206
|
+
const frizzDir = join28(deps.project.dir, ".frizz");
|
|
35944
36207
|
const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
|
|
35945
36208
|
function cleanupPrewrites(built) {
|
|
35946
36209
|
for (const path of new Set(built.prewrite.map((file) => file.path))) {
|
|
@@ -35953,7 +36216,7 @@ function createDispatcher(deps) {
|
|
|
35953
36216
|
function cleanupDispatchFiles(scratchRel, built, sessionId) {
|
|
35954
36217
|
cleanupPrewrites(built);
|
|
35955
36218
|
try {
|
|
35956
|
-
rmSync9(
|
|
36219
|
+
rmSync9(join28(deps.project.dir, scratchRel), { force: true, recursive: true });
|
|
35957
36220
|
} catch {
|
|
35958
36221
|
}
|
|
35959
36222
|
cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
|
|
@@ -36328,6 +36591,7 @@ var init_dispatch = __esm({
|
|
|
36328
36591
|
init_src();
|
|
36329
36592
|
init_logging();
|
|
36330
36593
|
init_project();
|
|
36594
|
+
init_project_mcp_servers();
|
|
36331
36595
|
init_types2();
|
|
36332
36596
|
init_types2();
|
|
36333
36597
|
init_worker_plugin_dir();
|
|
@@ -36385,8 +36649,8 @@ var init_dispatch = __esm({
|
|
|
36385
36649
|
import {
|
|
36386
36650
|
watch as fsWatch
|
|
36387
36651
|
} from "node:fs";
|
|
36388
|
-
import { homedir as
|
|
36389
|
-
import { join as
|
|
36652
|
+
import { homedir as homedir13 } from "node:os";
|
|
36653
|
+
import { join as join29 } from "node:path";
|
|
36390
36654
|
import watcher from "@parcel/watcher";
|
|
36391
36655
|
function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
|
|
36392
36656
|
if (!liveness) return false;
|
|
@@ -36515,7 +36779,7 @@ function safeQuestionAnswer(raw2) {
|
|
|
36515
36779
|
return void 0;
|
|
36516
36780
|
}
|
|
36517
36781
|
}
|
|
36518
|
-
function answersInFlight(rows, lastUserAt) {
|
|
36782
|
+
function answersInFlight(rows, lastUserAt, wakeOnDismissals = false) {
|
|
36519
36783
|
const userAt = lastUserAt ? Date.parse(lastUserAt) : Number.NaN;
|
|
36520
36784
|
const answers = [];
|
|
36521
36785
|
const dismissed = [];
|
|
@@ -36531,7 +36795,8 @@ function answersInFlight(rows, lastUserAt) {
|
|
|
36531
36795
|
const parsed = safeQuestionAnswer(q.answer);
|
|
36532
36796
|
if (parsed) answers.push(parsed);
|
|
36533
36797
|
}
|
|
36534
|
-
|
|
36798
|
+
if (answers.length > 0) return questionAnswerMessage(answers, dismissed);
|
|
36799
|
+
return wakeOnDismissals && dismissed.length > 0 ? questionsCancelledWakeMessage(dismissed.length) : void 0;
|
|
36535
36800
|
}
|
|
36536
36801
|
function hasRegisteredBackgroundPark(tele, armedWatches, nowMs) {
|
|
36537
36802
|
return armedWatches.some((w) => {
|
|
@@ -36781,7 +37046,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
|
|
|
36781
37046
|
const spec = safeQuestionSpec(q.spec);
|
|
36782
37047
|
if (spec) questions.push({ id: q.id, spec, askedAt: new Date(q.asked_at).toISOString() });
|
|
36783
37048
|
}
|
|
36784
|
-
const inFlightAnswers = answersInFlight(questionRows, rawTele?.lastUserAt);
|
|
37049
|
+
const inFlightAnswers = answersInFlight(questionRows, rawTele?.lastUserAt, row.recurring_on_rest === 1 && Boolean(row.recurring_prompt?.trim()));
|
|
36785
37050
|
const armedWatches = storage.listThreadWatches(row.slug, { armedOnly: true }).map((w) => ({
|
|
36786
37051
|
id: w.id,
|
|
36787
37052
|
kind: w.kind,
|
|
@@ -36866,6 +37131,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
|
|
|
36866
37131
|
// Only meaningful while the snooze is still pending — a prompt without a live deadline is an
|
|
36867
37132
|
// already-delivered (or superseded) bump the row has not been swept clean of yet.
|
|
36868
37133
|
snoozePrompt: snoozedUntil ? row.snooze_prompt ?? void 0 : void 0,
|
|
37134
|
+
pinnedAt: row.pinned_at ?? void 0,
|
|
36869
37135
|
// The event-snooze, as a fact the CHAT can read. `awaitingBackground` still ignores it — that flag
|
|
36870
37136
|
// states whether the thread is waiting, which the snooze does not change — so the suppression is a
|
|
36871
37137
|
// presentation rule the client applies, not a second opinion about the thread's state.
|
|
@@ -37083,7 +37349,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
37083
37349
|
...project.githubRepo ? { githubRepo: project.githubRepo } : {},
|
|
37084
37350
|
projectSlug: findByPath(project.dir)?.slug,
|
|
37085
37351
|
// So the client can expand a `~` a worker wrote in prose (see BoardSnapshot.homeDir).
|
|
37086
|
-
homeDir:
|
|
37352
|
+
homeDir: homedir13()
|
|
37087
37353
|
};
|
|
37088
37354
|
const sessionThreads = buildSessionThreads(assembledAtMs);
|
|
37089
37355
|
armSnoozeWake(sessionThreads, assembledAtMs);
|
|
@@ -37184,7 +37450,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
37184
37450
|
if (parcelSub || stopped) return Promise.resolve();
|
|
37185
37451
|
if (watchSetup) return watchSetup;
|
|
37186
37452
|
const setup = (async () => {
|
|
37187
|
-
const next = await subscribe(
|
|
37453
|
+
const next = await subscribe(join29(project.dir, ".frizz"), () => scheduleRebuild());
|
|
37188
37454
|
if (stopped) {
|
|
37189
37455
|
await next.unsubscribe();
|
|
37190
37456
|
return;
|
|
@@ -37284,6 +37550,9 @@ var init_board = __esm({
|
|
|
37284
37550
|
});
|
|
37285
37551
|
|
|
37286
37552
|
// packages/server/src/wake-store.ts
|
|
37553
|
+
function isQuietWindowExempt(hintKey) {
|
|
37554
|
+
return WAKE_QUIET_EXEMPT_HINT_PREFIXES.some((prefix) => hintKey.startsWith(prefix));
|
|
37555
|
+
}
|
|
37287
37556
|
function delivery(row) {
|
|
37288
37557
|
return {
|
|
37289
37558
|
id: row.id,
|
|
@@ -37334,14 +37603,22 @@ function ensureWakeDeliverySchema(db) {
|
|
|
37334
37603
|
ON wake_delivery(project_id, state, next_attempt_at, created_at);
|
|
37335
37604
|
`);
|
|
37336
37605
|
}
|
|
37337
|
-
function createWakeDeliveryStore(scope) {
|
|
37606
|
+
function createWakeDeliveryStore(scope, options = {}) {
|
|
37338
37607
|
const db = scope.db;
|
|
37339
37608
|
ensureWakeDeliverySchema(db);
|
|
37609
|
+
const quietWindowMs = Math.max(0, options.quietWindowMs ?? WAKE_QUIET_WINDOW_MS);
|
|
37340
37610
|
const byId = scope.prepare("SELECT * FROM wake_delivery WHERE project_id = @project_id AND id = ?");
|
|
37341
37611
|
const all = scope.prepare("SELECT * FROM wake_delivery WHERE project_id = @project_id ORDER BY created_at, id");
|
|
37342
37612
|
const open = scope.prepare(
|
|
37343
37613
|
"SELECT * FROM wake_delivery WHERE project_id = @project_id AND state IN ('pending', 'leased') ORDER BY created_at, id"
|
|
37344
37614
|
);
|
|
37615
|
+
const pendingForStmt = scope.prepare(
|
|
37616
|
+
"SELECT * FROM wake_delivery WHERE project_id = @project_id AND thread_slug = ? AND session_id = ? AND state = 'pending' ORDER BY created_at, id"
|
|
37617
|
+
);
|
|
37618
|
+
const lastHandoffStmt = scope.prepare(`
|
|
37619
|
+
SELECT MAX(MAX(COALESCE(delivered_at, 0), COALESCE(sent_at, 0))) AS at
|
|
37620
|
+
FROM wake_delivery WHERE project_id = @project_id AND thread_slug = ?
|
|
37621
|
+
`);
|
|
37345
37622
|
const insert = scope.prepare(`
|
|
37346
37623
|
INSERT INTO wake_delivery (
|
|
37347
37624
|
project_id, id, thread_slug, session_id, fence_id, hint_key, message, reason, state, attempts,
|
|
@@ -37349,7 +37626,7 @@ function createWakeDeliveryStore(scope) {
|
|
|
37349
37626
|
delivered_at, terminal_at
|
|
37350
37627
|
) VALUES (
|
|
37351
37628
|
@project_id, @id, @slug, @sessionId, @fenceId, @hintKey, @message, @reason, 'pending', 0,
|
|
37352
|
-
@
|
|
37629
|
+
@notBefore, NULL, NULL, NULL, @now, @now, NULL, NULL
|
|
37353
37630
|
)
|
|
37354
37631
|
ON CONFLICT DO NOTHING
|
|
37355
37632
|
`);
|
|
@@ -37376,6 +37653,12 @@ function createWakeDeliveryStore(scope) {
|
|
|
37376
37653
|
last_error = NULL, sent_at = NULL, updated_at = @now
|
|
37377
37654
|
WHERE project_id = @project_id AND id = @id AND state = 'pending' AND next_attempt_at <= @now AND attempts < @maxAttempts
|
|
37378
37655
|
`);
|
|
37656
|
+
const adoptStmt = scope.prepare(`
|
|
37657
|
+
UPDATE wake_delivery SET
|
|
37658
|
+
state = 'leased', attempts = attempts + 1, lease_owner = @owner, lease_until = @leaseUntil,
|
|
37659
|
+
last_error = NULL, sent_at = NULL, updated_at = @now
|
|
37660
|
+
WHERE project_id = @project_id AND id = @id AND state = 'pending' AND attempts < @maxAttempts
|
|
37661
|
+
`);
|
|
37379
37662
|
const deferFailureStmt = scope.prepare(`
|
|
37380
37663
|
UPDATE wake_delivery SET
|
|
37381
37664
|
lease_until = @retryAt, last_error = @error, updated_at = @now
|
|
@@ -37416,7 +37699,12 @@ function createWakeDeliveryStore(scope) {
|
|
|
37416
37699
|
WHERE project_id = @project_id AND id = @id AND state IN ('pending', 'leased')
|
|
37417
37700
|
`);
|
|
37418
37701
|
const enqueueTxn = db.transaction((input, now) => {
|
|
37419
|
-
|
|
37702
|
+
let notBefore = now;
|
|
37703
|
+
if (quietWindowMs > 0 && !isQuietWindowExempt(input.hintKey)) {
|
|
37704
|
+
const lastHandoff = lastHandoffStmt.get(input.slug)?.at ?? 0;
|
|
37705
|
+
if (lastHandoff > 0) notBefore = Math.max(now, lastHandoff + quietWindowMs);
|
|
37706
|
+
}
|
|
37707
|
+
const created = insert.run({ ...input, now, notBefore }).changes === 1;
|
|
37420
37708
|
const row = byId.get(input.id);
|
|
37421
37709
|
if (!row) throw new Error("wake delivery disappeared while it was being enqueued");
|
|
37422
37710
|
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 +37720,10 @@ function createWakeDeliveryStore(scope) {
|
|
|
37432
37720
|
if (claimStmt.run({ id: candidate.id, owner, now, leaseUntil, maxAttempts }).changes !== 1) return void 0;
|
|
37433
37721
|
return delivery(byId.get(candidate.id));
|
|
37434
37722
|
});
|
|
37723
|
+
const adoptTxn = db.transaction((id, owner, now, leaseUntil, maxAttempts) => {
|
|
37724
|
+
if (adoptStmt.run({ id, owner, now, leaseUntil, maxAttempts }).changes !== 1) return void 0;
|
|
37725
|
+
return delivery(byId.get(id));
|
|
37726
|
+
});
|
|
37435
37727
|
return {
|
|
37436
37728
|
enqueue: (input, now) => enqueueTxn.immediate(input, now),
|
|
37437
37729
|
get: (id) => {
|
|
@@ -37440,7 +37732,9 @@ function createWakeDeliveryStore(scope) {
|
|
|
37440
37732
|
},
|
|
37441
37733
|
list: () => all.all().map(delivery),
|
|
37442
37734
|
listOpen: () => open.all().map(delivery),
|
|
37735
|
+
pendingFor: (slug, sessionId) => pendingForStmt.all(slug, sessionId).map(delivery),
|
|
37443
37736
|
claim: (owner, now, leaseUntil, maxAttempts) => claimTxn.immediate(owner, now, leaseUntil, maxAttempts),
|
|
37737
|
+
adopt: (id, owner, now, leaseUntil, maxAttempts) => adoptTxn.immediate(id, owner, now, leaseUntil, maxAttempts),
|
|
37444
37738
|
deferFailure: (id, owner, now, retryAt, error) => deferFailureStmt.run({
|
|
37445
37739
|
id,
|
|
37446
37740
|
owner,
|
|
@@ -37459,10 +37753,12 @@ function createWakeDeliveryStore(scope) {
|
|
|
37459
37753
|
supersede: (id, now, reason) => supersedeStmt.run({ id, now, reason: reason.slice(0, 500) }).changes === 1
|
|
37460
37754
|
};
|
|
37461
37755
|
}
|
|
37462
|
-
var OUTBOX_CAP, WAKE_DELIVERY_TABLES;
|
|
37756
|
+
var WAKE_QUIET_WINDOW_MS, WAKE_QUIET_EXEMPT_HINT_PREFIXES, OUTBOX_CAP, WAKE_DELIVERY_TABLES;
|
|
37463
37757
|
var init_wake_store = __esm({
|
|
37464
37758
|
"packages/server/src/wake-store.ts"() {
|
|
37465
37759
|
"use strict";
|
|
37760
|
+
WAKE_QUIET_WINDOW_MS = 5 * 6e4;
|
|
37761
|
+
WAKE_QUIET_EXEMPT_HINT_PREFIXES = ["answers:", "limit:"];
|
|
37466
37762
|
OUTBOX_CAP = 2e3;
|
|
37467
37763
|
WAKE_DELIVERY_TABLES = ["wake_delivery"];
|
|
37468
37764
|
}
|
|
@@ -37745,9 +38041,9 @@ function createGithubReviewFetcher(deps = {}) {
|
|
|
37745
38041
|
for (const entry of batch.values()) for (const resolve12 of entry.resolve) resolve12({ status: "deferred" });
|
|
37746
38042
|
return;
|
|
37747
38043
|
}
|
|
37748
|
-
const
|
|
37749
|
-
for (let offset = 0; offset <
|
|
37750
|
-
const chunk =
|
|
38044
|
+
const entries2 = [...batch.values()];
|
|
38045
|
+
for (let offset = 0; offset < entries2.length; offset += MAX_REFS_PER_REQUEST) {
|
|
38046
|
+
const chunk = entries2.slice(offset, offset + MAX_REFS_PER_REQUEST);
|
|
37751
38047
|
if (offset > 0 && now() < rateLimitBlockedUntilMs) {
|
|
37752
38048
|
for (const entry of chunk) for (const resolve12 of entry.resolve) resolve12({ status: "deferred" });
|
|
37753
38049
|
continue;
|
|
@@ -37960,11 +38256,11 @@ function failedCheckNames(rollup, runs = [], cap = 8) {
|
|
|
37960
38256
|
return { names: names.slice(0, cap), omitted: Math.max(0, names.length - cap) };
|
|
37961
38257
|
}
|
|
37962
38258
|
function githubWatchStatus(pr, polledAt) {
|
|
37963
|
-
const
|
|
38259
|
+
const entries2 = Array.isArray(pr.rollup) ? pr.rollup.filter((c) => c && typeof c === "object") : [];
|
|
37964
38260
|
let running = 0;
|
|
37965
38261
|
let passed = 0;
|
|
37966
38262
|
let failed = 0;
|
|
37967
|
-
for (const c of
|
|
38263
|
+
for (const c of entries2) {
|
|
37968
38264
|
const status = typeof c.status === "string" ? c.status : void 0;
|
|
37969
38265
|
const state2 = typeof c.state === "string" ? c.state : void 0;
|
|
37970
38266
|
const conclusion = typeof c.conclusion === "string" ? c.conclusion : void 0;
|
|
@@ -37974,19 +38270,19 @@ function githubWatchStatus(pr, polledAt) {
|
|
|
37974
38270
|
else passed++;
|
|
37975
38271
|
}
|
|
37976
38272
|
const state = pr.state === "MERGED" ? "merged" : pr.state === "CLOSED" ? "closed" : "open";
|
|
37977
|
-
const checks =
|
|
38273
|
+
const checks = entries2.length === 0 ? "none" : failed > 0 ? "failing" : running > 0 ? "running" : "passing";
|
|
37978
38274
|
const merge = pr.mergeable === "CONFLICTING" ? "conflicting" : pr.mergeable !== "MERGEABLE" ? "unknown" : checks === "failing" || pr.reviewDecision === "CHANGES_REQUESTED" || pr.reviewDecision === "REVIEW_REQUIRED" ? "blocked" : "mergeable";
|
|
37979
38275
|
return {
|
|
37980
38276
|
checks,
|
|
37981
38277
|
running,
|
|
37982
38278
|
passed,
|
|
37983
38279
|
failed,
|
|
37984
|
-
failing: failedCheckNames(
|
|
38280
|
+
failing: failedCheckNames(entries2, pr.workflowRuns ?? []).names,
|
|
37985
38281
|
merge,
|
|
37986
38282
|
state,
|
|
37987
38283
|
polledAt,
|
|
37988
38284
|
head: pr.head,
|
|
37989
|
-
failureSig: failureSignature(
|
|
38285
|
+
failureSig: failureSignature(entries2)
|
|
37990
38286
|
};
|
|
37991
38287
|
}
|
|
37992
38288
|
function wakeDeliveryId(slug, sessionId, fenceId) {
|
|
@@ -38215,7 +38511,7 @@ function createScheduler(deps) {
|
|
|
38215
38511
|
const confirmGraceMs = Math.max(1, deps.confirmGraceMs ?? 6e4);
|
|
38216
38512
|
const deliveryBatchSize = Math.max(0, deps.deliveryBatchSize ?? 50);
|
|
38217
38513
|
const deliveryOwner = randomUUID14();
|
|
38218
|
-
const outbox = createWakeDeliveryStore(deps.storage.scope);
|
|
38514
|
+
const outbox = createWakeDeliveryStore(deps.storage.scope, { quietWindowMs: deps.wakeQuietWindowMs ?? WAKE_QUIET_WINDOW_MS });
|
|
38219
38515
|
const reviewFailures = /* @__PURE__ */ new Map();
|
|
38220
38516
|
let timer = null;
|
|
38221
38517
|
let activeTick = null;
|
|
@@ -38311,9 +38607,14 @@ function createScheduler(deps) {
|
|
|
38311
38607
|
}
|
|
38312
38608
|
return "superseded";
|
|
38313
38609
|
}
|
|
38314
|
-
function isDeliverableNow(item, context) {
|
|
38610
|
+
function isDeliverableNow(item, context, nowMs) {
|
|
38315
38611
|
if (context === "current-idle") return true;
|
|
38316
|
-
|
|
38612
|
+
if (context !== "current-busy") return false;
|
|
38613
|
+
if (isHeartbeatFenceId(item.fenceId) || isTimerFenceId(item.fenceId) || isCompactFenceId(item.fenceId)) return true;
|
|
38614
|
+
return nowMs - item.createdAt >= MID_TURN_HOLD_MAX_MS;
|
|
38615
|
+
}
|
|
38616
|
+
function heldPastBound(item, context, nowMs) {
|
|
38617
|
+
return context === "current-busy" && item.sentAt === null && item.lastError === WAKE_HOLD_DEFERRAL && nowMs - item.createdAt >= MID_TURN_HOLD_MAX_MS;
|
|
38317
38618
|
}
|
|
38318
38619
|
function activityLabel(a) {
|
|
38319
38620
|
if (a.kind === "comment") return "comment";
|
|
@@ -38517,7 +38818,7 @@ function createScheduler(deps) {
|
|
|
38517
38818
|
if (tele.authFault) continue;
|
|
38518
38819
|
if (tele.apiFault) continue;
|
|
38519
38820
|
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) {
|
|
38821
|
+
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
38822
|
if ((row.signoff_nudges ?? 0) > 0) deps.storage.resetSignoffNudges(row.slug);
|
|
38522
38823
|
continue;
|
|
38523
38824
|
}
|
|
@@ -38724,12 +39025,27 @@ ${clock}`;
|
|
|
38724
39025
|
}
|
|
38725
39026
|
}
|
|
38726
39027
|
const prWatchPolledAt = /* @__PURE__ */ new Map();
|
|
39028
|
+
function readPrWatchHeld(raw2) {
|
|
39029
|
+
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
39030
|
+
const h = raw2;
|
|
39031
|
+
if (!Array.isArray(h.items)) return void 0;
|
|
39032
|
+
const items = h.items.flatMap((i) => GithubWakeItem.safeParse(i).success ? [i] : []);
|
|
39033
|
+
const c = h.checks && typeof h.checks === "object" ? h.checks : void 0;
|
|
39034
|
+
const checks = c && (c.verdict === "passing" || c.verdict === "failing") ? {
|
|
39035
|
+
verdict: c.verdict,
|
|
39036
|
+
passed: Number(c.passed) || 0,
|
|
39037
|
+
failed: Number(c.failed) || 0,
|
|
39038
|
+
failing: Array.isArray(c.failing) ? c.failing.filter((x) => typeof x === "string") : []
|
|
39039
|
+
} : void 0;
|
|
39040
|
+
return { items, omitted: Number(h.omitted) || 0, ...checks ? { checks } : {} };
|
|
39041
|
+
}
|
|
38727
39042
|
function readPrWatchCursor(raw2) {
|
|
38728
39043
|
try {
|
|
38729
39044
|
const parsed = raw2 ? JSON.parse(raw2) : null;
|
|
38730
39045
|
if (!parsed || typeof parsed !== "object") return { seen: [] };
|
|
38731
39046
|
const seen = Array.isArray(parsed.seen) ? parsed.seen.filter((x) => typeof x === "string") : [];
|
|
38732
|
-
|
|
39047
|
+
const held = readPrWatchHeld(parsed.held);
|
|
39048
|
+
return { seen, checks: typeof parsed.checks === "string" ? parsed.checks : void 0, report: Number(parsed.report) || 0, ...held ? { held } : {} };
|
|
38733
39049
|
} catch {
|
|
38734
39050
|
return { seen: [] };
|
|
38735
39051
|
}
|
|
@@ -38812,7 +39128,7 @@ ${clock}`;
|
|
|
38812
39128
|
}), `pr-watch ${key} ${st.state}`, nowMs);
|
|
38813
39129
|
continue;
|
|
38814
39130
|
}
|
|
38815
|
-
if (waiting) continue;
|
|
39131
|
+
if (waiting && !cursor.held) continue;
|
|
38816
39132
|
const terminal = st && (st.checks === "passing" || st.checks === "failing") ? st.checks : void 0;
|
|
38817
39133
|
const stamp = terminal !== void 0 ? `${st?.head ?? "?"}:${terminal}:${st?.failureSig ?? ""}` : void 0;
|
|
38818
39134
|
const checksChanged = stamp !== void 0 && cursor.checks !== stamp;
|
|
@@ -38829,37 +39145,42 @@ ${clock}`;
|
|
|
38829
39145
|
return Number.isFinite(landed) && landed > w.created_at;
|
|
38830
39146
|
});
|
|
38831
39147
|
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;
|
|
39148
|
+
const freshItems = [...named].reverse().map((a) => ({
|
|
39149
|
+
label: activityLabel(a),
|
|
39150
|
+
actor: a.actor,
|
|
39151
|
+
bot: isBotGithubActor(a),
|
|
39152
|
+
...a.at ? { at: a.at } : {},
|
|
39153
|
+
...a.url ? { url: a.url } : {}
|
|
39154
|
+
}));
|
|
38843
39155
|
const nextCursor = {
|
|
38844
39156
|
seen: acts ? [.../* @__PURE__ */ new Set([...cursor.seen, ...acts.map((a) => a.id)])].slice(-REVIEW_SEEN_CAP) : cursor.seen,
|
|
38845
39157
|
// The STAMP, so the next poll compares against the commit as well as the colour.
|
|
38846
39158
|
checks: stamp ?? cursor.checks,
|
|
38847
39159
|
report: cursor.report ?? 0
|
|
38848
39160
|
};
|
|
38849
|
-
if (!checksChanged &&
|
|
39161
|
+
if (!checksChanged && fresh.length === 0) {
|
|
38850
39162
|
if (JSON.stringify(nextCursor) !== JSON.stringify(cursor)) {
|
|
38851
39163
|
deps.storage.setPrWatchCursor(w.id, JSON.stringify(nextCursor));
|
|
38852
39164
|
}
|
|
38853
39165
|
continue;
|
|
38854
39166
|
}
|
|
38855
39167
|
deps.storage.setSnoozedUntil(row.slug, null);
|
|
39168
|
+
const held = waiting ? cursor.held : void 0;
|
|
39169
|
+
const items = [...held?.items ?? [], ...freshItems];
|
|
39170
|
+
const carried = {
|
|
39171
|
+
items: items.slice(-REVIEW_STEER_CAP),
|
|
39172
|
+
omitted: (held?.omitted ?? 0) + (fresh.length - named.length) + Math.max(0, items.length - REVIEW_STEER_CAP),
|
|
39173
|
+
...checksChanged && terminal ? { checks: { verdict: terminal, passed: st.passed, failed: st.failed, failing: st.failing } } : held?.checks ? { checks: held.checks } : {}
|
|
39174
|
+
};
|
|
39175
|
+
const review = carried.items.length > 0 ? formatGithubWakeSteer({ ref: key, items: carried.items, omitted: carried.omitted }) : void 0;
|
|
38856
39176
|
const report = nextReport(cursor);
|
|
39177
|
+
if (waiting) outbox.supersede(waiting.id, nowMs, `folded into report ${report}, which says this and everything since`);
|
|
38857
39178
|
enqueuePrWatchWake(row, w.id, report, prWatchWakeMessage({
|
|
38858
39179
|
target: key,
|
|
38859
|
-
...
|
|
38860
|
-
...
|
|
38861
|
-
}), `pr-watch ${key}${
|
|
38862
|
-
deps.storage.setPrWatchCursor(w.id, JSON.stringify({ ...nextCursor, report }));
|
|
39180
|
+
...carried.checks ? { checks: carried.checks } : {},
|
|
39181
|
+
...review ? { review } : {}
|
|
39182
|
+
}), `pr-watch ${key}${carried.checks ? ` CI ${carried.checks.verdict}` : ""}${review ? " review" : ""}`, nowMs);
|
|
39183
|
+
deps.storage.setPrWatchCursor(w.id, JSON.stringify({ ...nextCursor, report, held: carried }));
|
|
38863
39184
|
}
|
|
38864
39185
|
}
|
|
38865
39186
|
function nextReport(cursor) {
|
|
@@ -39085,6 +39406,7 @@ ${clock}`;
|
|
|
39085
39406
|
if (tele.authFault) continue;
|
|
39086
39407
|
if (tele.apiFault) continue;
|
|
39087
39408
|
if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug))) continue;
|
|
39409
|
+
if (answersInFlight(deps.storage.listThreadQuestions(row.slug), tele.lastUserAt, true) !== void 0) continue;
|
|
39088
39410
|
const reminder = outbox.get(wakeDeliveryId(row.slug, row.session_id, signoffFenceId(restedAt)));
|
|
39089
39411
|
if (reminder && reminder.state !== "superseded" && reminder.state !== "exhausted") continue;
|
|
39090
39412
|
const fenceId = stopHookFenceId(armed.armedAt, restedAt);
|
|
@@ -39155,11 +39477,8 @@ ${clock}`;
|
|
|
39155
39477
|
const context = deliveryContext(item);
|
|
39156
39478
|
if (context === "confirmed") {
|
|
39157
39479
|
outbox.confirm(item.id, nowMs);
|
|
39158
|
-
|
|
39159
|
-
|
|
39160
|
-
settleRestPrompt(item);
|
|
39161
|
-
settleCompactPrompt(item);
|
|
39162
|
-
settleTimer(item);
|
|
39480
|
+
settleDelivered(item);
|
|
39481
|
+
confirmFrame(item, nowMs);
|
|
39163
39482
|
continue;
|
|
39164
39483
|
}
|
|
39165
39484
|
if (context === "superseded") {
|
|
@@ -39200,7 +39519,7 @@ ${clock}`;
|
|
|
39200
39519
|
}
|
|
39201
39520
|
continue;
|
|
39202
39521
|
}
|
|
39203
|
-
if (context !== "current-idle") continue;
|
|
39522
|
+
if (context !== "current-idle" && !heldPastBound(item, context, nowMs)) continue;
|
|
39204
39523
|
const recovered = outbox.recoverExpired(
|
|
39205
39524
|
item.id,
|
|
39206
39525
|
nowMs,
|
|
@@ -39215,6 +39534,63 @@ ${clock}`;
|
|
|
39215
39534
|
}
|
|
39216
39535
|
}
|
|
39217
39536
|
}
|
|
39537
|
+
function settleDelivered(item) {
|
|
39538
|
+
settleSnooze(item);
|
|
39539
|
+
settleSchedulePrompt(item);
|
|
39540
|
+
settleRestPrompt(item);
|
|
39541
|
+
settleCompactPrompt(item);
|
|
39542
|
+
settleTimer(item);
|
|
39543
|
+
}
|
|
39544
|
+
function adoptCompanions(carrier, claimedAt) {
|
|
39545
|
+
if (isQuestionAnswerFenceId(carrier.fenceId)) return [];
|
|
39546
|
+
const adopted = [];
|
|
39547
|
+
for (const sibling of outbox.pendingFor(carrier.slug, carrier.sessionId)) {
|
|
39548
|
+
if (sibling.id === carrier.id || isQuestionAnswerFenceId(sibling.fenceId)) continue;
|
|
39549
|
+
const context = deliveryContext(sibling);
|
|
39550
|
+
if (context === "confirmed") {
|
|
39551
|
+
outbox.confirm(sibling.id, now());
|
|
39552
|
+
settleDelivered(sibling);
|
|
39553
|
+
continue;
|
|
39554
|
+
}
|
|
39555
|
+
if (context === "superseded") {
|
|
39556
|
+
outbox.supersede(sibling.id, now(), "the exact awaiting fence or session was superseded before delivery");
|
|
39557
|
+
settleSnooze(sibling);
|
|
39558
|
+
continue;
|
|
39559
|
+
}
|
|
39560
|
+
if (!isDeliverableNow(sibling, context, claimedAt)) continue;
|
|
39561
|
+
const leased = outbox.adopt(sibling.id, deliveryOwner, claimedAt, claimedAt + deliveryLeaseMs, maxDeliveryAttempts);
|
|
39562
|
+
if (leased) adopted.push(leased);
|
|
39563
|
+
}
|
|
39564
|
+
return adopted;
|
|
39565
|
+
}
|
|
39566
|
+
function mergedWakeMessage(frame, spokeAt) {
|
|
39567
|
+
const sections = [];
|
|
39568
|
+
for (const part of frame) {
|
|
39569
|
+
const body2 = stripWakeTimeHeader(part.message).trim();
|
|
39570
|
+
const same = sections.find((s) => s.reason === part.reason && s.body === body2);
|
|
39571
|
+
if (same) same.count++;
|
|
39572
|
+
else sections.push({ reason: part.reason, body: body2, count: 1 });
|
|
39573
|
+
}
|
|
39574
|
+
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.`;
|
|
39575
|
+
const body = sections.map((s, i) => `### ${i + 1}. ${s.reason}${s.count > 1 ? ` \u2014 ${s.count} identical events` : ""}
|
|
39576
|
+
|
|
39577
|
+
${s.body}`);
|
|
39578
|
+
return `${lead}
|
|
39579
|
+
|
|
39580
|
+
${body.join("\n\n")}
|
|
39581
|
+
|
|
39582
|
+
${wakeTimeHeader(now(), spokeAt)}`;
|
|
39583
|
+
}
|
|
39584
|
+
function confirmFrame(carrier, nowMs) {
|
|
39585
|
+
if (carrier.sentAt === null) return;
|
|
39586
|
+
for (const d of outbox.listOpen()) {
|
|
39587
|
+
if (d.id === carrier.id || d.slug !== carrier.slug || d.sessionId !== carrier.sessionId) continue;
|
|
39588
|
+
if (d.state !== "leased" || d.sentAt !== carrier.sentAt) continue;
|
|
39589
|
+
if (!outbox.confirm(d.id, nowMs)) continue;
|
|
39590
|
+
settleDelivered(d);
|
|
39591
|
+
log3(`waker: delivered ${d.slug} \u2014 ${d.reason} (in the same frame as: ${carrier.reason})`);
|
|
39592
|
+
}
|
|
39593
|
+
}
|
|
39218
39594
|
async function deliverDue() {
|
|
39219
39595
|
for (let delivered = 0; delivered < deliveryBatchSize; delivered++) {
|
|
39220
39596
|
const claimedAt = now();
|
|
@@ -39224,11 +39600,7 @@ ${clock}`;
|
|
|
39224
39600
|
const context = deliveryContext(item);
|
|
39225
39601
|
if (context === "confirmed") {
|
|
39226
39602
|
outbox.confirm(item.id, now());
|
|
39227
|
-
|
|
39228
|
-
settleSchedulePrompt(item);
|
|
39229
|
-
settleRestPrompt(item);
|
|
39230
|
-
settleCompactPrompt(item);
|
|
39231
|
-
settleTimer(item);
|
|
39603
|
+
settleDelivered(item);
|
|
39232
39604
|
continue;
|
|
39233
39605
|
}
|
|
39234
39606
|
if (context === "superseded") {
|
|
@@ -39236,44 +39608,55 @@ ${clock}`;
|
|
|
39236
39608
|
settleSnooze(item);
|
|
39237
39609
|
continue;
|
|
39238
39610
|
}
|
|
39239
|
-
if (!isDeliverableNow(item, context)) {
|
|
39611
|
+
if (!isDeliverableNow(item, context, claimedAt)) {
|
|
39240
39612
|
const deferredAt = now();
|
|
39241
39613
|
outbox.deferFailure(
|
|
39242
39614
|
item.id,
|
|
39243
39615
|
deliveryOwner,
|
|
39244
39616
|
deferredAt,
|
|
39245
39617
|
deferredAt + Math.max(deliveryLeaseMs, retryDelay(item.attempts)),
|
|
39246
|
-
|
|
39618
|
+
WAKE_HOLD_DEFERRAL
|
|
39247
39619
|
);
|
|
39248
39620
|
continue;
|
|
39249
39621
|
}
|
|
39622
|
+
const companions = adoptCompanions(item, claimedAt);
|
|
39623
|
+
const frame = [...companions, item].sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
|
39624
|
+
const message = companions.length === 0 ? item.message : mergedWakeMessage(frame, deps.tailer.get(item.slug)?.lastAssistantAt);
|
|
39625
|
+
if (companions.length > 0) {
|
|
39626
|
+
log3(`waker: merged ${frame.length} wakes for ${item.slug} into one delivery \u2014 ${frame.map((d) => d.reason).join("; ")}`);
|
|
39627
|
+
}
|
|
39250
39628
|
try {
|
|
39251
|
-
await deps.resume(item.slug,
|
|
39629
|
+
await deps.resume(item.slug, message, item.id);
|
|
39252
39630
|
} catch (error) {
|
|
39253
|
-
const
|
|
39631
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
39254
39632
|
const failedAt = now();
|
|
39255
39633
|
if (error?.terminalDelivery === true) {
|
|
39256
|
-
|
|
39257
|
-
|
|
39258
|
-
|
|
39259
|
-
|
|
39634
|
+
for (const d of frame) {
|
|
39635
|
+
outbox.supersede(d.id, failedAt, message2);
|
|
39636
|
+
settleSnooze(d);
|
|
39637
|
+
settleTimer(d);
|
|
39638
|
+
}
|
|
39639
|
+
log3(`waker: delivery ABANDONED for ${item.slug} (terminal, no retry): ${message2}`);
|
|
39260
39640
|
continue;
|
|
39261
39641
|
}
|
|
39262
|
-
|
|
39263
|
-
|
|
39264
|
-
|
|
39265
|
-
|
|
39266
|
-
|
|
39267
|
-
|
|
39268
|
-
|
|
39269
|
-
|
|
39642
|
+
for (const d of frame) {
|
|
39643
|
+
outbox.deferFailure(
|
|
39644
|
+
d.id,
|
|
39645
|
+
deliveryOwner,
|
|
39646
|
+
failedAt,
|
|
39647
|
+
failedAt + Math.max(deliveryLeaseMs, retryDelay(d.attempts)),
|
|
39648
|
+
message2
|
|
39649
|
+
);
|
|
39650
|
+
}
|
|
39651
|
+
log3(`waker: delivery FAILED for ${item.slug} (attempt ${item.attempts} of ${maxDeliveryAttempts}): ${message2}`);
|
|
39270
39652
|
continue;
|
|
39271
39653
|
}
|
|
39272
39654
|
const runtime = deps.wakeRuntimeState?.(item.slug, item.sessionId) ?? "unknown";
|
|
39273
39655
|
if (runtime !== "unknown") {
|
|
39274
39656
|
const sentAt = now();
|
|
39275
39657
|
if (outbox.markSent(item.id, deliveryOwner, sentAt, sentAt + confirmGraceMs)) {
|
|
39276
|
-
|
|
39658
|
+
for (const d of companions) outbox.markSent(d.id, deliveryOwner, sentAt, sentAt + confirmGraceMs);
|
|
39659
|
+
for (const d of frame) settleSignoffNudge(d);
|
|
39277
39660
|
log3(`waker: sent ${item.slug} \u2014 ${item.reason}; confirming within ${Math.round(confirmGraceMs / 1e3)}s`);
|
|
39278
39661
|
checkpoint("after-delivery", item);
|
|
39279
39662
|
continue;
|
|
@@ -39282,16 +39665,15 @@ ${clock}`;
|
|
|
39282
39665
|
const retried = item.attempts > 1 ? ` (on attempt ${item.attempts})` : "";
|
|
39283
39666
|
log3(`waker: delivered ${item.slug} \u2014 ${item.reason}${retried}`);
|
|
39284
39667
|
checkpoint("after-delivery", item);
|
|
39285
|
-
|
|
39668
|
+
const acked = frame.filter((d) => outbox.acknowledge(d.id, deliveryOwner, now()));
|
|
39669
|
+
if (!acked.includes(item)) {
|
|
39286
39670
|
log3(`waker: delivery acknowledgement lost ownership for ${item.slug}; preserving the authoritative terminal state`);
|
|
39287
|
-
continue;
|
|
39288
39671
|
}
|
|
39289
|
-
|
|
39290
|
-
|
|
39291
|
-
|
|
39292
|
-
|
|
39293
|
-
|
|
39294
|
-
settleSignoffNudge(item);
|
|
39672
|
+
for (const d of acked) {
|
|
39673
|
+
settleDelivered(d);
|
|
39674
|
+
settleSignoffNudge(d);
|
|
39675
|
+
}
|
|
39676
|
+
if (!acked.includes(item)) continue;
|
|
39295
39677
|
const acknowledged = outbox.get(item.id);
|
|
39296
39678
|
if (acknowledged) checkpoint("after-ack", acknowledged);
|
|
39297
39679
|
}
|
|
@@ -39412,7 +39794,7 @@ ${clock}`;
|
|
|
39412
39794
|
}
|
|
39413
39795
|
};
|
|
39414
39796
|
}
|
|
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;
|
|
39797
|
+
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
39798
|
var init_scheduler = __esm({
|
|
39417
39799
|
"packages/server/src/scheduler.ts"() {
|
|
39418
39800
|
"use strict";
|
|
@@ -39467,6 +39849,8 @@ var init_scheduler = __esm({
|
|
|
39467
39849
|
timer: "timers",
|
|
39468
39850
|
pr: "prs"
|
|
39469
39851
|
};
|
|
39852
|
+
MID_TURN_HOLD_MAX_MS = 10 * 6e4;
|
|
39853
|
+
WAKE_HOLD_DEFERRAL = "delivery deferred until exact awaiting telemetry is idle and available";
|
|
39470
39854
|
}
|
|
39471
39855
|
});
|
|
39472
39856
|
|
|
@@ -39546,7 +39930,7 @@ var init_resume = __esm({
|
|
|
39546
39930
|
});
|
|
39547
39931
|
|
|
39548
39932
|
// packages/server/src/backend/claude.ts
|
|
39549
|
-
import { join as
|
|
39933
|
+
import { join as join30 } from "node:path";
|
|
39550
39934
|
function toolResultText2(content) {
|
|
39551
39935
|
if (typeof content === "string") return content;
|
|
39552
39936
|
if (!Array.isArray(content)) return "";
|
|
@@ -39615,7 +39999,8 @@ function createClaudeBackend(opts) {
|
|
|
39615
39999
|
pluginDir: workerPluginDir(),
|
|
39616
40000
|
workerPrompt: o.workerContract,
|
|
39617
40001
|
extraSystemPrompt: o.extraSystemPrompt,
|
|
39618
|
-
frizzMcp: o.frizzMcp
|
|
40002
|
+
frizzMcp: o.frizzMcp,
|
|
40003
|
+
projectMcpServers: (opts.resolveProjectMcpServers ?? projectMcpServers)(o.cwd)
|
|
39619
40004
|
});
|
|
39620
40005
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
39621
40006
|
},
|
|
@@ -39630,12 +40015,13 @@ function createClaudeBackend(opts) {
|
|
|
39630
40015
|
pluginDir: workerPluginDir(),
|
|
39631
40016
|
workerPrompt: o.workerContract,
|
|
39632
40017
|
extraSystemPrompt: o.extraSystemPrompt,
|
|
39633
|
-
frizzMcp: o.frizzMcp
|
|
40018
|
+
frizzMcp: o.frizzMcp,
|
|
40019
|
+
projectMcpServers: (opts.resolveProjectMcpServers ?? projectMcpServers)(o.cwd)
|
|
39634
40020
|
});
|
|
39635
40021
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
39636
40022
|
},
|
|
39637
40023
|
transcriptPath(sessionId) {
|
|
39638
|
-
return
|
|
40024
|
+
return join30(opts.logDir, `${sessionId}.jsonl`);
|
|
39639
40025
|
},
|
|
39640
40026
|
parseLine(line) {
|
|
39641
40027
|
return parseClaudeLine(line);
|
|
@@ -39653,6 +40039,7 @@ function createClaudeBackend(opts) {
|
|
|
39653
40039
|
var init_claude = __esm({
|
|
39654
40040
|
"packages/server/src/backend/claude.ts"() {
|
|
39655
40041
|
"use strict";
|
|
40042
|
+
init_project_mcp_servers();
|
|
39656
40043
|
init_dispatch();
|
|
39657
40044
|
init_tailer();
|
|
39658
40045
|
}
|
|
@@ -39805,12 +40192,12 @@ var init_login_utility = __esm({
|
|
|
39805
40192
|
|
|
39806
40193
|
// packages/server/src/backend/codex-app-server-diagnostics.ts
|
|
39807
40194
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as statSync13 } from "node:fs";
|
|
39808
|
-
import { join as
|
|
40195
|
+
import { join as join31 } from "node:path";
|
|
39809
40196
|
function codexDiagnosticLogPath(stateDir, projectId) {
|
|
39810
|
-
return
|
|
40197
|
+
return join31(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
|
|
39811
40198
|
}
|
|
39812
40199
|
function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
|
|
39813
|
-
const dir =
|
|
40200
|
+
const dir = join31(stateDir, "codex-app-server");
|
|
39814
40201
|
const path = codexDiagnosticLogPath(stateDir, projectId);
|
|
39815
40202
|
let ensured = false;
|
|
39816
40203
|
return (event) => {
|
|
@@ -39842,7 +40229,7 @@ var init_codex_app_server_diagnostics = __esm({
|
|
|
39842
40229
|
import { execFile as execFile7 } from "node:child_process";
|
|
39843
40230
|
import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
|
|
39844
40231
|
import { connect as connectSocket } from "node:net";
|
|
39845
|
-
import { join as
|
|
40232
|
+
import { join as join32 } from "node:path";
|
|
39846
40233
|
function parseLsofSocketNames(stdout) {
|
|
39847
40234
|
const referenced = /* @__PURE__ */ new Set();
|
|
39848
40235
|
for (const line of stdout.split("\n")) {
|
|
@@ -39859,7 +40246,7 @@ function sweepStaleSockets(options, deps = {}) {
|
|
|
39859
40246
|
const keep = new Set(options.keep ?? []);
|
|
39860
40247
|
let candidates;
|
|
39861
40248
|
try {
|
|
39862
|
-
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) =>
|
|
40249
|
+
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
40250
|
} catch {
|
|
39864
40251
|
return;
|
|
39865
40252
|
}
|
|
@@ -39909,7 +40296,7 @@ var init_stale_socket_sweep = __esm({
|
|
|
39909
40296
|
|
|
39910
40297
|
// packages/server/src/orphan-reaper.ts
|
|
39911
40298
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
39912
|
-
import { readFileSync as
|
|
40299
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
39913
40300
|
import { dirname as dirname12 } from "node:path";
|
|
39914
40301
|
function firstTokenBasename(command) {
|
|
39915
40302
|
const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
|
|
@@ -40176,7 +40563,7 @@ var init_orphan_reaper = __esm({
|
|
|
40176
40563
|
defaultExec = (file, args) => execFileSync5(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
|
|
40177
40564
|
defaultReadEnv = (pid) => {
|
|
40178
40565
|
try {
|
|
40179
|
-
return
|
|
40566
|
+
return readFileSync19(`/proc/${pid}/environ`, "utf8");
|
|
40180
40567
|
} catch {
|
|
40181
40568
|
return null;
|
|
40182
40569
|
}
|
|
@@ -40323,9 +40710,9 @@ var init_thread_hibernation = __esm({
|
|
|
40323
40710
|
});
|
|
40324
40711
|
|
|
40325
40712
|
// packages/server/src/context.ts
|
|
40326
|
-
import { join as
|
|
40713
|
+
import { join as join33 } from "node:path";
|
|
40327
40714
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
40328
|
-
import { homedir as
|
|
40715
|
+
import { homedir as homedir14 } from "node:os";
|
|
40329
40716
|
function reconcileSessions(storage) {
|
|
40330
40717
|
for (const row of storage.allSessions()) {
|
|
40331
40718
|
if (isHeadlessRow(row)) continue;
|
|
@@ -40471,9 +40858,9 @@ async function createContext(opts = {}) {
|
|
|
40471
40858
|
}
|
|
40472
40859
|
}
|
|
40473
40860
|
function createContextUnchecked(opts, resources) {
|
|
40474
|
-
const home = opts.home ??
|
|
40861
|
+
const home = opts.home ?? homedir14();
|
|
40475
40862
|
const project = opts.project ?? resolveProject();
|
|
40476
|
-
const storage = createStorage(opts.database ??
|
|
40863
|
+
const storage = createStorage(opts.database ?? join33(project.stateDir, "ui.db"), project.id);
|
|
40477
40864
|
resources.storage = storage;
|
|
40478
40865
|
const bus = new Bus();
|
|
40479
40866
|
const transcriptChange = new Emitter();
|
|
@@ -40622,7 +41009,8 @@ function createContextUnchecked(opts, resources) {
|
|
|
40622
41009
|
// The frizz worker environment — the SDK equivalent of the CLI argv's --plugin-dir / --mcp-config.
|
|
40623
41010
|
// Computed ONCE here (constant per project) and applied on every broker fork so a broker worker
|
|
40624
41011
|
// gets the frizz sub-agent profiles, the frizz MCP server (the only one frizz mounts), and the
|
|
40625
|
-
// cc-worker hooks.
|
|
41012
|
+
// cc-worker hooks. The project's own `.mcp.json` servers are merged in by the broker daemon at each
|
|
41013
|
+
// fork (project-mcp-servers.ts): under `--strict-mcp-config` nothing mounts that frizz did not hand over.
|
|
40626
41014
|
workerEnv: {
|
|
40627
41015
|
pluginDir: workerPluginDir(),
|
|
40628
41016
|
...claudeMcpConfig(resolveFrizzMcp(frizzMcpTarget)),
|
|
@@ -40765,6 +41153,8 @@ ${wakeDeliveryToken(deliveryId)}`;
|
|
|
40765
41153
|
Date.now(),
|
|
40766
41154
|
(tailer.get(slug)?.subAgents ?? []).some((agent) => agent.state === "running")
|
|
40767
41155
|
)
|
|
41156
|
+
}).then(() => {
|
|
41157
|
+
if (row.exited === 1) storage.setExitedIfCurrent(slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
40768
41158
|
});
|
|
40769
41159
|
}
|
|
40770
41160
|
resumeThread(
|
|
@@ -43523,7 +43913,7 @@ var init_server = __esm({
|
|
|
43523
43913
|
});
|
|
43524
43914
|
|
|
43525
43915
|
// packages/server/src/repair.ts
|
|
43526
|
-
import { existsSync as existsSync11, readFileSync as
|
|
43916
|
+
import { existsSync as existsSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "node:fs";
|
|
43527
43917
|
import { basename as basename7, dirname as dirname13, resolve as resolve7 } from "node:path";
|
|
43528
43918
|
function quoteValue(v) {
|
|
43529
43919
|
if (/^[\w./#:+-]+$/.test(v)) return v;
|
|
@@ -43539,7 +43929,7 @@ function repairThreadFile(frizzDir, file) {
|
|
|
43539
43929
|
if (dirname13(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
|
|
43540
43930
|
if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
|
|
43541
43931
|
if (!existsSync11(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
|
|
43542
|
-
const content =
|
|
43932
|
+
const content = readFileSync20(abs, "utf8");
|
|
43543
43933
|
const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
|
|
43544
43934
|
if (firstLine2?.trim() === "---") {
|
|
43545
43935
|
throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
|
|
@@ -43617,7 +44007,7 @@ var init_edited_files = __esm({
|
|
|
43617
44007
|
});
|
|
43618
44008
|
|
|
43619
44009
|
// cc-worker/hooks/bash-background.mjs
|
|
43620
|
-
import { readFileSync as
|
|
44010
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
43621
44011
|
import { basename as basename8 } from "node:path";
|
|
43622
44012
|
import { pathToFileURL } from "node:url";
|
|
43623
44013
|
function emit(obj) {
|
|
@@ -43766,7 +44156,7 @@ var init_bash_background = __esm({
|
|
|
43766
44156
|
if (isDirectHookExecution(process.argv[1], import.meta.url)) {
|
|
43767
44157
|
try {
|
|
43768
44158
|
const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
|
|
43769
|
-
emit(evaluateBashBackgroundHook(JSON.parse(
|
|
44159
|
+
emit(evaluateBashBackgroundHook(JSON.parse(readFileSync21(0, "utf8")), env));
|
|
43770
44160
|
} catch {
|
|
43771
44161
|
emit({});
|
|
43772
44162
|
}
|
|
@@ -43775,11 +44165,11 @@ var init_bash_background = __esm({
|
|
|
43775
44165
|
});
|
|
43776
44166
|
|
|
43777
44167
|
// 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
|
|
44168
|
+
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
44169
|
import { createHash as createHash13 } from "node:crypto";
|
|
43780
44170
|
import { StringDecoder as StringDecoder4 } from "node:string_decoder";
|
|
43781
|
-
import { join as
|
|
43782
|
-
import { homedir as
|
|
44171
|
+
import { join as join34 } from "node:path";
|
|
44172
|
+
import { homedir as homedir15 } from "node:os";
|
|
43783
44173
|
function formatTokens(n) {
|
|
43784
44174
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
43785
44175
|
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
@@ -44329,18 +44719,18 @@ function looksLikeImage(buf, ext) {
|
|
|
44329
44719
|
}
|
|
44330
44720
|
function pruneScreenshotCache() {
|
|
44331
44721
|
try {
|
|
44332
|
-
const
|
|
44333
|
-
if (
|
|
44334
|
-
const byMtime =
|
|
44722
|
+
const entries2 = readdirSync9(SCREENSHOT_CACHE_DIR).filter((n) => !n.startsWith("."));
|
|
44723
|
+
if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
|
|
44724
|
+
const byMtime = entries2.map((n) => {
|
|
44335
44725
|
try {
|
|
44336
|
-
return { n, m: statSync14(
|
|
44726
|
+
return { n, m: statSync14(join34(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
|
|
44337
44727
|
} catch {
|
|
44338
44728
|
return { n, m: 0 };
|
|
44339
44729
|
}
|
|
44340
44730
|
}).sort((a, b) => b.m - a.m);
|
|
44341
44731
|
for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
|
|
44342
44732
|
try {
|
|
44343
|
-
unlinkSync5(
|
|
44733
|
+
unlinkSync5(join34(SCREENSHOT_CACHE_DIR, n));
|
|
44344
44734
|
} catch {
|
|
44345
44735
|
}
|
|
44346
44736
|
}
|
|
@@ -44370,14 +44760,14 @@ function persistBase64Image(mediaType, data, idKey) {
|
|
|
44370
44760
|
const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
|
|
44371
44761
|
if (!ext || !data) return void 0;
|
|
44372
44762
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
44373
|
-
const path =
|
|
44763
|
+
const path = join34(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
|
|
44374
44764
|
try {
|
|
44375
44765
|
if (existsSync12(path)) return path;
|
|
44376
44766
|
if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
|
|
44377
44767
|
const buf = Buffer.from(data, "base64");
|
|
44378
44768
|
if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
|
|
44379
44769
|
mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
44380
|
-
const tmp =
|
|
44770
|
+
const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
44381
44771
|
writeFileSync12(tmp, buf);
|
|
44382
44772
|
renameSync9(tmp, path);
|
|
44383
44773
|
pruneScreenshotCache();
|
|
@@ -44392,14 +44782,14 @@ function persistSentFile(srcPath, idKey) {
|
|
|
44392
44782
|
const outExt = ext === "jpeg" ? "jpg" : ext;
|
|
44393
44783
|
try {
|
|
44394
44784
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
44395
|
-
const dest =
|
|
44785
|
+
const dest = join34(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
|
|
44396
44786
|
if (existsSync12(dest)) return dest;
|
|
44397
44787
|
const size = statSync14(srcPath).size;
|
|
44398
44788
|
if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
|
|
44399
|
-
const buf =
|
|
44789
|
+
const buf = readFileSync22(srcPath);
|
|
44400
44790
|
if (!looksLikeImage(buf, outExt)) return void 0;
|
|
44401
44791
|
mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
44402
|
-
const tmp =
|
|
44792
|
+
const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
44403
44793
|
writeFileSync12(tmp, buf);
|
|
44404
44794
|
renameSync9(tmp, dest);
|
|
44405
44795
|
pruneScreenshotCache();
|
|
@@ -44837,7 +45227,7 @@ function readTranscript(project, sessionId) {
|
|
|
44837
45227
|
}
|
|
44838
45228
|
function verifyIncrementalParse(path, identityPrefix, incremental) {
|
|
44839
45229
|
try {
|
|
44840
|
-
const fresh = parseTranscript(
|
|
45230
|
+
const fresh = parseTranscript(readFileSync22(path, "utf8"), identityPrefix);
|
|
44841
45231
|
const a = JSON.stringify(incremental);
|
|
44842
45232
|
const b = JSON.stringify(fresh);
|
|
44843
45233
|
if (a !== b) {
|
|
@@ -44857,16 +45247,16 @@ function verifyIncrementalParse(path, identityPrefix, incremental) {
|
|
|
44857
45247
|
}
|
|
44858
45248
|
}
|
|
44859
45249
|
function logDirOf(project) {
|
|
44860
|
-
return
|
|
45250
|
+
return join34(homedir15(), ".claude", "projects", project.cwdSlug);
|
|
44861
45251
|
}
|
|
44862
45252
|
function resolveTranscriptPath(project, sessionId) {
|
|
44863
|
-
const path =
|
|
45253
|
+
const path = join34(logDirOf(project), `${sessionId}.jsonl`);
|
|
44864
45254
|
try {
|
|
44865
45255
|
if (statSync14(path).size > 0) return path;
|
|
44866
45256
|
} catch {
|
|
44867
45257
|
}
|
|
44868
45258
|
const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
|
|
44869
|
-
return stranded ?
|
|
45259
|
+
return stranded ? join34(stranded, `${sessionId}.jsonl`) : path;
|
|
44870
45260
|
}
|
|
44871
45261
|
function projectCodexTranscript(raw2, identityPrefix = "codex") {
|
|
44872
45262
|
const out = [];
|
|
@@ -45395,10 +45785,10 @@ function wrappedInvocations(source) {
|
|
|
45395
45785
|
let open = nameStart + name.length;
|
|
45396
45786
|
while (/\s/.test(source[open] ?? "")) open++;
|
|
45397
45787
|
if (source[open] !== "(") continue;
|
|
45398
|
-
const
|
|
45399
|
-
if (
|
|
45400
|
-
out.push({ name, args: source.slice(open + 1,
|
|
45401
|
-
i =
|
|
45788
|
+
const close2 = matchingParen(source, open);
|
|
45789
|
+
if (close2 === -1) break;
|
|
45790
|
+
out.push({ name, args: source.slice(open + 1, close2).trim() });
|
|
45791
|
+
i = close2;
|
|
45402
45792
|
}
|
|
45403
45793
|
return out;
|
|
45404
45794
|
}
|
|
@@ -46074,7 +46464,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
|
|
|
46074
46464
|
}
|
|
46075
46465
|
function readCodexTranscriptFile(absPath, nativeId = absPath) {
|
|
46076
46466
|
try {
|
|
46077
|
-
return parseCodexTranscript(
|
|
46467
|
+
return parseCodexTranscript(readFileSync22(absPath, "utf8"), `codex:${nativeId}`);
|
|
46078
46468
|
} catch {
|
|
46079
46469
|
return [];
|
|
46080
46470
|
}
|
|
@@ -46172,7 +46562,7 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
|
|
|
46172
46562
|
function readTranscriptFile(absPath) {
|
|
46173
46563
|
try {
|
|
46174
46564
|
const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
|
|
46175
|
-
return parseTranscript(
|
|
46565
|
+
return parseTranscript(readFileSync22(absPath, "utf8"), `claude-file:${pathKey}`);
|
|
46176
46566
|
} catch {
|
|
46177
46567
|
return [];
|
|
46178
46568
|
}
|
|
@@ -46271,9 +46661,9 @@ var init_open_external = __esm({
|
|
|
46271
46661
|
|
|
46272
46662
|
// packages/server/src/local-file.ts
|
|
46273
46663
|
import { spawn as spawn6 } from "node:child_process";
|
|
46274
|
-
import { readFileSync as
|
|
46275
|
-
import { homedir as
|
|
46276
|
-
import { isAbsolute as isAbsolute4, join as
|
|
46664
|
+
import { readFileSync as readFileSync23, realpathSync as realpathSync8, statSync as statSync15 } from "node:fs";
|
|
46665
|
+
import { homedir as homedir16 } from "node:os";
|
|
46666
|
+
import { isAbsolute as isAbsolute4, join as join35, resolve as resolve8, sep as sep2 } from "node:path";
|
|
46277
46667
|
function isUnder(real, root) {
|
|
46278
46668
|
let rootReal;
|
|
46279
46669
|
try {
|
|
@@ -46300,10 +46690,10 @@ function resolveLocalFile(rawPath, roots) {
|
|
|
46300
46690
|
}
|
|
46301
46691
|
return real;
|
|
46302
46692
|
}
|
|
46303
|
-
function resolveOpenableFile(raw2, projectDir, roots, home =
|
|
46693
|
+
function resolveOpenableFile(raw2, projectDir, roots, home = homedir16()) {
|
|
46304
46694
|
const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
|
|
46305
46695
|
if (!trimmed) return null;
|
|
46306
|
-
const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ?
|
|
46696
|
+
const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join35(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve8(projectDir, trimmed);
|
|
46307
46697
|
try {
|
|
46308
46698
|
return resolveLocalFile(abs, roots);
|
|
46309
46699
|
} catch {
|
|
@@ -46312,7 +46702,7 @@ function resolveOpenableFile(raw2, projectDir, roots, home = homedir15()) {
|
|
|
46312
46702
|
}
|
|
46313
46703
|
function readLocalTextFile(rawPath, roots) {
|
|
46314
46704
|
const path = resolveLocalFile(rawPath, roots);
|
|
46315
|
-
const bytes =
|
|
46705
|
+
const bytes = readFileSync23(path);
|
|
46316
46706
|
if (bytes.subarray(0, 8192).includes(0)) throw new Error("Local file is not a text file");
|
|
46317
46707
|
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path, text: bytes.toString("utf8"), truncated: false };
|
|
46318
46708
|
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
@@ -46323,12 +46713,19 @@ function readLocalMarkdown(rawPath, roots) {
|
|
|
46323
46713
|
if (!MARKDOWN_FILE_EXT.test(rawPath.trim())) throw new Error("Local file is not a Markdown file");
|
|
46324
46714
|
const path = resolveLocalFile(rawPath, roots);
|
|
46325
46715
|
if (!MARKDOWN_FILE_EXT.test(path)) throw new Error("Local file is not a Markdown file");
|
|
46326
|
-
const bytes =
|
|
46716
|
+
const bytes = readFileSync23(path);
|
|
46327
46717
|
if (bytes.length <= MARKDOWN_READ_LIMIT) return { path, markdown: bytes.toString("utf8"), truncated: false };
|
|
46328
46718
|
const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
|
|
46329
46719
|
const lastBreak = head.lastIndexOf(10);
|
|
46330
46720
|
return { path, markdown: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
|
|
46331
46721
|
}
|
|
46722
|
+
function resolveWatchableLocalFile(rawPath, projectDir, openRoots) {
|
|
46723
|
+
if (MARKDOWN_FILE_EXT.test(rawPath.trim())) {
|
|
46724
|
+
const path = resolveLocalFile(rawPath, openRoots);
|
|
46725
|
+
if (MARKDOWN_FILE_EXT.test(path)) return path;
|
|
46726
|
+
}
|
|
46727
|
+
return resolveLocalFile(rawPath, [projectDir]);
|
|
46728
|
+
}
|
|
46332
46729
|
function defaultSpawn(command, args, options) {
|
|
46333
46730
|
return spawn6(command, [...args], options);
|
|
46334
46731
|
}
|
|
@@ -47022,12 +47419,12 @@ var init_directory_picker = __esm({
|
|
|
47022
47419
|
});
|
|
47023
47420
|
|
|
47024
47421
|
// packages/server/src/router.ts
|
|
47025
|
-
import { readFileSync as
|
|
47026
|
-
import { join as
|
|
47422
|
+
import { readFileSync as readFileSync24, statSync as statSync17 } from "node:fs";
|
|
47423
|
+
import { join as join36, resolve as resolve9 } from "node:path";
|
|
47027
47424
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
47028
47425
|
import { basename as basename9, dirname as dirname14 } from "node:path";
|
|
47029
47426
|
import { existsSync as existsSync13, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
|
|
47030
|
-
import { homedir as
|
|
47427
|
+
import { homedir as homedir17 } from "node:os";
|
|
47031
47428
|
function validateGithubDispatchProfile(input) {
|
|
47032
47429
|
validateThreadProfile(input.backend, input.model, input.effort);
|
|
47033
47430
|
}
|
|
@@ -47122,6 +47519,10 @@ function holdOps(ops) {
|
|
|
47122
47519
|
state: op.state
|
|
47123
47520
|
}));
|
|
47124
47521
|
}
|
|
47522
|
+
function cutOffHold(telemetry) {
|
|
47523
|
+
if (telemetry?.turn !== "in-flight") return void 0;
|
|
47524
|
+
return { turnInFlight: true, cutOff: true, unobservable: false, subAgents: [], subAgentCount: 0, bgShells: [], bgShellCount: 0 };
|
|
47525
|
+
}
|
|
47125
47526
|
async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker) {
|
|
47126
47527
|
const binding = adoptionRuntimeBinding(storage, row);
|
|
47127
47528
|
if (binding.kind === "conflict") {
|
|
@@ -47130,7 +47531,7 @@ async function completeRegisteredThread(storage, row, terminateLive, runtime = c
|
|
|
47130
47531
|
const appServerCodex = isAppServerCodexRow(row);
|
|
47131
47532
|
const brokerClaude = isBrokerClaudeRow(row);
|
|
47132
47533
|
const live = brokerClaude ? claudeBroker?.isDaemonAlive(row.session_id) ?? false : appServerCodex ? appServerCodexTurnLive(codex, row) : false;
|
|
47133
|
-
const hold =
|
|
47534
|
+
const hold = terminateLive ? void 0 : live ? completionConfirmationHold(telemetry) : cutOffHold(telemetry);
|
|
47134
47535
|
if (hold) return { needsConfirmation: true, hold };
|
|
47135
47536
|
if (live) {
|
|
47136
47537
|
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
|
|
@@ -47183,10 +47584,10 @@ function projectCard(entry, stale) {
|
|
|
47183
47584
|
iconIsCustom: entry.iconSource === "custom" ? true : void 0
|
|
47184
47585
|
};
|
|
47185
47586
|
}
|
|
47186
|
-
function addProjectAtPath(input) {
|
|
47587
|
+
function addProjectAtPath(input, home = homedir17()) {
|
|
47187
47588
|
const typed = input.trim();
|
|
47188
47589
|
if (!typed) throw new Error("Enter a folder path.");
|
|
47189
|
-
const expanded = typed === "~" || typed.startsWith("~/") ?
|
|
47590
|
+
const expanded = typed === "~" || typed.startsWith("~/") ? join36(home, typed.slice(1)) : typed;
|
|
47190
47591
|
const absolute = resolve9(expanded);
|
|
47191
47592
|
let stats;
|
|
47192
47593
|
try {
|
|
@@ -47195,12 +47596,13 @@ function addProjectAtPath(input) {
|
|
|
47195
47596
|
throw new Error(`No folder at ${absolute}`);
|
|
47196
47597
|
}
|
|
47197
47598
|
if (!stats.isDirectory()) throw new Error(`That is a file, not a folder: ${absolute}`);
|
|
47198
|
-
const root =
|
|
47199
|
-
|
|
47599
|
+
const root = chosenProjectRoot(absolute, home);
|
|
47600
|
+
if (isHomeDirectory(root, home)) throw new Error("The home folder cannot be a project \u2014 choose a folder inside it.");
|
|
47601
|
+
const id = ensureProjectIdFile(root, home, existingProjectId(root));
|
|
47200
47602
|
const remoteOwner = resolveProjectLabel(root)?.split("/")[0];
|
|
47201
|
-
let registered = registerProject({ dir: root, id, remoteOwner });
|
|
47603
|
+
let registered = registerProject({ dir: root, id, remoteOwner }, home);
|
|
47202
47604
|
if (registered.action === "duplicate") {
|
|
47203
|
-
registered = registerProject({ dir: root, id: writeProjectIdFile(root, randomUUID16()), remoteOwner });
|
|
47605
|
+
registered = registerProject({ dir: root, id: writeProjectIdFile(root, randomUUID16()), remoteOwner }, home);
|
|
47204
47606
|
}
|
|
47205
47607
|
if (!registered.entry) throw new Error("Could not register that folder.");
|
|
47206
47608
|
return projectCard(registered.entry, false);
|
|
@@ -47231,7 +47633,7 @@ function storeProjectIcon(id, name, bytes) {
|
|
|
47231
47633
|
function setProjectIconFromFile(id, file) {
|
|
47232
47634
|
let bytes;
|
|
47233
47635
|
try {
|
|
47234
|
-
bytes =
|
|
47636
|
+
bytes = readFileSync24(file);
|
|
47235
47637
|
} catch {
|
|
47236
47638
|
throw new Error("That file could not be read.");
|
|
47237
47639
|
}
|
|
@@ -47239,7 +47641,7 @@ function setProjectIconFromFile(id, file) {
|
|
|
47239
47641
|
return storeProjectIcon(id, file, bytes);
|
|
47240
47642
|
}
|
|
47241
47643
|
function createRouter(ctx) {
|
|
47242
|
-
const frizzDir =
|
|
47644
|
+
const frizzDir = join36(ctx.project.dir, ".frizz");
|
|
47243
47645
|
const openRoots = openableFileRoots(ctx.project);
|
|
47244
47646
|
function isAutoTitledSession(slug) {
|
|
47245
47647
|
return ctx.storage.getSession(slug)?.title_auto === 1;
|
|
@@ -47390,6 +47792,9 @@ function createRouter(ctx) {
|
|
|
47390
47792
|
if (row.claude_runtime !== "broker" || !ctx.claudeBroker) {
|
|
47391
47793
|
return blocked("Steering a sub-agent needs the Claude session broker; this thread predates it.");
|
|
47392
47794
|
}
|
|
47795
|
+
if (ctx.tailer.get(slug)?.turn === "in-flight") {
|
|
47796
|
+
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.");
|
|
47797
|
+
}
|
|
47393
47798
|
return { sessionId: row.session_id };
|
|
47394
47799
|
}
|
|
47395
47800
|
function codexShellTarget(slug, id) {
|
|
@@ -47644,9 +48049,10 @@ function createRouter(ctx) {
|
|
|
47644
48049
|
//
|
|
47645
48050
|
// WHY THE GATE IS STRICT. Measured live: addressing a child that has ALREADY SETTLED does not
|
|
47646
48051
|
// 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
|
-
//
|
|
48052
|
+
// parent obeys it as if the operator had typed it into the thread composer. A steer sent while
|
|
48053
|
+
// the parent's own turn is IN FLIGHT misdelivers the same way (absorbed into that turn — see the
|
|
48054
|
+
// predicate). So an ungated steer is not a no-op, it is a misdelivery. `subAgentSteerable` is the
|
|
48055
|
+
// single predicate that decides, and the drawer's prompt box is rendered off the same answer.
|
|
47650
48056
|
subAgentSteer: mutation({
|
|
47651
48057
|
input: external_exports.object({ slug: ThreadSlug, id: external_exports.string(), message: external_exports.string().min(1), deliveryId: external_exports.string().optional() }).strict(),
|
|
47652
48058
|
output: external_exports.object({ delivered: external_exports.boolean() }),
|
|
@@ -47915,6 +48321,7 @@ ${gapNote}` : input.message;
|
|
|
47915
48321
|
mayHaveLiveBackgroundWork(ctx.tailer.get(input.slug))
|
|
47916
48322
|
)
|
|
47917
48323
|
});
|
|
48324
|
+
if (row.exited === 1) ctx.storage.setExitedIfCurrent(input.slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
47918
48325
|
if (input.freshProcess) retireOutstandingDeliveries(ctx.storage, input.slug);
|
|
47919
48326
|
const preempted = input.interrupt === true && bridge.interruptTurn({ threadSlug: input.slug, sessionId: row.session_id });
|
|
47920
48327
|
if (input.deliveryId) {
|
|
@@ -48191,6 +48598,19 @@ ${gapNote}` : input.message;
|
|
|
48191
48598
|
ctx.board.refresh();
|
|
48192
48599
|
}
|
|
48193
48600
|
}),
|
|
48601
|
+
// Pin/unpin: the human lifts a thread out of the rail's band system into the pinned band at the
|
|
48602
|
+
// very top (or drops it back in). Same editability gate as the snooze, but NO archived refusal —
|
|
48603
|
+
// the pin deliberately outranks Done, so a pinned thread that finishes stays pinned until unpinned.
|
|
48604
|
+
setThreadPinned: mutation({
|
|
48605
|
+
input: SetThreadPinnedInput,
|
|
48606
|
+
handler: async ({ input }) => {
|
|
48607
|
+
currentOwnedSession(input.slug, input.sessionId);
|
|
48608
|
+
const thread = (await ctx.board.snapshot()).threads.find((candidate) => candidate.id === input.slug);
|
|
48609
|
+
if (!thread || thread.kind !== "session" || thread.foreign) throw new Error(`thread ${input.slug} is not editable`);
|
|
48610
|
+
ctx.storage.setPinnedAt(input.slug, input.pinned ? (/* @__PURE__ */ new Date()).toISOString() : null);
|
|
48611
|
+
ctx.board.refresh();
|
|
48612
|
+
}
|
|
48613
|
+
}),
|
|
48194
48614
|
// Re-read the worker plugin closure INTO the live session: hooks, skills, agent profiles and MCP
|
|
48195
48615
|
// servers, without restarting the process. This is `/reload-plugins` driven from the board.
|
|
48196
48616
|
//
|
|
@@ -48239,7 +48659,7 @@ ${gapNote}` : input.message;
|
|
|
48239
48659
|
throw new Error("This thread moved on; reopen it and try again");
|
|
48240
48660
|
}
|
|
48241
48661
|
if (input.stopHook && input.prompt?.trim() && autonomousGoal(row) === void 0) {
|
|
48242
|
-
cancelQuestionsForAutonomy(input.slug);
|
|
48662
|
+
if (cancelQuestionsForAutonomy(input.slug) > 0) ctx.scheduler.kick();
|
|
48243
48663
|
}
|
|
48244
48664
|
ctx.board.refresh();
|
|
48245
48665
|
}
|
|
@@ -48271,7 +48691,7 @@ ${gapNote}` : input.message;
|
|
|
48271
48691
|
throw new Error(`thread ${input.slug} could not be updated`);
|
|
48272
48692
|
}
|
|
48273
48693
|
if (input.stopHook && input.prompt?.trim() && autonomousGoal(row) === void 0) {
|
|
48274
|
-
cancelQuestionsForAutonomy(input.slug);
|
|
48694
|
+
if (cancelQuestionsForAutonomy(input.slug) > 0) ctx.scheduler.kick();
|
|
48275
48695
|
}
|
|
48276
48696
|
ctx.board.refresh();
|
|
48277
48697
|
return { replaced };
|
|
@@ -48431,14 +48851,19 @@ ${gapNote}` : input.message;
|
|
|
48431
48851
|
const armed = ctx.storage.listPrWatches(input.slug, { armedOnly: true });
|
|
48432
48852
|
const existing = armed.find((w) => w.owner === ref.owner && w.repo === ref.repo && w.number === ref.number);
|
|
48433
48853
|
const target = `${ref.owner}/${ref.repo}#${ref.number}`;
|
|
48434
|
-
if (existing)
|
|
48854
|
+
if (existing) {
|
|
48855
|
+
const expiresAt = new Date(existing.expires_at ?? Date.now()).toISOString();
|
|
48856
|
+
return { id: existing.id, target, alreadyArmed: true, expiresAt, watches: armedPrWatchViews(input.slug) };
|
|
48857
|
+
}
|
|
48435
48858
|
if (armed.length >= PR_WATCH_MAX_ARMED) {
|
|
48436
48859
|
throw new Error(`this thread already watches ${armed.length} pull requests (the limit is ${PR_WATCH_MAX_ARMED}) \u2014 drop one first`);
|
|
48437
48860
|
}
|
|
48438
|
-
const
|
|
48439
|
-
if (
|
|
48440
|
-
throw new Error(`\`for: ${input.for}\` is not a duration \u2014 give one like \`
|
|
48861
|
+
const asked = input.for === void 0 ? PR_WATCH_DEFAULT_FOR_MS : parseAwaitingDurationRaw(input.for);
|
|
48862
|
+
if (asked === null) {
|
|
48863
|
+
throw new Error(`\`for: ${input.for}\` is not a duration \u2014 give one like \`2h\`, \`3d\` or \`180d\` (max 365d)`);
|
|
48441
48864
|
}
|
|
48865
|
+
const forMs = Math.min(asked, PR_WATCH_FOR_MAX_MS);
|
|
48866
|
+
const clampedFrom = input.for !== void 0 && asked > PR_WATCH_FOR_MAX_MS ? { clampedFrom: input.for } : {};
|
|
48442
48867
|
const probe = await ctx.probePr(ref);
|
|
48443
48868
|
if (!probe.ok) {
|
|
48444
48869
|
throw new Error(
|
|
@@ -48450,7 +48875,7 @@ ${gapNote}` : input.message;
|
|
|
48450
48875
|
ctx.storage.clearThreadDone(input.slug);
|
|
48451
48876
|
ctx.storage.armPrWatch({ id, slug: input.slug, owner: ref.owner, repo: ref.repo, number: ref.number, createdAtMs: now, expiresAtMs: now + forMs });
|
|
48452
48877
|
ctx.board.refresh();
|
|
48453
|
-
return { id, target, alreadyArmed: false, watches: armedPrWatchViews(input.slug) };
|
|
48878
|
+
return { id, target, alreadyArmed: false, expiresAt: new Date(now + forMs).toISOString(), ...clampedFrom, watches: armedPrWatchViews(input.slug) };
|
|
48454
48879
|
}
|
|
48455
48880
|
}),
|
|
48456
48881
|
dropOwnPrWatch: mutation({
|
|
@@ -48495,16 +48920,18 @@ ${gapNote}` : input.message;
|
|
|
48495
48920
|
if (armed.length >= OWN_WATCH_MAX_ARMED) {
|
|
48496
48921
|
throw new Error(`this thread already holds ${armed.length} watches (the limit is ${OWN_WATCH_MAX_ARMED}) \u2014 drop one first`);
|
|
48497
48922
|
}
|
|
48498
|
-
const
|
|
48499
|
-
if (
|
|
48923
|
+
const asked = parseAwaitingDurationRaw(input.for);
|
|
48924
|
+
if (asked === null) {
|
|
48500
48925
|
throw new Error(`\`for: ${input.for}\` is not a duration \u2014 give one like \`30m\`, \`2h\` or \`3d\` (max 24h)`);
|
|
48501
48926
|
}
|
|
48927
|
+
const forMs = Math.min(asked, AWAITING_FOR_MAX_MS);
|
|
48928
|
+
const clampedFrom = asked > AWAITING_FOR_MAX_MS ? { clampedFrom: input.for } : {};
|
|
48502
48929
|
const now = Date.now();
|
|
48503
48930
|
const id = `wch_${randomUUID16().replace(/-/g, "").slice(0, 12)}`;
|
|
48504
48931
|
ctx.storage.clearThreadDone(input.slug);
|
|
48505
48932
|
ctx.storage.armThreadWatch({ id, slug: input.slug, kind: input.kind, target, createdAtMs: now, expiresAtMs: now + forMs });
|
|
48506
48933
|
ctx.board.refresh();
|
|
48507
|
-
return { id, kind: input.kind, target, alreadyArmed: false, watches: armedOwnWatchViews(input.slug) };
|
|
48934
|
+
return { id, kind: input.kind, target, alreadyArmed: false, ...clampedFrom, watches: armedOwnWatchViews(input.slug) };
|
|
48508
48935
|
}
|
|
48509
48936
|
}),
|
|
48510
48937
|
dropOwnWatch: mutation({
|
|
@@ -48547,12 +48974,6 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
48547
48974
|
}
|
|
48548
48975
|
const faults = input.questions.flatMap((q) => askedQuestionFaults(q));
|
|
48549
48976
|
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
48977
|
const now = Date.now();
|
|
48557
48978
|
const registered = input.questions.map((spec) => {
|
|
48558
48979
|
const id = `qst_${randomUUID16().replace(/-/g, "").slice(0, 12)}`;
|
|
@@ -49191,8 +49612,8 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
49191
49612
|
* Register a directory as a project, from the grid's phantom card.
|
|
49192
49613
|
*
|
|
49193
49614
|
* The same authority as running `frizz` in that directory, and strictly less: this registers and
|
|
49194
|
-
* resolves an id, it dispatches nothing.
|
|
49195
|
-
*
|
|
49615
|
+
* resolves an id, it dispatches nothing. The root comes from chosenProjectRoot — a folder inside
|
|
49616
|
+
* a checkout adds the checkout, but an adopted plain-directory ancestor never captures the pick.
|
|
49196
49617
|
*/
|
|
49197
49618
|
projectAdd: mutation({
|
|
49198
49619
|
input: external_exports.object({ path: external_exports.string().min(1) }),
|
|
@@ -49573,7 +49994,7 @@ var init_compression = __esm({
|
|
|
49573
49994
|
});
|
|
49574
49995
|
|
|
49575
49996
|
// packages/server/src/local-image.ts
|
|
49576
|
-
import { readFileSync as
|
|
49997
|
+
import { readFileSync as readFileSync25, realpathSync as realpathSync9, statSync as statSync18 } from "node:fs";
|
|
49577
49998
|
import { extname as extname2, isAbsolute as isAbsolute5 } from "node:path";
|
|
49578
49999
|
function resolveLocalImage(rawPath) {
|
|
49579
50000
|
if (!rawPath || !isAbsolute5(rawPath)) return { status: 400 };
|
|
@@ -49587,7 +50008,7 @@ function resolveLocalImage(rawPath) {
|
|
|
49587
50008
|
}
|
|
49588
50009
|
try {
|
|
49589
50010
|
if (!statSync18(real).isFile()) return { status: 404 };
|
|
49590
|
-
return { status: 200, contentType, body:
|
|
50011
|
+
return { status: 200, contentType, body: readFileSync25(real) };
|
|
49591
50012
|
} catch {
|
|
49592
50013
|
return { status: 404 };
|
|
49593
50014
|
}
|
|
@@ -49607,9 +50028,9 @@ var init_local_image = __esm({
|
|
|
49607
50028
|
});
|
|
49608
50029
|
|
|
49609
50030
|
// 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
|
|
50031
|
+
import { readFileSync as readFileSync26, readdirSync as readdirSync10, realpathSync as realpathSync10, statSync as statSync19 } from "node:fs";
|
|
50032
|
+
import { homedir as homedir18 } from "node:os";
|
|
50033
|
+
import { basename as basename10, dirname as dirname15, extname as extname3, join as join37, relative as relative2, sep as sep3 } from "node:path";
|
|
49613
50034
|
function normalizeStem(stem) {
|
|
49614
50035
|
const sized = stem.toLowerCase().replace(/[@-]\d+(\.\d+)?x(\d+)?$/u, "").replace(/[-_]\d{2,4}$/u, "");
|
|
49615
50036
|
const normalized = sized.replace(VARIANT_SUFFIX, "");
|
|
@@ -49627,7 +50048,7 @@ function sizeScore(dimensions) {
|
|
|
49627
50048
|
return Math.max(0, Math.min(40, Math.round(Math.log2(edge / 24) * 8)));
|
|
49628
50049
|
}
|
|
49629
50050
|
function under(root, relativeDirectory) {
|
|
49630
|
-
return relativeDirectory ?
|
|
50051
|
+
return relativeDirectory ? join37(root, relativeDirectory) : root;
|
|
49631
50052
|
}
|
|
49632
50053
|
function listFiles(directory) {
|
|
49633
50054
|
try {
|
|
@@ -49649,19 +50070,19 @@ function iconDirectories(root) {
|
|
|
49649
50070
|
for (const asset of ASSET_DIRECTORIES) directories.push(under(root, asset));
|
|
49650
50071
|
for (const host of HOST_DIRECTORIES) {
|
|
49651
50072
|
if (!children2.has(host)) continue;
|
|
49652
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
50073
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join37(root, host), asset));
|
|
49653
50074
|
}
|
|
49654
50075
|
for (const extra of EXTRA_DIRECTORIES) directories.push(under(root, extra));
|
|
49655
50076
|
for (const container of WORKSPACE_CONTAINERS) {
|
|
49656
50077
|
if (!children2.has(container)) continue;
|
|
49657
50078
|
let members;
|
|
49658
50079
|
try {
|
|
49659
|
-
members = readdirSync10(
|
|
50080
|
+
members = readdirSync10(join37(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
49660
50081
|
} catch {
|
|
49661
50082
|
continue;
|
|
49662
50083
|
}
|
|
49663
50084
|
for (const member of members) {
|
|
49664
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
50085
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join37(root, container, member), asset));
|
|
49665
50086
|
}
|
|
49666
50087
|
}
|
|
49667
50088
|
return [...new Set(directories)];
|
|
@@ -49669,13 +50090,13 @@ function iconDirectories(root) {
|
|
|
49669
50090
|
function manifestIcons(manifestPath) {
|
|
49670
50091
|
let parsed;
|
|
49671
50092
|
try {
|
|
49672
|
-
parsed = JSON.parse(
|
|
50093
|
+
parsed = JSON.parse(readFileSync26(manifestPath, "utf8"));
|
|
49673
50094
|
} catch {
|
|
49674
50095
|
return [];
|
|
49675
50096
|
}
|
|
49676
50097
|
if (!Array.isArray(parsed?.icons)) return [];
|
|
49677
50098
|
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) =>
|
|
50099
|
+
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
50100
|
}
|
|
49680
50101
|
function projectIconCandidates(root) {
|
|
49681
50102
|
const projectStem = normalizeStem(basename10(root)).normalized;
|
|
@@ -49694,7 +50115,7 @@ function projectIconCandidates(root) {
|
|
|
49694
50115
|
for (const name of listFiles(directory)) {
|
|
49695
50116
|
const extension2 = extname3(name).toLowerCase();
|
|
49696
50117
|
if (MANIFEST_NAMES.includes(name.toLowerCase())) {
|
|
49697
|
-
for (const icon of manifestIcons(
|
|
50118
|
+
for (const icon of manifestIcons(join37(directory, name))) {
|
|
49698
50119
|
if (MEASURABLE_IMAGE_EXTENSIONS.has(extname3(icon).toLowerCase())) {
|
|
49699
50120
|
fromManifest.add(icon);
|
|
49700
50121
|
files.push({ path: icon, directory: dirname15(icon) });
|
|
@@ -49705,7 +50126,7 @@ function projectIconCandidates(root) {
|
|
|
49705
50126
|
if (!MEASURABLE_IMAGE_EXTENSIONS.has(extension2)) continue;
|
|
49706
50127
|
const stem = name.slice(0, name.length - extension2.length);
|
|
49707
50128
|
if (normalizeStem(stem).normalized === "favicon") iconHomes.add(directory);
|
|
49708
|
-
files.push({ path:
|
|
50129
|
+
files.push({ path: join37(directory, name), directory });
|
|
49709
50130
|
if (files.length >= MAX_CANDIDATES) break;
|
|
49710
50131
|
}
|
|
49711
50132
|
}
|
|
@@ -49747,7 +50168,7 @@ function iconMediaType(path) {
|
|
|
49747
50168
|
}
|
|
49748
50169
|
function resolveProjectIconResponse(id, options = {}) {
|
|
49749
50170
|
if (!id) return { status: 400 };
|
|
49750
|
-
const home = options.home ??
|
|
50171
|
+
const home = options.home ?? homedir18();
|
|
49751
50172
|
const entry = readRegistry(home).projects.find((project) => project.id === id);
|
|
49752
50173
|
if (!entry) return { status: 404 };
|
|
49753
50174
|
const path = resolveProjectIcon(id, (root) => detectProjectIcon(root)?.path, { home });
|
|
@@ -49770,7 +50191,7 @@ function resolveProjectIconResponse(id, options = {}) {
|
|
|
49770
50191
|
});
|
|
49771
50192
|
if (!roots.some((root) => real === root || real.startsWith(root + sep3))) return { status: 404 };
|
|
49772
50193
|
try {
|
|
49773
|
-
return { status: 200, contentType, body:
|
|
50194
|
+
return { status: 200, contentType, body: readFileSync26(real) };
|
|
49774
50195
|
} catch {
|
|
49775
50196
|
return { status: 404 };
|
|
49776
50197
|
}
|
|
@@ -49882,8 +50303,8 @@ var init_project_icon = __esm({
|
|
|
49882
50303
|
});
|
|
49883
50304
|
|
|
49884
50305
|
// packages/server/src/local-visualization.ts
|
|
49885
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
49886
|
-
import { join as
|
|
50306
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync27, realpathSync as realpathSync11, statSync as statSync20 } from "node:fs";
|
|
50307
|
+
import { join as join38, sep as sep4 } from "node:path";
|
|
49887
50308
|
function children(path, pattern) {
|
|
49888
50309
|
try {
|
|
49889
50310
|
return readdirSync11(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
|
|
@@ -49895,7 +50316,7 @@ function isUnder2(path, root) {
|
|
|
49895
50316
|
return path === root || path.startsWith(root.endsWith(sep4) ? root : root + sep4);
|
|
49896
50317
|
}
|
|
49897
50318
|
function resolveFragment(projectDir, sessionId, file) {
|
|
49898
|
-
const base =
|
|
50319
|
+
const base = join38(projectDir, ".codex", "visualizations");
|
|
49899
50320
|
let projectReal;
|
|
49900
50321
|
let baseReal;
|
|
49901
50322
|
try {
|
|
@@ -49906,12 +50327,12 @@ function resolveFragment(projectDir, sessionId, file) {
|
|
|
49906
50327
|
}
|
|
49907
50328
|
if (!isUnder2(baseReal, projectReal)) return null;
|
|
49908
50329
|
for (const year of children(base, YEAR_PART)) {
|
|
49909
|
-
const yearDir =
|
|
50330
|
+
const yearDir = join38(base, year);
|
|
49910
50331
|
for (const month of children(yearDir, DATE_PART)) {
|
|
49911
|
-
const monthDir =
|
|
50332
|
+
const monthDir = join38(yearDir, month);
|
|
49912
50333
|
for (const day of children(monthDir, DATE_PART)) {
|
|
49913
|
-
const sessionRoot =
|
|
49914
|
-
const candidate =
|
|
50334
|
+
const sessionRoot = join38(baseReal, year, month, day, sessionId);
|
|
50335
|
+
const candidate = join38(monthDir, day, sessionId, file);
|
|
49915
50336
|
try {
|
|
49916
50337
|
const real = realpathSync11(candidate);
|
|
49917
50338
|
if (isUnder2(real, sessionRoot) && statSync20(real).isFile()) return real;
|
|
@@ -49997,7 +50418,7 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
|
|
|
49997
50418
|
try {
|
|
49998
50419
|
const size = statSync20(path).size;
|
|
49999
50420
|
if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
|
|
50000
|
-
fragment =
|
|
50421
|
+
fragment = readFileSync27(path, "utf8");
|
|
50001
50422
|
} catch {
|
|
50002
50423
|
return { status: 404 };
|
|
50003
50424
|
}
|
|
@@ -50040,7 +50461,7 @@ var init_local_visualization = __esm({
|
|
|
50040
50461
|
|
|
50041
50462
|
// packages/server/src/app.ts
|
|
50042
50463
|
import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync14 } from "node:fs";
|
|
50043
|
-
import { join as
|
|
50464
|
+
import { join as join39 } from "node:path";
|
|
50044
50465
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
50045
50466
|
function createApp(ctx, options = {}) {
|
|
50046
50467
|
const app = new Hono2();
|
|
@@ -50144,10 +50565,10 @@ function createApp(ctx, options = {}) {
|
|
|
50144
50565
|
if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
|
|
50145
50566
|
const ext = `.${attachmentExtension(name)}`;
|
|
50146
50567
|
const buf = Buffer.from(body.data, "base64");
|
|
50147
|
-
const dir =
|
|
50568
|
+
const dir = join39(ctx.project.stateDir, "attachments");
|
|
50148
50569
|
mkdirSync17(dir, { recursive: true });
|
|
50149
50570
|
const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
|
|
50150
|
-
const path =
|
|
50571
|
+
const path = join39(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
|
|
50151
50572
|
writeFileSync14(path, buf);
|
|
50152
50573
|
return c.json({ path });
|
|
50153
50574
|
});
|
|
@@ -50634,6 +51055,7 @@ function createAppSocketServer(deps) {
|
|
|
50634
51055
|
const maxOutputBufferBytes = deps.maxOutputBufferBytes ?? APP_SOCKET_MAX_OUTPUT_BUFFER_BYTES;
|
|
50635
51056
|
const maxConnections = boundedCount(deps.maxConnections, APP_SOCKET_MAX_CONNECTIONS);
|
|
50636
51057
|
const maxSubscriptions = deps.maxSubscriptionsPerConnection ?? APP_SOCKET_MAX_SUBSCRIPTIONS;
|
|
51058
|
+
const maxFileWatches = boundedCount(deps.maxFileWatchesPerConnection, APP_SOCKET_MAX_FILE_WATCHES);
|
|
50637
51059
|
const maxMessagesPerWindow = deps.maxMessagesPerWindow ?? APP_SOCKET_MAX_MESSAGES_PER_WINDOW;
|
|
50638
51060
|
const messageWindowMs = deps.messageWindowMs ?? APP_SOCKET_MESSAGE_WINDOW_MS;
|
|
50639
51061
|
const maxBufferedKeyframeEvents = deps.maxBufferedKeyframeEvents ?? APP_SOCKET_MAX_BUFFERED_KEYFRAME_EVENTS;
|
|
@@ -50673,6 +51095,55 @@ function createAppSocketServer(deps) {
|
|
|
50673
51095
|
let pendingUpgrades = 0;
|
|
50674
51096
|
const socketOrigins = /* @__PURE__ */ new WeakMap();
|
|
50675
51097
|
const lastSig = /* @__PURE__ */ new Map();
|
|
51098
|
+
const fileWatches = /* @__PURE__ */ new WeakMap();
|
|
51099
|
+
let fileWatchCount = 0;
|
|
51100
|
+
function subscribeFile(ws, path) {
|
|
51101
|
+
let held = fileWatches.get(ws);
|
|
51102
|
+
if (!held) {
|
|
51103
|
+
held = /* @__PURE__ */ new Map();
|
|
51104
|
+
fileWatches.set(ws, held);
|
|
51105
|
+
}
|
|
51106
|
+
if (held.has(path)) return;
|
|
51107
|
+
if (held.size >= maxFileWatches) {
|
|
51108
|
+
closeSocket(ws, 1008, "subscription limit exceeded");
|
|
51109
|
+
return;
|
|
51110
|
+
}
|
|
51111
|
+
const watchFile = deps.watchFile;
|
|
51112
|
+
if (!watchFile) return;
|
|
51113
|
+
let release;
|
|
51114
|
+
try {
|
|
51115
|
+
release = watchFile(path, () => {
|
|
51116
|
+
sendMsg(ws, { t: "file-changed", path });
|
|
51117
|
+
});
|
|
51118
|
+
} catch {
|
|
51119
|
+
return;
|
|
51120
|
+
}
|
|
51121
|
+
held.set(path, release);
|
|
51122
|
+
fileWatchCount++;
|
|
51123
|
+
}
|
|
51124
|
+
function unsubscribeFile(ws, path) {
|
|
51125
|
+
const held = fileWatches.get(ws);
|
|
51126
|
+
const release = held?.get(path);
|
|
51127
|
+
if (!held || !release) return;
|
|
51128
|
+
held.delete(path);
|
|
51129
|
+
fileWatchCount--;
|
|
51130
|
+
try {
|
|
51131
|
+
release();
|
|
51132
|
+
} catch {
|
|
51133
|
+
}
|
|
51134
|
+
}
|
|
51135
|
+
function dropFileWatches(ws) {
|
|
51136
|
+
const held = fileWatches.get(ws);
|
|
51137
|
+
if (!held) return;
|
|
51138
|
+
fileWatches.delete(ws);
|
|
51139
|
+
for (const release of held.values()) {
|
|
51140
|
+
fileWatchCount--;
|
|
51141
|
+
try {
|
|
51142
|
+
release();
|
|
51143
|
+
} catch {
|
|
51144
|
+
}
|
|
51145
|
+
}
|
|
51146
|
+
}
|
|
50676
51147
|
function closeSocket(ws, code, reason) {
|
|
50677
51148
|
if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) return;
|
|
50678
51149
|
try {
|
|
@@ -51145,6 +51616,11 @@ function createAppSocketServer(deps) {
|
|
|
51145
51616
|
return;
|
|
51146
51617
|
}
|
|
51147
51618
|
const msg = parsed.data;
|
|
51619
|
+
if (msg.topic === "file") {
|
|
51620
|
+
if (msg.t === "sub") subscribeFile(ws, msg.path);
|
|
51621
|
+
else unsubscribeFile(ws, msg.path);
|
|
51622
|
+
return;
|
|
51623
|
+
}
|
|
51148
51624
|
if (msg.t === "sub") {
|
|
51149
51625
|
if (registry.isSubscribed(ws, msg.slug)) return;
|
|
51150
51626
|
if (registry.slugsFor(ws).length >= maxSubscriptions) {
|
|
@@ -51165,6 +51641,7 @@ function createAppSocketServer(deps) {
|
|
|
51165
51641
|
}
|
|
51166
51642
|
clearInterval(heartbeat);
|
|
51167
51643
|
cancelPendingSubscriptionPush(ws);
|
|
51644
|
+
dropFileWatches(ws);
|
|
51168
51645
|
const terminationTimer = terminationTimers.get(ws);
|
|
51169
51646
|
if (terminationTimer) clearTimeout(terminationTimer);
|
|
51170
51647
|
terminationTimers.delete(ws);
|
|
@@ -51288,10 +51765,13 @@ function createAppSocketServer(deps) {
|
|
|
51288
51765
|
},
|
|
51289
51766
|
get pendingTranscriptRefreshes() {
|
|
51290
51767
|
return pendingTranscriptRefreshes.size;
|
|
51768
|
+
},
|
|
51769
|
+
get fileWatchCount() {
|
|
51770
|
+
return fileWatchCount;
|
|
51291
51771
|
}
|
|
51292
51772
|
};
|
|
51293
51773
|
}
|
|
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;
|
|
51774
|
+
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
51775
|
var init_app_socket = __esm({
|
|
51296
51776
|
"packages/server/src/app-socket.ts"() {
|
|
51297
51777
|
"use strict";
|
|
@@ -51374,6 +51854,7 @@ var init_app_socket = __esm({
|
|
|
51374
51854
|
APP_SOCKET_MAX_OUTPUT_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
51375
51855
|
APP_SOCKET_MAX_CONNECTIONS = 64;
|
|
51376
51856
|
APP_SOCKET_MAX_SUBSCRIPTIONS = 32;
|
|
51857
|
+
APP_SOCKET_MAX_FILE_WATCHES = 16;
|
|
51377
51858
|
APP_SOCKET_MAX_MESSAGES_PER_WINDOW = 120;
|
|
51378
51859
|
APP_SOCKET_MESSAGE_WINDOW_MS = 1e3;
|
|
51379
51860
|
APP_SOCKET_MAX_BUFFERED_KEYFRAME_EVENTS = 256;
|
|
@@ -51385,11 +51866,60 @@ var init_app_socket = __esm({
|
|
|
51385
51866
|
}
|
|
51386
51867
|
});
|
|
51387
51868
|
|
|
51869
|
+
// packages/server/src/local-file-watch.ts
|
|
51870
|
+
import { watch } from "node:fs";
|
|
51871
|
+
import { basename as basename11, dirname as dirname16 } from "node:path";
|
|
51872
|
+
function close(path) {
|
|
51873
|
+
const entry = entries.get(path);
|
|
51874
|
+
if (!entry) return;
|
|
51875
|
+
entries.delete(path);
|
|
51876
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
51877
|
+
try {
|
|
51878
|
+
entry.watcher.close();
|
|
51879
|
+
} catch {
|
|
51880
|
+
}
|
|
51881
|
+
}
|
|
51882
|
+
function watchLocalFile(path, onChange) {
|
|
51883
|
+
let entry = entries.get(path);
|
|
51884
|
+
if (!entry) {
|
|
51885
|
+
const name = basename11(path);
|
|
51886
|
+
const watcher2 = watch(dirname16(path), { persistent: false }, (_event, changed) => {
|
|
51887
|
+
if (changed !== null && changed !== void 0 && changed.toString() !== name) return;
|
|
51888
|
+
const current = entries.get(path);
|
|
51889
|
+
if (!current) return;
|
|
51890
|
+
if (current.timer) clearTimeout(current.timer);
|
|
51891
|
+
current.timer = setTimeout(() => {
|
|
51892
|
+
current.timer = null;
|
|
51893
|
+
for (const listener of current.listeners) listener();
|
|
51894
|
+
}, SETTLE_MS);
|
|
51895
|
+
current.timer.unref?.();
|
|
51896
|
+
});
|
|
51897
|
+
entry = { watcher: watcher2, listeners: /* @__PURE__ */ new Set(), timer: null };
|
|
51898
|
+
watcher2.on("error", () => close(path));
|
|
51899
|
+
entries.set(path, entry);
|
|
51900
|
+
}
|
|
51901
|
+
entry.listeners.add(onChange);
|
|
51902
|
+
return () => {
|
|
51903
|
+
const current = entries.get(path);
|
|
51904
|
+
if (!current) return;
|
|
51905
|
+
current.listeners.delete(onChange);
|
|
51906
|
+
if (current.listeners.size === 0) close(path);
|
|
51907
|
+
};
|
|
51908
|
+
}
|
|
51909
|
+
var SETTLE_MS, entries;
|
|
51910
|
+
var init_local_file_watch = __esm({
|
|
51911
|
+
"packages/server/src/local-file-watch.ts"() {
|
|
51912
|
+
"use strict";
|
|
51913
|
+
SETTLE_MS = 50;
|
|
51914
|
+
entries = /* @__PURE__ */ new Map();
|
|
51915
|
+
}
|
|
51916
|
+
});
|
|
51917
|
+
|
|
51388
51918
|
// packages/server/src/boot-progress.ts
|
|
51389
|
-
import { mkdirSync as mkdirSync18, readFileSync as
|
|
51390
|
-
import { basename as
|
|
51919
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync28, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
|
|
51920
|
+
import { basename as basename12, dirname as dirname17, join as join40 } from "node:path";
|
|
51391
51921
|
function bootProgressPath(stateDir) {
|
|
51392
|
-
return
|
|
51922
|
+
return join40(stateDir, BOOT_PROGRESS_NAME);
|
|
51393
51923
|
}
|
|
51394
51924
|
function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
51395
51925
|
if (!stateDir) {
|
|
@@ -51409,8 +51939,8 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
51409
51939
|
lastAt = now;
|
|
51410
51940
|
step++;
|
|
51411
51941
|
try {
|
|
51412
|
-
mkdirSync18(
|
|
51413
|
-
const temp =
|
|
51942
|
+
mkdirSync18(dirname17(path), { recursive: true, mode: 448 });
|
|
51943
|
+
const temp = join40(dirname17(path), `.${basename12(path)}.${process.pid}.tmp`);
|
|
51414
51944
|
writeFileSync15(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
|
|
51415
51945
|
`, "utf8");
|
|
51416
51946
|
renameSync10(temp, path);
|
|
@@ -51963,17 +52493,17 @@ var init_legacy_project_db = __esm({
|
|
|
51963
52493
|
|
|
51964
52494
|
// packages/server/src/frizz-db.ts
|
|
51965
52495
|
import { existsSync as existsSync14, mkdirSync as mkdirSync19, readdirSync as readdirSync12, renameSync as renameSync11, statSync as statSync21 } from "node:fs";
|
|
51966
|
-
import { basename as
|
|
52496
|
+
import { basename as basename13, dirname as dirname18, join as join41 } from "node:path";
|
|
51967
52497
|
function frizzDatabaseLocation(options) {
|
|
51968
52498
|
if (options.stateDir !== void 0) {
|
|
51969
|
-
const projectsDir =
|
|
51970
|
-
if (
|
|
51971
|
-
return { path: options.path ??
|
|
52499
|
+
const projectsDir = dirname18(options.stateDir);
|
|
52500
|
+
if (basename13(projectsDir) === "projects") {
|
|
52501
|
+
return { path: options.path ?? join41(dirname18(projectsDir), "ui.db"), projectsDir };
|
|
51972
52502
|
}
|
|
51973
|
-
return { path: options.path ??
|
|
52503
|
+
return { path: options.path ?? join41(options.stateDir, "ui.db"), projectsDir: void 0 };
|
|
51974
52504
|
}
|
|
51975
52505
|
const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
|
|
51976
|
-
return { path: options.path ??
|
|
52506
|
+
return { path: options.path ?? join41(data, "ui.db"), projectsDir: join41(data, "projects") };
|
|
51977
52507
|
}
|
|
51978
52508
|
function ensureFrizzSchema(db) {
|
|
51979
52509
|
db.exec(`
|
|
@@ -51992,7 +52522,7 @@ function ensureFrizzSchema(db) {
|
|
|
51992
52522
|
}
|
|
51993
52523
|
function openFrizzDatabase(options = {}) {
|
|
51994
52524
|
const { path, projectsDir } = frizzDatabaseLocation(options);
|
|
51995
|
-
mkdirSync19(
|
|
52525
|
+
mkdirSync19(dirname18(path), { recursive: true });
|
|
51996
52526
|
const db = new sqlite_default(path);
|
|
51997
52527
|
db.pragma("busy_timeout = 5000");
|
|
51998
52528
|
db.pragma("journal_mode = WAL");
|
|
@@ -52004,10 +52534,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
52004
52534
|
);
|
|
52005
52535
|
for (const projectId of retiredLegacyProjectIds(projectsDir)) {
|
|
52006
52536
|
if (alreadyImported.has(projectId)) continue;
|
|
52007
|
-
const dir =
|
|
52537
|
+
const dir = join41(projectsDir, projectId);
|
|
52008
52538
|
try {
|
|
52009
|
-
renameSync11(
|
|
52010
|
-
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId,
|
|
52539
|
+
renameSync11(join41(dir, "ui.db.imported"), join41(dir, "ui.db"));
|
|
52540
|
+
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
52541
|
alreadyImported.add(projectId);
|
|
52012
52542
|
log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
|
|
52013
52543
|
} catch (error) {
|
|
@@ -52017,7 +52547,7 @@ function openFrizzDatabase(options = {}) {
|
|
|
52017
52547
|
}
|
|
52018
52548
|
for (const projectId of legacyProjectIds(projectsDir)) {
|
|
52019
52549
|
if (alreadyImported.has(projectId)) continue;
|
|
52020
|
-
const legacyPath =
|
|
52550
|
+
const legacyPath = join41(projectsDir, projectId, "ui.db");
|
|
52021
52551
|
try {
|
|
52022
52552
|
const rows = importLegacyProject(db, projectId, legacyPath);
|
|
52023
52553
|
imported.push({ projectId, rows });
|
|
@@ -52050,7 +52580,7 @@ function projectIdsWithFile(projectsDir, file) {
|
|
|
52050
52580
|
if (!existsSync14(projectsDir)) return [];
|
|
52051
52581
|
return readdirSync12(projectsDir).filter((name) => {
|
|
52052
52582
|
try {
|
|
52053
|
-
return statSync21(
|
|
52583
|
+
return statSync21(join41(projectsDir, name, file)).isFile();
|
|
52054
52584
|
} catch {
|
|
52055
52585
|
return false;
|
|
52056
52586
|
}
|
|
@@ -52060,7 +52590,7 @@ function legacyProjectIds(projectsDir) {
|
|
|
52060
52590
|
return projectIdsWithFile(projectsDir, "ui.db");
|
|
52061
52591
|
}
|
|
52062
52592
|
function retiredLegacyProjectIds(projectsDir) {
|
|
52063
|
-
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync14(
|
|
52593
|
+
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync14(join41(projectsDir, id, "ui.db")));
|
|
52064
52594
|
}
|
|
52065
52595
|
function tableColumns(db, schema, table) {
|
|
52066
52596
|
return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
|
|
@@ -52213,8 +52743,8 @@ var init_tenant_prime = __esm({
|
|
|
52213
52743
|
|
|
52214
52744
|
// packages/server/src/project-teardown.ts
|
|
52215
52745
|
import { rmSync as rmSync12 } from "node:fs";
|
|
52216
|
-
import { homedir as
|
|
52217
|
-
import { dirname as
|
|
52746
|
+
import { homedir as homedir19 } from "node:os";
|
|
52747
|
+
import { dirname as dirname19, resolve as resolve10 } from "node:path";
|
|
52218
52748
|
async function stopProjectWorkers(ctx) {
|
|
52219
52749
|
let stopped = 0;
|
|
52220
52750
|
for (const row of ctx.storage.allSessions()) {
|
|
@@ -52229,12 +52759,12 @@ async function stopProjectWorkers(ctx) {
|
|
|
52229
52759
|
}
|
|
52230
52760
|
return stopped;
|
|
52231
52761
|
}
|
|
52232
|
-
function deleteProjectState(projectId, home =
|
|
52762
|
+
function deleteProjectState(projectId, home = homedir19()) {
|
|
52233
52763
|
const target = projectStateDir(projectId, home);
|
|
52234
52764
|
if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
|
|
52235
52765
|
throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
|
|
52236
52766
|
}
|
|
52237
|
-
if (
|
|
52767
|
+
if (dirname19(resolve10(target)) !== resolve10(dirname19(projectStateDir("x", home)))) {
|
|
52238
52768
|
throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
|
|
52239
52769
|
}
|
|
52240
52770
|
rmSync12(target, { recursive: true, force: true });
|
|
@@ -52262,11 +52792,11 @@ __export(index_exports, {
|
|
|
52262
52792
|
unknownProjectPage: () => unknownProjectPage
|
|
52263
52793
|
});
|
|
52264
52794
|
import { createServer } from "node:http";
|
|
52265
|
-
import { readFileSync as
|
|
52266
|
-
import { dirname as
|
|
52795
|
+
import { readFileSync as readFileSync29, existsSync as existsSync15 } from "node:fs";
|
|
52796
|
+
import { dirname as dirname20, join as join42, resolve as resolve11, extname as extname4, normalize } from "node:path";
|
|
52267
52797
|
function readServerAddressHolder(path) {
|
|
52268
52798
|
try {
|
|
52269
|
-
const value = JSON.parse(
|
|
52799
|
+
const value = JSON.parse(readFileSync29(path, "utf8"));
|
|
52270
52800
|
if (typeof value?.pid !== "number") return void 0;
|
|
52271
52801
|
return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
|
|
52272
52802
|
} catch {
|
|
@@ -52366,11 +52896,11 @@ function createShutdownSignalHandler(options) {
|
|
|
52366
52896
|
}
|
|
52367
52897
|
function serveStatic(distDir, req, res) {
|
|
52368
52898
|
const rel = normalize((req.url ?? "/").split("?")[0]).replace(/^(\.\.[/\\])+/, "");
|
|
52369
|
-
let file =
|
|
52370
|
-
if (!file.startsWith(distDir)) file =
|
|
52371
|
-
if (!existsSync15(file)) file =
|
|
52899
|
+
let file = join42(distDir, rel === "/" ? "index.html" : rel);
|
|
52900
|
+
if (!file.startsWith(distDir)) file = join42(distDir, "index.html");
|
|
52901
|
+
if (!existsSync15(file)) file = join42(distDir, "index.html");
|
|
52372
52902
|
try {
|
|
52373
|
-
const body =
|
|
52903
|
+
const body = readFileSync29(file);
|
|
52374
52904
|
res.writeHead(200, { "content-type": MIME[extname4(file)] ?? "application/octet-stream" });
|
|
52375
52905
|
res.end(body);
|
|
52376
52906
|
} catch {
|
|
@@ -52472,7 +53002,11 @@ async function startServer(opts = {}) {
|
|
|
52472
53002
|
// The /ws producer is the one the live UI actually renders, so a dead owner has to reach it
|
|
52473
53003
|
// too — projecting only the RPC is the exact half-fix the × already had to correct once.
|
|
52474
53004
|
(slug) => c.tailer.ownerGone?.(slug) ?? false
|
|
52475
|
-
)
|
|
53005
|
+
),
|
|
53006
|
+
// A reader's live watch on the file it shows. Gated as the read is (Markdown through the reader
|
|
53007
|
+
// roots, anything else through the project directory alone) and watched by CANONICAL path, so a
|
|
53008
|
+
// symlinked `.md` and its target are one watch.
|
|
53009
|
+
watchFile: (path, onChange) => watchLocalFile(resolveWatchableLocalFile(path, c.project.dir, openableFileRoots(c.project)), onChange)
|
|
52476
53010
|
});
|
|
52477
53011
|
let githubInit;
|
|
52478
53012
|
let terminal;
|
|
@@ -52616,7 +53150,7 @@ async function startServer(opts = {}) {
|
|
|
52616
53150
|
if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
|
|
52617
53151
|
if (statusPath && statusIdentity) {
|
|
52618
53152
|
try {
|
|
52619
|
-
runtime.removeStatus(serverAddressPathForStateDir(
|
|
53153
|
+
runtime.removeStatus(serverAddressPathForStateDir(dirname20(statusPath)), statusIdentity);
|
|
52620
53154
|
} catch {
|
|
52621
53155
|
}
|
|
52622
53156
|
}
|
|
@@ -52745,7 +53279,7 @@ async function startServer(opts = {}) {
|
|
|
52745
53279
|
}
|
|
52746
53280
|
statusPath = serverLockPathFor(ctx.project);
|
|
52747
53281
|
const webRoot = resolve11(import.meta.dirname, "..", "..", "web");
|
|
52748
|
-
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) :
|
|
53282
|
+
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join42(webRoot, "dist");
|
|
52749
53283
|
startupPhase = "Vite";
|
|
52750
53284
|
if (opts.dev) {
|
|
52751
53285
|
try {
|
|
@@ -52821,7 +53355,7 @@ async function startServer(opts = {}) {
|
|
|
52821
53355
|
if (vite) {
|
|
52822
53356
|
vite.middlewares(req, res, () => {
|
|
52823
53357
|
try {
|
|
52824
|
-
const html =
|
|
53358
|
+
const html = readFileSync29(join42(webRoot, "index.html"), "utf8");
|
|
52825
53359
|
void vite.transformIndexHtml(url, html).then((out) => {
|
|
52826
53360
|
res.writeHead(200, { "content-type": "text/html" });
|
|
52827
53361
|
res.end(out);
|
|
@@ -52975,6 +53509,8 @@ var init_index = __esm({
|
|
|
52975
53509
|
init_app_socket();
|
|
52976
53510
|
init_shutdown();
|
|
52977
53511
|
init_project();
|
|
53512
|
+
init_local_file();
|
|
53513
|
+
init_local_file_watch();
|
|
52978
53514
|
init_project_launch();
|
|
52979
53515
|
init_frizz_paths();
|
|
52980
53516
|
init_project_identity();
|
|
@@ -53026,7 +53562,7 @@ var init_index = __esm({
|
|
|
53026
53562
|
}
|
|
53027
53563
|
};
|
|
53028
53564
|
isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
|
|
53029
|
-
serverLockPathFor = (project) =>
|
|
53565
|
+
serverLockPathFor = (project) => join42(project.stateDir, "server.lock");
|
|
53030
53566
|
MIME = {
|
|
53031
53567
|
".html": "text/html",
|
|
53032
53568
|
".js": "text/javascript",
|
|
@@ -53045,7 +53581,7 @@ init_project_launch();
|
|
|
53045
53581
|
init_shutdown();
|
|
53046
53582
|
init_logging();
|
|
53047
53583
|
import { existsSync as existsSync16 } from "node:fs";
|
|
53048
|
-
import { join as
|
|
53584
|
+
import { join as join43 } from "node:path";
|
|
53049
53585
|
process.on("uncaughtException", (error) => {
|
|
53050
53586
|
log.error("dev-child", `uncaught exception: ${error instanceof Error ? error.stack ?? error.message : error}`);
|
|
53051
53587
|
process.exit(1);
|
|
@@ -53085,7 +53621,7 @@ try {
|
|
|
53085
53621
|
];
|
|
53086
53622
|
if (!existsSync16(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
|
|
53087
53623
|
for (const [name, directory, requiredFile] of required) {
|
|
53088
|
-
if (!directory || !existsSync16(
|
|
53624
|
+
if (!directory || !existsSync16(join43(directory, requiredFile)))
|
|
53089
53625
|
throw new Error(`stable artifact launch is missing verified ${name}`);
|
|
53090
53626
|
}
|
|
53091
53627
|
}
|