frizz 0.2.0 → 0.3.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/README.md +17 -17
- package/dist/claude-agent-broker.js +63 -23
- package/dist/dev-child.js +6877 -4459
- package/dist/frizz.js +654 -529
- package/package.json +6 -2
- package/runtime/cc-worker/.claude-plugin/plugin.json +1 -1
- package/runtime/cc-worker/DECISIONS.md +64 -4
- package/runtime/cc-worker/bin/frizz-mcp.mjs +494 -57
- package/runtime/cc-worker/hooks/agent-dispatch.mjs +1 -1
- package/runtime/cc-worker/hooks/deny-plan.mjs +1 -1
- package/runtime/cc-worker/hooks/hooks.json +0 -7
- package/runtime/cc-worker/hooks/precompact-instructions.mjs +39 -28
- package/runtime/cc-worker/hooks/scratchpad.mjs +142 -169
- package/runtime/cc-worker/hooks/session-seed.mjs +16 -15
- package/runtime/cc-worker/skills/waits/SKILL.md +2 -2
- package/web-dist/apple-touch-icon.png +0 -0
- package/web-dist/assets/{TerminalPane-ROKHp1ib.js → TerminalPane-CTdetDJJ.js} +2 -2
- package/web-dist/assets/{abnfDiagram-VRR7QNED-DcpdhBs3.js → abnfDiagram-VRR7QNED-BDKrjCMs.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-ChUMo004.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-CUAKf0mn.js → architectureDiagram-ZJ3FMSHR-BJebpUUM.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-BPwpJIzx.js → blockDiagram-677ZJIJ3-BNbuk25k.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-1lptuHzZ.js → c4Diagram-LMCZKHZV-lCfyotdU.js} +1 -1
- package/web-dist/assets/channel-pr7r6raB.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-CFR9AKjY.js → chunk-32BRIVSS-DP72SEkr.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-CM9uct7m.js → chunk-52WLFC77-DxO3gN-p.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-DiveJARw.js → chunk-C7G6YPKG-BXFx9Vlr.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-BE1CBw8F.js → chunk-EX3LRPZG-CR3sHpPf.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-DL42uXiO.js → chunk-FWX5IMBZ-CJ8L__oG.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-DPhJWgDw.js → chunk-HOUHSVGY-BCl3JSWr.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-CwYy-6AP.js → chunk-ICXQ74PX-CZxFMci1.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-Ds5I9wxq.js → chunk-MOJQB5TN-Ck3_47dB.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-DHiZJwQD.js → chunk-OGEWGWER-CVXjES-F.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-B-eyQTsF.js → chunk-PUDLZKDR-JjrVgaR7.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-DK7dB3Ti.js → chunk-Q4XR5HBZ-C5YMRAQ0.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-Dn59m74L.js → chunk-V7JOEXUC-DK0mfUHU.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-DK7x36hd.js → chunk-VAUOI2AC-BKBntpid.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-D7-CI3Yl.js → chunk-VR4S4FIN-zgE1dG6U.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-B3l-mLCs.js → chunk-WYO6CB5R-CZhh1IBq.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-Dobxlxie.js → chunk-ZGVPDNZ5-CrNjIEem.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-CvGbPMn_.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-CvGbPMn_.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-Dh7RuEUJ.js → cynefin-VYW2F7L2-Ca_BPfTG.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-ClPi2mZZ.js → cynefinDiagram-TSTJHNR4-CsINQf6g.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-52_WP1QV.js → dagre-VKFMJZFB-CE0DYMFy.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-D_1zVsTL.js → diagram-FQU43EPY-BqTduEYE.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-CdZxuGUy.js → diagram-G47NLZAW-Cho718s6.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-C8pSFu0P.js → diagram-NH7WQ7WH-D0Z8t9UC.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-C5bjZLre.js → diagram-OA4YK3LP-CBacyeDx.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-Bxzhiuzn.js → diagram-WEI45ONY-CozpXa4j.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-g-Z0J2wP.js → ebnfDiagram-CCIWWBDH-ba4NbQQo.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-DVNkgIHp.js → erDiagram-Q63AITRT-B3BciOYa.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-DKmyo-jd.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-DluCBvT4.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-_71pQYEK.js → ganttDiagram-NO4QXBWP-lPmrRit4.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-ChIZiGZS.js → gitGraph-TEB2WS4Q-BjyclOq0.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DCHAFI0l.js → gitGraphDiagram-IHSO6WYX-pzeb4Yrw.js} +1 -1
- package/web-dist/assets/index-BQtjYMpV.css +1 -0
- package/web-dist/assets/index-CLW1Q49U.js +360 -0
- package/web-dist/assets/{info-DKCQHKI2-BW-n_T1j.js → info-DKCQHKI2-Bwycegvf.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-CgDYsKi9.js → infoDiagram-FWYZ7A6U-BCvRGj_5.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-ClzGNt9N.js → ishikawaDiagram-FXEZZL3T-Ofw1RMj3.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-DSCQxkHC.js → journeyDiagram-5HDEW3XC-C5ROwFio.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-CdrdX9N8.js → kanban-definition-HUTT4EX6-YLPLkpeT.js} +1 -1
- package/web-dist/assets/{line-ha38Dc-1.js → line-KtkNqRgI.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-Z4uMcpip.js → mermaid-parser.core-D_FfqBe7.js} +3 -3
- package/web-dist/assets/{mermaid.core-iZRq3hbu.js → mermaid.core-Ffv8anVf.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DmhInJO_.js → mindmap-definition-LN4V7U3C-B2jj4vfm.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-DBPB36Kl.js → packet-7NZHBO7P-BjmWHwra.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-CAH3ljfj.js → pegDiagram-2B236MQR-Dq3iJDyq.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-_h_eX4Ca.js → pie-RZYD4A2V-jBbH1lv9.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-DFBPus8j.js → pieDiagram-ENE6RG2P-C3ETW9lq.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-DMvOCjt8.js → quadrantDiagram-ABIIQ3AL-DItSmme7.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-2EzoPHEZ.js → radar-I7S5WNFK-6ey6crgP.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-BPJnn-hm.js → railroad-3IZDKUUU-Cii-Mn0E.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-U_vb4BrX.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-BIHG7gNU.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-Cpd9r-tB.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-DKq5FagA.js → railroadDiagram-RFXS5EU6-M363ils_.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-BJ5tGazp.js → requirementDiagram-TGXJPOKE-CTs2_V6T.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-XSJjcBhX.js → sankeyDiagram-HTMAVEWB-QTLLcDPD.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CBb8emSe.js → sequenceDiagram-DBY2YBRQ-Bxw9Tr6e.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-DDfRW94V.js → stateDiagram-2N3HPSRC-BecB6roG.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-JXw9T96l.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-DvRYbkZi.js → swimlanes-5IMT3BWC-COiYgS0w.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV--1Wv7FQQ.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-BNUa_DwI.js → timeline-definition-FHXFAJF6-Ci22coeH.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-I6-IW6nJ.js → treeView-QDETBFTQ-4DW35czh.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-CWWmEUYJ.js → treemap-6X3UGDF4-Dq_-Z6-Y.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-DTDrPGLk.js → vennDiagram-L72KCM5P-CHQSIEYq.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-CNsdgXXA.js → wardley-OPB4EBWU-Bwlh7HCY.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-YE0tq3Kh.js → wardleyDiagram-EHGQE667-DctjuPYn.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-D0ofMX8C.js → xychartDiagram-FW5EYKEG-ZoIGosv8.js} +1 -1
- package/web-dist/favicon-16.png +0 -0
- package/web-dist/favicon-32.png +0 -0
- package/web-dist/favicon.svg +17 -61
- package/web-dist/icon-192.png +0 -0
- package/web-dist/icon-512.png +0 -0
- package/web-dist/icon-maskable-512.png +0 -0
- package/web-dist/index.html +19 -8
- package/web-dist/manifest.webmanifest +3 -3
- package/runtime/cc-worker/hooks/scratchpad-stop.mjs +0 -125
- package/runtime/cc-worker/skills/handoff/SKILL.md +0 -209
- package/web-dist/assets/architecture-TIHT7OUA-CAviNivx.js +0 -1
- package/web-dist/assets/channel-CqKDIFQF.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-B_-6iXYY.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-B_-6iXYY.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-MpmeH5YZ.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-D-QgjjhF.js +0 -1
- package/web-dist/assets/index-w4v-GZEc.js +0 -358
- package/web-dist/assets/index-zyi22LPz.css +0 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-YeUoiySk.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-Ddw1SuGG.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-Dd8BOGeW.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-hc41W5Lx.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-BZyGdgSG.js +0 -8
package/dist/frizz.js
CHANGED
|
@@ -12,8 +12,8 @@ var __export = (target2, all) => {
|
|
|
12
12
|
|
|
13
13
|
// packages/server/src/frizz-paths.ts
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
|
-
import { homedir as homedir2 } from "node:os";
|
|
16
|
-
import { join as join2 } from "node:path";
|
|
15
|
+
import { homedir as homedir2, tmpdir } from "node:os";
|
|
16
|
+
import { dirname, join as join2 } from "node:path";
|
|
17
17
|
function legacyFrizzRoot(home = homedir2()) {
|
|
18
18
|
return join2(home, LEGACY_DIR_NAME);
|
|
19
19
|
}
|
|
@@ -387,7 +387,7 @@ import {
|
|
|
387
387
|
writeFileSync as writeFileSync2
|
|
388
388
|
} from "node:fs";
|
|
389
389
|
import { homedir as homedir4 } from "node:os";
|
|
390
|
-
import { dirname, join as join4, resolve } from "node:path";
|
|
390
|
+
import { dirname as dirname2, join as join4, resolve } from "node:path";
|
|
391
391
|
import { setTimeout as delay } from "node:timers/promises";
|
|
392
392
|
function errorCode2(error) {
|
|
393
393
|
return error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
@@ -492,7 +492,7 @@ function quarantineLock(lockPath) {
|
|
|
492
492
|
return true;
|
|
493
493
|
}
|
|
494
494
|
function tryAcquireLock(lockPath, adapter) {
|
|
495
|
-
mkdirSync2(
|
|
495
|
+
mkdirSync2(dirname2(lockPath), { recursive: true, mode: 448 });
|
|
496
496
|
let fd;
|
|
497
497
|
try {
|
|
498
498
|
fd = openSync2(lockPath, "wx", 384);
|
|
@@ -513,7 +513,7 @@ function tryAcquireLock(lockPath, adapter) {
|
|
|
513
513
|
`, "utf8");
|
|
514
514
|
fsyncSync(fd);
|
|
515
515
|
closeSync(fd);
|
|
516
|
-
syncDirectory(
|
|
516
|
+
syncDirectory(dirname2(lockPath));
|
|
517
517
|
} catch (error) {
|
|
518
518
|
try {
|
|
519
519
|
closeSync(fd);
|
|
@@ -663,7 +663,7 @@ function syncGitConfigPath(path) {
|
|
|
663
663
|
} finally {
|
|
664
664
|
closeSync(fd);
|
|
665
665
|
}
|
|
666
|
-
syncDirectory(
|
|
666
|
+
syncDirectory(dirname2(path));
|
|
667
667
|
}
|
|
668
668
|
function syncGitConfig(dir) {
|
|
669
669
|
let raw;
|
|
@@ -744,6 +744,35 @@ function resolveGitProjectIdentity(dir, home = homedir4()) {
|
|
|
744
744
|
release();
|
|
745
745
|
}
|
|
746
746
|
}
|
|
747
|
+
function parseRepoLabel(remoteUrl) {
|
|
748
|
+
const url = remoteUrl.trim();
|
|
749
|
+
if (!url) return null;
|
|
750
|
+
const scp = url.match(/^[^/@]+@[^:/]+:(.+?)(?:\.git)?\/?$/);
|
|
751
|
+
if (scp) return normalizeOwnerRepo(scp[1]);
|
|
752
|
+
const m = url.match(/^[a-z][a-z0-9+.-]*:\/\/[^/]+\/(.+?)(?:\.git)?\/?$/i);
|
|
753
|
+
if (m) return normalizeOwnerRepo(m[1]);
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
function normalizeOwnerRepo(path) {
|
|
757
|
+
const parts = path.split("/").filter(Boolean);
|
|
758
|
+
if (parts.length < 2) return null;
|
|
759
|
+
return parts.slice(-2).join("/");
|
|
760
|
+
}
|
|
761
|
+
function originRemoteUrl(dir) {
|
|
762
|
+
try {
|
|
763
|
+
return execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
764
|
+
cwd: dir,
|
|
765
|
+
encoding: "utf8",
|
|
766
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
767
|
+
}).trim() || null;
|
|
768
|
+
} catch {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function resolveProjectLabel(dir) {
|
|
773
|
+
const url = originRemoteUrl(dir);
|
|
774
|
+
return url ? parseRepoLabel(url) : null;
|
|
775
|
+
}
|
|
747
776
|
var GLOBAL_LOCK_NAME, GLOBAL_LOCK_OWNER, PORT_LOCK_DIR, INCOMPLETE_LOCK_GRACE_MS, LOCK_POLL_MS, PROJECT_ID_RE, SYNC_WAIT2;
|
|
748
777
|
var init_project_identity = __esm({
|
|
749
778
|
"packages/server/src/project-identity.ts"() {
|
|
@@ -773,7 +802,7 @@ import {
|
|
|
773
802
|
statSync as statSync3,
|
|
774
803
|
writeFileSync as writeFileSync3
|
|
775
804
|
} from "node:fs";
|
|
776
|
-
import { basename as basename2, dirname as
|
|
805
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
|
|
777
806
|
function errorCode3(error) {
|
|
778
807
|
return error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
779
808
|
}
|
|
@@ -796,8 +825,8 @@ function syncDirectory2(path) {
|
|
|
796
825
|
}
|
|
797
826
|
}
|
|
798
827
|
function atomicJson(path, value) {
|
|
799
|
-
mkdirSync3(
|
|
800
|
-
const temp = join5(
|
|
828
|
+
mkdirSync3(dirname3(path), { recursive: true, mode: 448 });
|
|
829
|
+
const temp = join5(dirname3(path), `.${basename2(path)}.${process.pid}.${randomUUID3()}.tmp`);
|
|
801
830
|
let fd;
|
|
802
831
|
try {
|
|
803
832
|
fd = openSync3(temp, "wx", 384);
|
|
@@ -807,7 +836,7 @@ function atomicJson(path, value) {
|
|
|
807
836
|
closeSync2(fd);
|
|
808
837
|
fd = void 0;
|
|
809
838
|
renameSync2(temp, path);
|
|
810
|
-
syncDirectory2(
|
|
839
|
+
syncDirectory2(dirname3(path));
|
|
811
840
|
} catch (error) {
|
|
812
841
|
if (fd !== void 0) {
|
|
813
842
|
try {
|
|
@@ -984,7 +1013,7 @@ function writeNewOwner(path, record) {
|
|
|
984
1013
|
fsyncSync2(fd);
|
|
985
1014
|
closeSync2(fd);
|
|
986
1015
|
fd = void 0;
|
|
987
|
-
syncDirectory2(
|
|
1016
|
+
syncDirectory2(dirname3(path));
|
|
988
1017
|
} catch (error) {
|
|
989
1018
|
if (fd !== void 0) {
|
|
990
1019
|
try {
|
|
@@ -1200,6 +1229,7 @@ var OWNER_NAME, MUTATION_GUARD_NAME, OWNER_VERSION, PARTIAL_GRACE_MS, GUARD_TIME
|
|
|
1200
1229
|
var init_project_launch = __esm({
|
|
1201
1230
|
"packages/server/src/project-launch.ts"() {
|
|
1202
1231
|
"use strict";
|
|
1232
|
+
init_project_identity();
|
|
1203
1233
|
init_process_generation();
|
|
1204
1234
|
init_process_generation();
|
|
1205
1235
|
OWNER_NAME = "project-launch.owner";
|
|
@@ -1376,7 +1406,7 @@ var init_local_origin = __esm({
|
|
|
1376
1406
|
|
|
1377
1407
|
// packages/server/src/boot-progress.ts
|
|
1378
1408
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1379
|
-
import { basename as basename3, dirname as
|
|
1409
|
+
import { basename as basename3, dirname as dirname4, join as join6 } from "node:path";
|
|
1380
1410
|
function bootProgressPath(stateDir) {
|
|
1381
1411
|
return join6(stateDir, BOOT_PROGRESS_NAME);
|
|
1382
1412
|
}
|
|
@@ -1397,419 +1427,13 @@ var init_boot_progress = __esm({
|
|
|
1397
1427
|
}
|
|
1398
1428
|
});
|
|
1399
1429
|
|
|
1400
|
-
// packages/server/src/sqlite-quiet.ts
|
|
1401
|
-
function isSqliteExperimentalNotice(warning, type) {
|
|
1402
|
-
const name = typeof warning === "string" ? type : warning?.name;
|
|
1403
|
-
if (name !== "ExperimentalWarning") return false;
|
|
1404
|
-
const text = typeof warning === "string" ? warning : warning?.message ?? "";
|
|
1405
|
-
return text.includes("SQLite");
|
|
1406
|
-
}
|
|
1407
|
-
var emitWarning;
|
|
1408
|
-
var init_sqlite_quiet = __esm({
|
|
1409
|
-
"packages/server/src/sqlite-quiet.ts"() {
|
|
1410
|
-
"use strict";
|
|
1411
|
-
emitWarning = process.emitWarning.bind(process);
|
|
1412
|
-
process.emitWarning = ((warning, ...rest) => {
|
|
1413
|
-
const type = typeof rest[0] === "string" ? rest[0] : void 0;
|
|
1414
|
-
if (isSqliteExperimentalNotice(warning, type)) return;
|
|
1415
|
-
return emitWarning(warning, ...rest);
|
|
1416
|
-
});
|
|
1417
|
-
}
|
|
1418
|
-
});
|
|
1419
|
-
|
|
1420
|
-
// packages/server/src/sqlite.ts
|
|
1421
|
-
import { createRequire } from "node:module";
|
|
1422
|
-
function namedParameters(sql) {
|
|
1423
|
-
const found = /* @__PURE__ */ new Set();
|
|
1424
|
-
for (let index = 0; index < sql.length; index++) {
|
|
1425
|
-
const char = sql[index];
|
|
1426
|
-
if (char === "'" || char === '"' || char === "`") {
|
|
1427
|
-
const quote = char;
|
|
1428
|
-
index++;
|
|
1429
|
-
while (index < sql.length) {
|
|
1430
|
-
if (sql[index] === quote) {
|
|
1431
|
-
if (sql[index + 1] === quote) index++;
|
|
1432
|
-
else break;
|
|
1433
|
-
}
|
|
1434
|
-
index++;
|
|
1435
|
-
}
|
|
1436
|
-
continue;
|
|
1437
|
-
}
|
|
1438
|
-
if (char === "[") {
|
|
1439
|
-
while (index < sql.length && sql[index] !== "]") index++;
|
|
1440
|
-
continue;
|
|
1441
|
-
}
|
|
1442
|
-
if (char === "-" && sql[index + 1] === "-") {
|
|
1443
|
-
while (index < sql.length && sql[index] !== "\n") index++;
|
|
1444
|
-
continue;
|
|
1445
|
-
}
|
|
1446
|
-
if (char === "/" && sql[index + 1] === "*") {
|
|
1447
|
-
index += 2;
|
|
1448
|
-
while (index < sql.length && !(sql[index] === "*" && sql[index + 1] === "/")) index++;
|
|
1449
|
-
index++;
|
|
1450
|
-
continue;
|
|
1451
|
-
}
|
|
1452
|
-
if (char === "@" || char === ":" || char === "$") {
|
|
1453
|
-
let end = index + 1;
|
|
1454
|
-
while (end < sql.length && /[A-Za-z0-9_]/.test(sql[end])) end++;
|
|
1455
|
-
if (end > index + 1) found.add(sql.slice(index + 1, end));
|
|
1456
|
-
index = end - 1;
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
return [...found];
|
|
1460
|
-
}
|
|
1461
|
-
function isNamedBagCandidate(value) {
|
|
1462
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && !ArrayBuffer.isView(value) && !(value instanceof ArrayBuffer) && !(value instanceof Date);
|
|
1463
|
-
}
|
|
1464
|
-
function plainRow(row) {
|
|
1465
|
-
return row === void 0 || row === null ? row : Object.assign({}, row);
|
|
1466
|
-
}
|
|
1467
|
-
function hasBinding(bag, name) {
|
|
1468
|
-
return name in bag || `@${name}` in bag || `:${name}` in bag || `$${name}` in bag;
|
|
1469
|
-
}
|
|
1470
|
-
function transactionControl(sql) {
|
|
1471
|
-
let last = null;
|
|
1472
|
-
for (const statement of sql.split(";")) {
|
|
1473
|
-
const text = statement.trim().toUpperCase();
|
|
1474
|
-
if (/^BEGIN\b/.test(text)) last = "open";
|
|
1475
|
-
else if (/^(COMMIT|END)\b/.test(text)) last = "close";
|
|
1476
|
-
else if (/^ROLLBACK\b/.test(text) && !/^ROLLBACK\s+TO\b/.test(text)) last = "close";
|
|
1477
|
-
}
|
|
1478
|
-
return last;
|
|
1479
|
-
}
|
|
1480
|
-
var DatabaseSync, PreparedStatement, Database;
|
|
1481
|
-
var init_sqlite = __esm({
|
|
1482
|
-
"packages/server/src/sqlite.ts"() {
|
|
1483
|
-
"use strict";
|
|
1484
|
-
init_sqlite_quiet();
|
|
1485
|
-
({ DatabaseSync } = createRequire(import.meta.url)("node:sqlite"));
|
|
1486
|
-
PreparedStatement = class {
|
|
1487
|
-
#statement;
|
|
1488
|
-
#required;
|
|
1489
|
-
#sql;
|
|
1490
|
-
constructor(statement, sql) {
|
|
1491
|
-
this.#statement = statement;
|
|
1492
|
-
this.#sql = sql;
|
|
1493
|
-
this.#required = namedParameters(sql);
|
|
1494
|
-
}
|
|
1495
|
-
/**
|
|
1496
|
-
* Build exactly the bindings this statement asks for, and nothing else.
|
|
1497
|
-
*
|
|
1498
|
-
* Frizz hands whole row objects to statements that bind a subset of their fields, which raw
|
|
1499
|
-
* node:sqlite rejects with "Unknown named parameter". The obvious fix is
|
|
1500
|
-
* `setAllowUnknownNamedParameters(true)` — but that method only exists from Node 23.11, and using it
|
|
1501
|
-
* would have raised Frizz's floor above the release that first shipped `node:sqlite` at all, for no
|
|
1502
|
-
* reason. Selecting the required names from the bag needs no runtime API, and gets the same result.
|
|
1503
|
-
*
|
|
1504
|
-
* A name the caller never supplied throws, rather than binding NULL the way node:sqlite would.
|
|
1505
|
-
* See the file header: silently writing NULL where a value was expected is the one difference that
|
|
1506
|
-
* could quietly corrupt data instead of failing.
|
|
1507
|
-
*/
|
|
1508
|
-
#bind(params) {
|
|
1509
|
-
if (params.length === 1 && this.#required.length > 0 && isNamedBagCandidate(params[0])) {
|
|
1510
|
-
const bag = params[0];
|
|
1511
|
-
const bound = {};
|
|
1512
|
-
for (const name of this.#required) {
|
|
1513
|
-
if (!hasBinding(bag, name)) {
|
|
1514
|
-
throw new RangeError(
|
|
1515
|
-
`Missing named parameter "${name}" for statement: ${this.#sql.trim().slice(0, 120)}`
|
|
1516
|
-
);
|
|
1517
|
-
}
|
|
1518
|
-
const value = name in bag ? bag[name] : bag[`@${name}`] ?? bag[`:${name}`] ?? bag[`$${name}`];
|
|
1519
|
-
bound[name] = value === void 0 ? null : value;
|
|
1520
|
-
}
|
|
1521
|
-
return [bound];
|
|
1522
|
-
}
|
|
1523
|
-
return params.map((value) => value === void 0 ? null : value);
|
|
1524
|
-
}
|
|
1525
|
-
run(...params) {
|
|
1526
|
-
const result = this.#statement.run(...this.#bind(params));
|
|
1527
|
-
return { changes: Number(result.changes), lastInsertRowid: Number(result.lastInsertRowid) };
|
|
1528
|
-
}
|
|
1529
|
-
get(...params) {
|
|
1530
|
-
return plainRow(this.#statement.get(...this.#bind(params)));
|
|
1531
|
-
}
|
|
1532
|
-
all(...params) {
|
|
1533
|
-
return this.#statement.all(...this.#bind(params)).map(plainRow);
|
|
1534
|
-
}
|
|
1535
|
-
};
|
|
1536
|
-
Database = class {
|
|
1537
|
-
#db;
|
|
1538
|
-
/** Transaction nesting depth, so a nested transaction becomes a SAVEPOINT rather than an error. */
|
|
1539
|
-
#depth = 0;
|
|
1540
|
-
/** Fallback transaction state for runtimes without `isTransaction` (added in Node 24). */
|
|
1541
|
-
#openTransaction = false;
|
|
1542
|
-
/** better-sqlite3 exposes the database file here; the scheduler tests reopen a store through it. */
|
|
1543
|
-
name;
|
|
1544
|
-
constructor(path, options2 = {}) {
|
|
1545
|
-
this.name = path;
|
|
1546
|
-
this.#db = new DatabaseSync(path, options2.readonly ? { readOnly: true } : {});
|
|
1547
|
-
}
|
|
1548
|
-
/** True while a transaction is open, however it was opened. */
|
|
1549
|
-
get inTransaction() {
|
|
1550
|
-
const native = this.#db.isTransaction;
|
|
1551
|
-
return typeof native === "boolean" ? native : this.#openTransaction;
|
|
1552
|
-
}
|
|
1553
|
-
/**
|
|
1554
|
-
* Type parameters are `<BindParameters, Row>`, matching better-sqlite3 — 53 call sites already
|
|
1555
|
-
* spell them that way (`prepare<[string], SessionRow>`), and silently swapping the order would
|
|
1556
|
-
* type every one of them as `any` rather than failing loudly.
|
|
1557
|
-
*/
|
|
1558
|
-
prepare(sql) {
|
|
1559
|
-
return new PreparedStatement(this.#db.prepare(sql), sql);
|
|
1560
|
-
}
|
|
1561
|
-
/** Returns the database, matching better-sqlite3, so existing `db.exec(a).exec(b)` chains hold. */
|
|
1562
|
-
exec(sql) {
|
|
1563
|
-
this.#db.exec(sql);
|
|
1564
|
-
const control = transactionControl(sql);
|
|
1565
|
-
if (control === "open") this.#openTransaction = true;
|
|
1566
|
-
else if (control === "close") this.#openTransaction = false;
|
|
1567
|
-
return this;
|
|
1568
|
-
}
|
|
1569
|
-
/**
|
|
1570
|
-
* `PRAGMA` with better-sqlite3's calling convention. Frizz only ever SETS pragmas (`journal_mode`,
|
|
1571
|
-
* `busy_timeout`, `foreign_keys`) and ignores the result, but returning the rows keeps the shape
|
|
1572
|
-
* honest for a read like `pragma("journal_mode")`.
|
|
1573
|
-
*/
|
|
1574
|
-
pragma(source) {
|
|
1575
|
-
const sql = `PRAGMA ${source}`;
|
|
1576
|
-
try {
|
|
1577
|
-
return this.#db.prepare(sql).all();
|
|
1578
|
-
} catch {
|
|
1579
|
-
this.#db.exec(sql);
|
|
1580
|
-
return void 0;
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
/**
|
|
1584
|
-
* better-sqlite3's `transaction()`, including the part that is easy to miss: it is RE-ENTRANT.
|
|
1585
|
-
* Calling one transaction-wrapped function from inside another must not throw "cannot start a
|
|
1586
|
-
* transaction within a transaction" — better-sqlite3 quietly switches to a SAVEPOINT, and Frizz's
|
|
1587
|
-
* storage layer composes these freely, so the nesting has to survive the port.
|
|
1588
|
-
*/
|
|
1589
|
-
transaction(fn) {
|
|
1590
|
-
const runWith = (mode) => (...args2) => {
|
|
1591
|
-
const depth = this.#depth;
|
|
1592
|
-
const savepoint = `frizz_txn_${depth}`;
|
|
1593
|
-
this.#depth = depth + 1;
|
|
1594
|
-
try {
|
|
1595
|
-
this.exec(depth === 0 ? `BEGIN ${mode}` : `SAVEPOINT ${savepoint}`);
|
|
1596
|
-
let result;
|
|
1597
|
-
try {
|
|
1598
|
-
result = fn(...args2);
|
|
1599
|
-
} catch (error) {
|
|
1600
|
-
this.exec(depth === 0 ? "ROLLBACK" : `ROLLBACK TO ${savepoint}`);
|
|
1601
|
-
if (depth > 0) this.exec(`RELEASE ${savepoint}`);
|
|
1602
|
-
throw error;
|
|
1603
|
-
}
|
|
1604
|
-
this.exec(depth === 0 ? "COMMIT" : `RELEASE ${savepoint}`);
|
|
1605
|
-
return result;
|
|
1606
|
-
} finally {
|
|
1607
|
-
this.#depth = depth;
|
|
1608
|
-
}
|
|
1609
|
-
};
|
|
1610
|
-
const wrapper = runWith("IMMEDIATE");
|
|
1611
|
-
wrapper.default = wrapper;
|
|
1612
|
-
wrapper.deferred = runWith("DEFERRED");
|
|
1613
|
-
wrapper.immediate = runWith("IMMEDIATE");
|
|
1614
|
-
wrapper.exclusive = runWith("EXCLUSIVE");
|
|
1615
|
-
return wrapper;
|
|
1616
|
-
}
|
|
1617
|
-
close() {
|
|
1618
|
-
this.#db.close();
|
|
1619
|
-
}
|
|
1620
|
-
};
|
|
1621
|
-
}
|
|
1622
|
-
});
|
|
1623
|
-
|
|
1624
|
-
// packages/server/src/migrate-fray.ts
|
|
1625
|
-
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1626
|
-
import { existsSync as existsSync2, lstatSync, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync4, rmdirSync } from "node:fs";
|
|
1627
|
-
import { homedir as homedir5 } from "node:os";
|
|
1628
|
-
import { join as join7, resolve as resolve2 } from "node:path";
|
|
1629
|
-
function xdg2(env, name) {
|
|
1630
|
-
const value = env[name];
|
|
1631
|
-
return value && (value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) ? value : void 0;
|
|
1632
|
-
}
|
|
1633
|
-
function movesFor(env, platform, home) {
|
|
1634
|
-
const moves = [{ from: join7(home, ".fray"), to: join7(home, ".frizz") }];
|
|
1635
|
-
if (platform === "win32") {
|
|
1636
|
-
const local = env.LOCALAPPDATA || join7(env.USERPROFILE || home, "AppData", "Local");
|
|
1637
|
-
moves.push({ from: join7(local, "Fray"), to: join7(local, "Frizz") });
|
|
1638
|
-
} else if (platform === "darwin") {
|
|
1639
|
-
const support = join7(home, "Library", "Application Support");
|
|
1640
|
-
moves.push({ from: join7(support, "Fray"), to: join7(support, "Frizz") });
|
|
1641
|
-
moves.push({ from: join7(home, "Library", "Caches", "Fray"), to: join7(home, "Library", "Caches", "Frizz") });
|
|
1642
|
-
} else {
|
|
1643
|
-
moves.push({ from: join7(home, ".local", "share", "fray"), to: join7(home, ".local", "share", "frizz") });
|
|
1644
|
-
moves.push({ from: join7(home, ".local", "state", "fray"), to: join7(home, ".local", "state", "frizz") });
|
|
1645
|
-
moves.push({ from: join7(home, ".cache", "fray"), to: join7(home, ".cache", "frizz") });
|
|
1646
|
-
}
|
|
1647
|
-
for (const name of ["XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"]) {
|
|
1648
|
-
const base = xdg2(env, name);
|
|
1649
|
-
if (base) moves.push({ from: join7(base, "fray"), to: join7(base, "frizz") });
|
|
1650
|
-
}
|
|
1651
|
-
return moves;
|
|
1652
|
-
}
|
|
1653
|
-
function migrateFrayGlobalRoots(options2 = {}) {
|
|
1654
|
-
const env = options2.env ?? process.env;
|
|
1655
|
-
const platform = options2.platform ?? process.platform;
|
|
1656
|
-
const home = options2.home ?? homedir5();
|
|
1657
|
-
const exists = options2.exists ?? existsSync2;
|
|
1658
|
-
const rename = options2.rename ?? renameSync4;
|
|
1659
|
-
const merge = options2.merge ?? mergeInto;
|
|
1660
|
-
const moved = [];
|
|
1661
|
-
for (const move of movesFor(env, platform, home)) {
|
|
1662
|
-
if (!exists(move.from)) continue;
|
|
1663
|
-
try {
|
|
1664
|
-
if (exists(move.to)) {
|
|
1665
|
-
if (merge(move.from, move.to)) moved.push(move);
|
|
1666
|
-
continue;
|
|
1667
|
-
}
|
|
1668
|
-
rename(move.from, move.to);
|
|
1669
|
-
moved.push(move);
|
|
1670
|
-
} catch {
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
return moved;
|
|
1674
|
-
}
|
|
1675
|
-
function mergeInto(from, to) {
|
|
1676
|
-
let moved = false;
|
|
1677
|
-
for (const entry of readdirSync2(from, { withFileTypes: true })) {
|
|
1678
|
-
const src = join7(from, entry.name);
|
|
1679
|
-
const dst = join7(to, entry.name);
|
|
1680
|
-
if (!existsSync2(dst)) {
|
|
1681
|
-
renameSync4(src, dst);
|
|
1682
|
-
moved = true;
|
|
1683
|
-
continue;
|
|
1684
|
-
}
|
|
1685
|
-
if (entry.isDirectory() && lstatSync(dst).isDirectory() && mergeInto(src, dst)) moved = true;
|
|
1686
|
-
}
|
|
1687
|
-
try {
|
|
1688
|
-
rmdirSync(from);
|
|
1689
|
-
} catch {
|
|
1690
|
-
}
|
|
1691
|
-
return moved;
|
|
1692
|
-
}
|
|
1693
|
-
function migrateFrayProjectDir(projectDir) {
|
|
1694
|
-
const from = join7(projectDir, ".fray");
|
|
1695
|
-
const to = join7(projectDir, ".frizz");
|
|
1696
|
-
try {
|
|
1697
|
-
if (!lstatSync(from).isDirectory()) return false;
|
|
1698
|
-
if (!existsSync2(to)) {
|
|
1699
|
-
renameSync4(from, to);
|
|
1700
|
-
return true;
|
|
1701
|
-
}
|
|
1702
|
-
return lstatSync(to).isDirectory() ? mergeInto(from, to) : false;
|
|
1703
|
-
} catch {
|
|
1704
|
-
return false;
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
function readId(dir, scope, key) {
|
|
1708
|
-
let output;
|
|
1709
|
-
try {
|
|
1710
|
-
output = execFileSync3("git", ["config", ...scope, "--get-all", key], {
|
|
1711
|
-
cwd: dir,
|
|
1712
|
-
encoding: "utf8",
|
|
1713
|
-
env: { ...process.env, LC_ALL: "C" },
|
|
1714
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1715
|
-
});
|
|
1716
|
-
} catch {
|
|
1717
|
-
return void 0;
|
|
1718
|
-
}
|
|
1719
|
-
const values = output.replace(/\r?\n$/u, "").split(/\r?\n/u).filter((value) => value.length > 0);
|
|
1720
|
-
if (values.length !== 1) return void 0;
|
|
1721
|
-
const id = (values[0] ?? "").trim();
|
|
1722
|
-
return PROJECT_ID_RE2.test(id) ? id : void 0;
|
|
1723
|
-
}
|
|
1724
|
-
function writeId(dir, scope, id) {
|
|
1725
|
-
try {
|
|
1726
|
-
execFileSync3("git", ["config", ...scope, "--replace-all", CURRENT_KEY, id], {
|
|
1727
|
-
cwd: dir,
|
|
1728
|
-
stdio: ["ignore", "ignore", "ignore"]
|
|
1729
|
-
});
|
|
1730
|
-
} catch {
|
|
1731
|
-
return false;
|
|
1732
|
-
}
|
|
1733
|
-
return readId(dir, scope, CURRENT_KEY) === id;
|
|
1734
|
-
}
|
|
1735
|
-
function hasBoard(id, home) {
|
|
1736
|
-
const db = join7(projectStateDir(id, home), "ui.db");
|
|
1737
|
-
if (!existsSync2(db)) return false;
|
|
1738
|
-
let sql;
|
|
1739
|
-
try {
|
|
1740
|
-
sql = new Database(db, { readonly: true });
|
|
1741
|
-
const row = sql.prepare("select count(*) as n from session").get();
|
|
1742
|
-
return Number(row?.n ?? 0) > 0;
|
|
1743
|
-
} catch {
|
|
1744
|
-
return true;
|
|
1745
|
-
} finally {
|
|
1746
|
-
try {
|
|
1747
|
-
sql?.close();
|
|
1748
|
-
} catch {
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
function linkedWorktreeScopes(dir) {
|
|
1753
|
-
const gitPath = (flag) => {
|
|
1754
|
-
try {
|
|
1755
|
-
const raw = execFileSync3("git", ["rev-parse", flag], {
|
|
1756
|
-
cwd: dir,
|
|
1757
|
-
encoding: "utf8",
|
|
1758
|
-
env: { ...process.env, LC_ALL: "C" },
|
|
1759
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1760
|
-
}).trim();
|
|
1761
|
-
return realpathSync2(resolve2(dir, raw));
|
|
1762
|
-
} catch {
|
|
1763
|
-
return void 0;
|
|
1764
|
-
}
|
|
1765
|
-
};
|
|
1766
|
-
const gitDir = gitPath("--git-dir");
|
|
1767
|
-
const commonGitDir = gitPath("--git-common-dir");
|
|
1768
|
-
if (!gitDir || !commonGitDir || gitDir === commonGitDir) return void 0;
|
|
1769
|
-
return {
|
|
1770
|
-
legacy: ["--file", join7(gitDir, "fray.config")],
|
|
1771
|
-
current: ["--file", join7(gitDir, "frizz.config")]
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
function adopt(dir, legacy, current, home) {
|
|
1775
|
-
const legacyId = readId(dir, legacy, LEGACY_KEY);
|
|
1776
|
-
if (!legacyId) return void 0;
|
|
1777
|
-
const currentId = readId(dir, current, CURRENT_KEY);
|
|
1778
|
-
if (currentId === legacyId) return void 0;
|
|
1779
|
-
if (currentId && hasBoard(currentId, home)) return void 0;
|
|
1780
|
-
if (!writeId(dir, current, legacyId)) return void 0;
|
|
1781
|
-
return currentId ? { id: legacyId, replaced: currentId } : { id: legacyId };
|
|
1782
|
-
}
|
|
1783
|
-
function migrateFrayProjectId(projectDir, { home } = {}) {
|
|
1784
|
-
const adopted = [];
|
|
1785
|
-
const repository = adopt(projectDir, ["--local"], ["--local"], home);
|
|
1786
|
-
if (repository) adopted.push(repository);
|
|
1787
|
-
const worktree = linkedWorktreeScopes(projectDir);
|
|
1788
|
-
if (worktree) {
|
|
1789
|
-
const own = adopt(projectDir, worktree.legacy, worktree.current, home);
|
|
1790
|
-
if (own) adopted.push({ ...own, worktree: true });
|
|
1791
|
-
}
|
|
1792
|
-
return adopted;
|
|
1793
|
-
}
|
|
1794
|
-
var PROJECT_ID_RE2, LEGACY_KEY, CURRENT_KEY;
|
|
1795
|
-
var init_migrate_fray = __esm({
|
|
1796
|
-
"packages/server/src/migrate-fray.ts"() {
|
|
1797
|
-
"use strict";
|
|
1798
|
-
init_frizz_paths();
|
|
1799
|
-
init_sqlite();
|
|
1800
|
-
PROJECT_ID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
1801
|
-
LEGACY_KEY = "fray.id";
|
|
1802
|
-
CURRENT_KEY = "frizz.id";
|
|
1803
|
-
}
|
|
1804
|
-
});
|
|
1805
|
-
|
|
1806
1430
|
// packages/server/src/project-root.ts
|
|
1807
1431
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
1808
|
-
import { closeSync as closeSync3, existsSync as
|
|
1809
|
-
import { homedir as
|
|
1810
|
-
import { dirname as
|
|
1432
|
+
import { closeSync as closeSync3, existsSync as existsSync2, fsyncSync as fsyncSync3, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync5, realpathSync as realpathSync2, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1433
|
+
import { homedir as homedir5 } from "node:os";
|
|
1434
|
+
import { dirname as dirname5, join as join7, parse, resolve as resolve2 } from "node:path";
|
|
1811
1435
|
function projectIdPath(root) {
|
|
1812
|
-
return
|
|
1436
|
+
return join7(root, FRIZZ_DIR, ID_FILE);
|
|
1813
1437
|
}
|
|
1814
1438
|
function readProjectIdFile(root) {
|
|
1815
1439
|
let raw;
|
|
@@ -1825,17 +1449,17 @@ function readProjectIdFile(root) {
|
|
|
1825
1449
|
}
|
|
1826
1450
|
}
|
|
1827
1451
|
function writeProjectIdFile(root, id) {
|
|
1828
|
-
const dir =
|
|
1452
|
+
const dir = join7(root, FRIZZ_DIR);
|
|
1829
1453
|
mkdirSync5(dir, { recursive: true });
|
|
1830
|
-
const ignore =
|
|
1831
|
-
if (!
|
|
1454
|
+
const ignore = join7(dir, SELF_IGNORE);
|
|
1455
|
+
if (!existsSync2(ignore)) {
|
|
1832
1456
|
try {
|
|
1833
1457
|
writeFileSync5(ignore, "*\n", { flag: "wx" });
|
|
1834
1458
|
} catch {
|
|
1835
1459
|
}
|
|
1836
1460
|
}
|
|
1837
1461
|
const path = projectIdPath(root);
|
|
1838
|
-
const temp =
|
|
1462
|
+
const temp = join7(dir, `.${ID_FILE}.${process.pid}.${randomUUID4()}.tmp`);
|
|
1839
1463
|
let fd;
|
|
1840
1464
|
try {
|
|
1841
1465
|
fd = openSync4(temp, "wx", 384);
|
|
@@ -1844,7 +1468,7 @@ function writeProjectIdFile(root, id) {
|
|
|
1844
1468
|
fsyncSync3(fd);
|
|
1845
1469
|
closeSync3(fd);
|
|
1846
1470
|
fd = void 0;
|
|
1847
|
-
|
|
1471
|
+
renameSync4(temp, path);
|
|
1848
1472
|
} catch (error) {
|
|
1849
1473
|
if (fd !== void 0) {
|
|
1850
1474
|
try {
|
|
@@ -1863,7 +1487,7 @@ function writeProjectIdFile(root, id) {
|
|
|
1863
1487
|
function projectRootLockName(root) {
|
|
1864
1488
|
return `identity-path-${createHash3("sha256").update(root).digest("hex")}.lock`;
|
|
1865
1489
|
}
|
|
1866
|
-
function ensureProjectIdFile(root, home =
|
|
1490
|
+
function ensureProjectIdFile(root, home = homedir5(), seed) {
|
|
1867
1491
|
const existing = readProjectIdFile(root);
|
|
1868
1492
|
if (existing) return existing;
|
|
1869
1493
|
const release = acquireNamedLaunchLockSync(home, projectRootLockName(root));
|
|
@@ -1883,23 +1507,52 @@ function isNotAGitWorktree(error) {
|
|
|
1883
1507
|
return /not a git repository/iu.test(stderr) || /must be run in a work ?tree/iu.test(stderr);
|
|
1884
1508
|
}
|
|
1885
1509
|
function hasAny(dir, names) {
|
|
1886
|
-
return names.some((name) =>
|
|
1510
|
+
return names.some((name) => existsSync2(join7(dir, name)));
|
|
1887
1511
|
}
|
|
1888
|
-
function
|
|
1512
|
+
function isHomeDirectory(dir, home = homedir5()) {
|
|
1513
|
+
const canonical = (value) => {
|
|
1514
|
+
try {
|
|
1515
|
+
return realpathSync2(resolve2(value));
|
|
1516
|
+
} catch {
|
|
1517
|
+
return resolve2(value);
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
try {
|
|
1521
|
+
return canonical(dir) === canonical(home);
|
|
1522
|
+
} catch {
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
function existingProjectId(dir) {
|
|
1527
|
+
const fromFile = readProjectIdFile(dir);
|
|
1528
|
+
if (fromFile) return fromFile;
|
|
1529
|
+
try {
|
|
1530
|
+
return readGitProjectId(dir);
|
|
1531
|
+
} catch {
|
|
1532
|
+
return void 0;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function isExistingProjectRoot(dir) {
|
|
1536
|
+
return existingProjectId(dir) !== void 0;
|
|
1537
|
+
}
|
|
1538
|
+
function hasProjectMarker(dir) {
|
|
1539
|
+
return hasAny(dir, REPO_MARKERS) || hasAny(dir, PROJECT_MARKERS);
|
|
1540
|
+
}
|
|
1541
|
+
function discoverProjectRoot(cwd = process.cwd(), home = homedir5()) {
|
|
1889
1542
|
let dir;
|
|
1890
1543
|
try {
|
|
1891
|
-
dir =
|
|
1544
|
+
dir = resolve2(cwd);
|
|
1892
1545
|
} catch {
|
|
1893
|
-
return
|
|
1546
|
+
return resolve2(cwd);
|
|
1894
1547
|
}
|
|
1895
|
-
const stop =
|
|
1548
|
+
const stop = resolve2(home);
|
|
1896
1549
|
const filesystemRoot = parse(dir).root;
|
|
1897
|
-
for (let at = dir; ; at =
|
|
1550
|
+
for (let at = dir; ; at = dirname5(at)) {
|
|
1898
1551
|
if (at === stop || at === filesystemRoot) break;
|
|
1899
|
-
if (
|
|
1552
|
+
if (existsSync2(projectIdPath(at))) return at;
|
|
1900
1553
|
if (hasAny(at, REPO_MARKERS)) return at;
|
|
1901
1554
|
if (hasAny(at, PROJECT_MARKERS)) return at;
|
|
1902
|
-
if (
|
|
1555
|
+
if (dirname5(at) === at) break;
|
|
1903
1556
|
}
|
|
1904
1557
|
return dir;
|
|
1905
1558
|
}
|
|
@@ -6035,6 +5688,10 @@ var init_zod = __esm({
|
|
|
6035
5688
|
});
|
|
6036
5689
|
|
|
6037
5690
|
// packages/shared/src/thread-slug.ts
|
|
5691
|
+
function slugify(title) {
|
|
5692
|
+
const s = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5693
|
+
return s.slice(0, THREAD_SLUG_MAX_CHARS).replace(/-+$/g, "") || "thread";
|
|
5694
|
+
}
|
|
6038
5695
|
var THREAD_SLUG_MAX_CHARS, ThreadSlug;
|
|
6039
5696
|
var init_thread_slug = __esm({
|
|
6040
5697
|
"packages/shared/src/thread-slug.ts"() {
|
|
@@ -6595,7 +6252,10 @@ var init_receipt_bus = __esm({
|
|
|
6595
6252
|
});
|
|
6596
6253
|
|
|
6597
6254
|
// packages/shared/src/index.ts
|
|
6598
|
-
|
|
6255
|
+
function fallbackPort(base) {
|
|
6256
|
+
return base + 1e4;
|
|
6257
|
+
}
|
|
6258
|
+
var ATTACHMENT_IMAGE_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, NativeInputRequired, AwaitingHint, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, ThreadWatchKind, RegisterableThreadWatchKind, ThreadWatchTarget, ThreadWatchView, ThreadTimerView, ThreadFence, PlanView, 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, FollowUpInput, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, AddOwnThreadWatchInput, PromoteOwnThreadWatchInput, PromoteOwnThreadWatchResult, DropOwnThreadWatchInput, ListOwnThreadWatchesInput, OwnThreadWatchesResult, AddOwnThreadWatchResult, DropOwnThreadWatchResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, ConfirmAwaitingInput, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, GithubStatus, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketClientMsg, ProjectCard, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT;
|
|
6599
6259
|
var init_src = __esm({
|
|
6600
6260
|
"packages/shared/src/index.ts"() {
|
|
6601
6261
|
"use strict";
|
|
@@ -6863,21 +6523,57 @@ var init_src = __esm({
|
|
|
6863
6523
|
RecurringIntervalSeconds = external_exports.number().int().min(RECURRING_MIN_INTERVAL_SECONDS).max(RECURRING_MAX_INTERVAL_SECONDS);
|
|
6864
6524
|
ThreadRecurringPrompt = external_exports.object({
|
|
6865
6525
|
prompt: external_exports.string(),
|
|
6866
|
-
/** The
|
|
6867
|
-
* fires on `intervalSeconds`
|
|
6526
|
+
/** The three mechanisms, named as the panel labels them. `stopHook` fires at every rest; `heartbeat`
|
|
6527
|
+
* fires on `intervalSeconds`; `postCompaction` fires whenever the harness summarizes the thread's
|
|
6528
|
+
* context away. The latter two both reach the agent mid-turn. */
|
|
6868
6529
|
stopHook: external_exports.boolean(),
|
|
6869
6530
|
heartbeat: external_exports.boolean(),
|
|
6531
|
+
postCompaction: external_exports.boolean(),
|
|
6532
|
+
/** NOT a fourth trigger — a HOLD over all three. While it is on, nothing is sent for as long as the
|
|
6533
|
+
* thread is blocked on the human: an unanswered ```question fence, a native ask, or a permission
|
|
6534
|
+
* prompt. The footer panel seeds it ON for a fresh arming, with the stop hook, because they are one
|
|
6535
|
+
* intent — see scheduler.ts, "WHAT A PENDING QUESTION DOES TO ALL THREE TRIGGERS". The stored column
|
|
6536
|
+
* still defaults OFF, so an existing row and an older caller both keep the behaviour they had. */
|
|
6537
|
+
pauseOnQuestions: external_exports.boolean(),
|
|
6870
6538
|
intervalSeconds: external_exports.number().int().positive().optional(),
|
|
6871
6539
|
armedAt: external_exports.string(),
|
|
6872
|
-
/** Last delivery
|
|
6540
|
+
/** Last delivery per trigger; stamped separately so each reads its own clock. */
|
|
6873
6541
|
lastRestFiredAt: external_exports.string().optional(),
|
|
6874
|
-
lastScheduleFiredAt: external_exports.string().optional()
|
|
6542
|
+
lastScheduleFiredAt: external_exports.string().optional(),
|
|
6543
|
+
lastCompactFiredAt: external_exports.string().optional()
|
|
6875
6544
|
}).strict();
|
|
6876
|
-
ALLDONE_SENTINEL = "ALLDONE";
|
|
6877
|
-
OPT_OUT_NOTE = `If there is genuinely no further work, reply ${ALLDONE_SENTINEL} on its own line to stop these prompts \u2014 but be sure, because it permanently stalls this run.`;
|
|
6878
6545
|
TIMER_PROMPT_MAX = SNOOZE_PROMPT_MAX;
|
|
6879
6546
|
TIMER_MAX_DELAY_SECONDS = 30 * 24 * 60 * 60;
|
|
6880
6547
|
TimerPromptText = external_exports.string().trim().min(1).max(TIMER_PROMPT_MAX);
|
|
6548
|
+
SIGNOFF_NUDGE_MARKER = "**This message is from frizz, not from the human.**";
|
|
6549
|
+
SIGNOFF_NUDGE_MESSAGE = [
|
|
6550
|
+
`${SIGNOFF_NUDGE_MARKER} Nothing about your task has changed, and this is not a request to do more work.`,
|
|
6551
|
+
"",
|
|
6552
|
+
"You rested without a fence, so this thread cannot be triaged. Add one at the END of your next message:",
|
|
6553
|
+
"",
|
|
6554
|
+
"- `` ```question `` \u2014 you need the human. One question per fence, lettered options, one recommended.",
|
|
6555
|
+
"- `` ```done `` \u2014 genuinely FINISHED. A DISMISSAL: the card is filed away and nobody looks again, so",
|
|
6556
|
+
" if anything is still owed, it is not done. Body: 1-3 sentences, then bullets, each opening with a",
|
|
6557
|
+
" **bolded verb phrase**.",
|
|
6558
|
+
"- Blocked on a PR, CI, or your own background shell? Register it with `mcp__frizz__watch` and rest.",
|
|
6559
|
+
"",
|
|
6560
|
+
"**DO NOT REPEAT YOURSELF.** If the message you just wrote already stands on its own, reply with the",
|
|
6561
|
+
"fence ALONE \u2014 the human reads both together, so restating it costs them the second read for nothing.",
|
|
6562
|
+
"",
|
|
6563
|
+
"Only if it does NOT stand alone, fix that first, briefly. It has to be readable cold: the human has",
|
|
6564
|
+
"seen nothing since their own last message \u2014 the Goal, this reminder, a watcher wake all came from",
|
|
6565
|
+
"frizz \u2014 so anything you assumed they had followed, they have not."
|
|
6566
|
+
].join("\n");
|
|
6567
|
+
ThreadWatchKind = external_exports.enum(["shell", "github"]);
|
|
6568
|
+
RegisterableThreadWatchKind = external_exports.enum(["shell"]);
|
|
6569
|
+
ThreadWatchTarget = external_exports.string().trim().min(1).max(200);
|
|
6570
|
+
ThreadWatchView = external_exports.object({
|
|
6571
|
+
id: external_exports.string(),
|
|
6572
|
+
kind: ThreadWatchKind,
|
|
6573
|
+
target: external_exports.string(),
|
|
6574
|
+
state: external_exports.enum(["armed", "fired", "dropped"]),
|
|
6575
|
+
createdAt: external_exports.string()
|
|
6576
|
+
}).strict();
|
|
6881
6577
|
ThreadTimerView = external_exports.object({
|
|
6882
6578
|
id: external_exports.string(),
|
|
6883
6579
|
prompt: external_exports.string(),
|
|
@@ -6982,6 +6678,14 @@ var init_src = __esm({
|
|
|
6982
6678
|
// Live background SHELLS the worker launched (tailer-derived). Same default-[] discipline. Rendered
|
|
6983
6679
|
// in the anchored background-ops strip alongside sub-agents; ids make current rows drillable.
|
|
6984
6680
|
bgShells: external_exports.array(BgShellView).default([]),
|
|
6681
|
+
// The thread's ARMED WATCHERS — registry-derived, not folded from the transcript, which is what makes
|
|
6682
|
+
// them survive the worker saying one more sentence. Same default-[] discipline as the two above.
|
|
6683
|
+
//
|
|
6684
|
+
// These NEVER park the thread: a row with an armed watcher stays a visible queue handoff, and Snooze
|
|
6685
|
+
// remains the only way to hide one (maintainer 2026-08-12, choosing this over auto-Held for every
|
|
6686
|
+
// kind). So this field is for the operator to SEE what a thread is waiting on — it deliberately feeds
|
|
6687
|
+
// no queue-membership rule.
|
|
6688
|
+
watches: external_exports.array(ThreadWatchView).default([]),
|
|
6985
6689
|
// A pending native AskUserQuestion the session is frozen on (tailer-derived). Optional — absent
|
|
6986
6690
|
// when there's no unanswered ask. Feeds needsAction + the read-only question render + "Answer in Terminal".
|
|
6987
6691
|
pendingAsk: PendingAsk.optional(),
|
|
@@ -7068,9 +6772,6 @@ var init_src = __esm({
|
|
|
7068
6772
|
// ISO8601 read/seen telemetry (threadSeen RPC — recorded when the human opens the thread). Kept for
|
|
7069
6773
|
// compatibility and analytics only; viewing never acknowledges or removes a queue handoff.
|
|
7070
6774
|
seenAt: external_exports.string().optional(),
|
|
7071
|
-
// Project-relative scratchpad path (.frizz/threads/<session-id>/scratch.md) once provisioned — the worker's
|
|
7072
|
-
// compaction-proof working memory, rendered as the thread's doc tab.
|
|
7073
|
-
scratchpadPath: external_exports.string().optional(),
|
|
7074
6775
|
// Project-relative plan artifact this thread was dispatched from (.frizz/plans/*.md), if any.
|
|
7075
6776
|
planPath: external_exports.string().optional(),
|
|
7076
6777
|
// Which agent backend runs this thread (Codex-support epic, Phase 3) — drives the subtle per-row
|
|
@@ -7087,13 +6788,13 @@ var init_src = __esm({
|
|
|
7087
6788
|
// Raw durable barrier bit. Unlike permissionPending this remains true for a future/corrupt value,
|
|
7088
6789
|
// so rolling clients fail closed instead of enabling another composer while ownership is unknown.
|
|
7089
6790
|
permissionChangePending: external_exports.boolean().optional(),
|
|
7090
|
-
// The last
|
|
7091
|
-
// perm-policy.mjs), and how many times it has
|
|
7092
|
-
//
|
|
7093
|
-
//
|
|
7094
|
-
//
|
|
6791
|
+
// The last DENIAL the worker's permission POLICY made for this thread (cc-worker/hooks/
|
|
6792
|
+
// perm-policy.mjs), and how many times it has denied. A refusal changes what the worker can do, so
|
|
6793
|
+
// it earns a card. Approvals and deferrals are deliberately absent: a deferral already shows as a
|
|
6794
|
+
// permission prompt / Needs you, and an approval blocks nobody — the quiet line that used to report
|
|
6795
|
+
// one stuck to the bottom of the thread forever, describing a command the reader had long moved past.
|
|
7095
6796
|
permPolicy: external_exports.object({
|
|
7096
|
-
decision: external_exports.
|
|
6797
|
+
decision: external_exports.literal("deny"),
|
|
7097
6798
|
rule: external_exports.string(),
|
|
7098
6799
|
reason: external_exports.string(),
|
|
7099
6800
|
tool: external_exports.string().nullable(),
|
|
@@ -7135,6 +6836,18 @@ var init_src = __esm({
|
|
|
7135
6836
|
projectName: external_exports.string(),
|
|
7136
6837
|
projectLabel: external_exports.string(),
|
|
7137
6838
|
// "owner/repo" from the git origin remote; falls back to projectName
|
|
6839
|
+
// "owner/repo" ONLY when that origin remote is github.com — the link target the rendered-markdown
|
|
6840
|
+
// autolinker turns `#123` and a bare commit hash into. Deliberately NOT projectLabel, which is a
|
|
6841
|
+
// host-agnostic DISPLAY name: a GitLab origin yields an owner/repo there too, and pointing its `#12`
|
|
6842
|
+
// at github.com would be a wrong destination rather than a missing one. Absent means the
|
|
6843
|
+
// augmentation stays off (no remote, another forge, or a pre-restart server).
|
|
6844
|
+
githubRepo: external_exports.string().optional(),
|
|
6845
|
+
// This project's URL slug — the `<slug>` in `/project/<slug>`. The client cannot derive it: a
|
|
6846
|
+
// PREFIXED page reads it off its own path, but the LAUNCHING project is served unprefixed and so has
|
|
6847
|
+
// nothing to read, which left `/` — the all-projects GRID — as the only URL its queue could name.
|
|
6848
|
+
// Optional so a pre-restart server keeps working; absent means "fall back to `/`", i.e. the old
|
|
6849
|
+
// behaviour. Registry-derived, so it is the same slug every other surface links to.
|
|
6850
|
+
projectSlug: external_exports.string().optional(),
|
|
7138
6851
|
// (No `.frizz/ exists` bit here on purpose. Threads are session-first — the ui.db registry IS the
|
|
7139
6852
|
// board — so `.frizz/` presence says nothing about whether this project has one. Its only consumer
|
|
7140
6853
|
// was a shell gate that dead-ended `.frizz`-less repos; the server still probes the directory
|
|
@@ -7223,6 +6936,17 @@ var init_src = __esm({
|
|
|
7223
6936
|
// presents it; resolveClaudeEffort (server/backend/claude-effort.ts) translates it at the spawn edge.
|
|
7224
6937
|
effort: external_exports.enum(["low", "medium", "high", "xhigh", "max", "ultra", "ultracode"]).optional(),
|
|
7225
6938
|
notifications: external_exports.boolean(),
|
|
6939
|
+
/**
|
|
6940
|
+
* The permanent column of project icons. OFF by default, on purpose.
|
|
6941
|
+
*
|
|
6942
|
+
* A rail of every project on the machine is a standing invitation to leave the thread you are in —
|
|
6943
|
+
* "just too tempting" (maintainer 2026-08-06). Frizz's home is one board; the grid is a page you go
|
|
6944
|
+
* to, not furniture you sit beside. Hidden, the way back is a breadcrumb in the status bar, which
|
|
6945
|
+
* costs a click exactly when you meant to switch and nothing when you did not.
|
|
6946
|
+
*
|
|
6947
|
+
* Machine-level, like the font: which chrome you want is a property of the person, not the repo.
|
|
6948
|
+
*/
|
|
6949
|
+
projectRail: external_exports.boolean(),
|
|
7226
6950
|
// UI type family. `mono` (default) is the mono-forward system; `sans` swaps prose/UI chrome to a
|
|
7227
6951
|
// sans stack while code / tool lines / the terminal stay mono. Optional so an old settings blob
|
|
7228
6952
|
// parses; defaultSettings pins "mono".
|
|
@@ -7361,6 +7085,13 @@ var init_src = __esm({
|
|
|
7361
7085
|
prompt: RecurringPromptText.nullable(),
|
|
7362
7086
|
stopHook: external_exports.boolean(),
|
|
7363
7087
|
heartbeat: external_exports.boolean(),
|
|
7088
|
+
// The POST-COMPACTION trigger (scheduler SOURCE 7, added 2026-08-06). Defaulted rather than required
|
|
7089
|
+
// so a client that predates it — an older tab, an older MCP server — keeps writing the row correctly
|
|
7090
|
+
// with the trigger off, which is the honest reading of a caller that has never heard of it.
|
|
7091
|
+
postCompaction: external_exports.boolean().default(false),
|
|
7092
|
+
// The QUESTION HOLD (2026-08-11). Defaulted for the same reason as the trigger above: a caller that
|
|
7093
|
+
// has never heard of it means "don't hold", which is also this option's own default.
|
|
7094
|
+
pauseOnQuestions: external_exports.boolean().default(false),
|
|
7364
7095
|
intervalSeconds: RecurringIntervalSeconds.optional()
|
|
7365
7096
|
}).strict();
|
|
7366
7097
|
SetOwnThreadRecurringPromptInput = external_exports.object({
|
|
@@ -7368,8 +7099,23 @@ var init_src = __esm({
|
|
|
7368
7099
|
prompt: RecurringPromptText.nullable(),
|
|
7369
7100
|
stopHook: external_exports.boolean(),
|
|
7370
7101
|
heartbeat: external_exports.boolean(),
|
|
7102
|
+
// The POST-COMPACTION trigger (scheduler SOURCE 7, added 2026-08-06). Defaulted rather than required
|
|
7103
|
+
// so a client that predates it — an older tab, an older MCP server — keeps writing the row correctly
|
|
7104
|
+
// with the trigger off, which is the honest reading of a caller that has never heard of it.
|
|
7105
|
+
postCompaction: external_exports.boolean().default(false),
|
|
7106
|
+
// The QUESTION HOLD (2026-08-11), defaulted exactly as the trigger above and for the same reason.
|
|
7107
|
+
pauseOnQuestions: external_exports.boolean().default(false),
|
|
7371
7108
|
intervalSeconds: RecurringIntervalSeconds.optional()
|
|
7372
7109
|
}).strict();
|
|
7110
|
+
SetOwnThreadRecurringPromptResult = external_exports.object({
|
|
7111
|
+
replaced: ThreadRecurringPrompt.nullable()
|
|
7112
|
+
}).strict();
|
|
7113
|
+
GetOwnThreadRecurringPromptInput = external_exports.object({
|
|
7114
|
+
slug: ThreadSlug
|
|
7115
|
+
}).strict();
|
|
7116
|
+
OwnThreadRecurringPromptResult = external_exports.object({
|
|
7117
|
+
recurringPrompt: ThreadRecurringPrompt.nullable()
|
|
7118
|
+
}).strict();
|
|
7373
7119
|
SetOwnThreadTimerInput = external_exports.object({
|
|
7374
7120
|
slug: ThreadSlug,
|
|
7375
7121
|
prompt: TimerPromptText,
|
|
@@ -7394,6 +7140,49 @@ var init_src = __esm({
|
|
|
7394
7140
|
cancelled: external_exports.boolean(),
|
|
7395
7141
|
timers: external_exports.array(ThreadTimerView)
|
|
7396
7142
|
}).strict();
|
|
7143
|
+
AddOwnThreadWatchInput = external_exports.object({
|
|
7144
|
+
slug: ThreadSlug,
|
|
7145
|
+
// REGISTERABLE kinds only, which is `shell` and nothing else — deliberately narrower than
|
|
7146
|
+
// `ThreadWatchKind`, whose `github` member is a board-derived VIEW row with no registration behind it.
|
|
7147
|
+
// The MCP tool already refuses a PR here in words ("a pr wait does not belong here — use an ```awaiting
|
|
7148
|
+
// fence"); this is the same refusal at the wire, where it cannot be talked past.
|
|
7149
|
+
kind: RegisterableThreadWatchKind,
|
|
7150
|
+
target: ThreadWatchTarget,
|
|
7151
|
+
/** The worker is BLOCKING on this one inside its tool call, so the scheduler settles it silently
|
|
7152
|
+
* instead of delivering a wake — see the column comment in storage.ts. Defaulted, so a caller that
|
|
7153
|
+
* has never heard of it registers an ordinary background watch. */
|
|
7154
|
+
foreground: external_exports.boolean().default(false)
|
|
7155
|
+
}).strict();
|
|
7156
|
+
PromoteOwnThreadWatchInput = external_exports.object({
|
|
7157
|
+
slug: ThreadSlug,
|
|
7158
|
+
id: external_exports.string().min(1).max(64)
|
|
7159
|
+
}).strict();
|
|
7160
|
+
PromoteOwnThreadWatchResult = external_exports.object({
|
|
7161
|
+
promoted: external_exports.boolean(),
|
|
7162
|
+
watches: external_exports.array(ThreadWatchView)
|
|
7163
|
+
}).strict();
|
|
7164
|
+
DropOwnThreadWatchInput = external_exports.object({
|
|
7165
|
+
slug: ThreadSlug,
|
|
7166
|
+
id: external_exports.string().min(1).max(64)
|
|
7167
|
+
}).strict();
|
|
7168
|
+
ListOwnThreadWatchesInput = external_exports.object({
|
|
7169
|
+
slug: ThreadSlug
|
|
7170
|
+
}).strict();
|
|
7171
|
+
OwnThreadWatchesResult = external_exports.object({
|
|
7172
|
+
watches: external_exports.array(ThreadWatchView)
|
|
7173
|
+
}).strict();
|
|
7174
|
+
AddOwnThreadWatchResult = external_exports.object({
|
|
7175
|
+
id: external_exports.string(),
|
|
7176
|
+
/** True when this exact (kind, target) was ALREADY armed on the thread, so the call registered
|
|
7177
|
+
* nothing new. A worker re-registering after a compaction is the common case, and silently minting a
|
|
7178
|
+
* duplicate would mean two wakes for one event. */
|
|
7179
|
+
alreadyArmed: external_exports.boolean(),
|
|
7180
|
+
watches: external_exports.array(ThreadWatchView)
|
|
7181
|
+
}).strict();
|
|
7182
|
+
DropOwnThreadWatchResult = external_exports.object({
|
|
7183
|
+
dropped: external_exports.boolean(),
|
|
7184
|
+
watches: external_exports.array(ThreadWatchView)
|
|
7185
|
+
}).strict();
|
|
7397
7186
|
SetOwnThreadStopHookInput = external_exports.object({
|
|
7398
7187
|
slug: ThreadSlug,
|
|
7399
7188
|
prompt: RecurringPromptText.nullable(),
|
|
@@ -7434,6 +7223,11 @@ var init_src = __esm({
|
|
|
7434
7223
|
// attempted a write after the flip to danger-full-access was still refused). So the change is real and
|
|
7435
7224
|
// durable, yet it does not reach work already executing. Distinct from "next-resume", which means
|
|
7436
7225
|
// nothing was applied to the live session at all.
|
|
7226
|
+
//
|
|
7227
|
+
// It is ALSO the Claude answer, arrived at from the opposite direction: a permission mode is a LAUNCH
|
|
7228
|
+
// flag there, so frizz retires the idle worker process and the next turn cold-resumes under the new
|
|
7229
|
+
// one (router `setThreadPermission`). Same promise to the operator — stored, and true from the next
|
|
7230
|
+
// turn on — reached by restarting rather than by retuning.
|
|
7437
7231
|
effect: external_exports.enum(["applied", "next-turn", "next-resume"])
|
|
7438
7232
|
});
|
|
7439
7233
|
ThreadProfileOptionsInput = external_exports.object({ slug: ThreadSlug }).strict();
|
|
@@ -7853,7 +7647,25 @@ ${DISPATCH_TASK_BANNER}
|
|
|
7853
7647
|
// there and stores the DISPATCH id here. Additive + optional: absent when the ack was never seen (a
|
|
7854
7648
|
// resumed session whose dispatch scrolled out), and the line then renders as plain text, not a dead link.
|
|
7855
7649
|
peerFrom: external_exports.string().optional(),
|
|
7856
|
-
peerDispatchId: external_exports.string().optional()
|
|
7650
|
+
peerDispatchId: external_exports.string().optional(),
|
|
7651
|
+
// …and the tell that `peerFrom` is ONLY that subagent_type — that the parser could not resolve the
|
|
7652
|
+
// dispatch's own description for this sender. It matters because a profile cell is not a name: every
|
|
7653
|
+
// child dispatched at `frizz:opus-high` reports under the identical string, so a divider reading
|
|
7654
|
+
// «frizz:opus-high» names the MODEL, not the work, and two siblings are indistinguishable
|
|
7655
|
+
// (maintainer 2026-08-06: "I'm also still occasionally seeing things like 'Agent <OPUS:HIGH>
|
|
7656
|
+
// rested'"). The client renders an unnamed sender as "Sub-agent reported" and keeps the cell in the
|
|
7657
|
+
// tooltip, rather than promoting a profile to a title.
|
|
7658
|
+
//
|
|
7659
|
+
// Resolution is genuinely late-arriving, not merely missing: the description comes from the DISPATCH
|
|
7660
|
+
// record, so a report rendered while the window has not yet reached that record is unnamed and gains
|
|
7661
|
+
// its title once it has. Set only on the Claude path — a codex peer names a real task.
|
|
7662
|
+
peerUnnamed: external_exports.literal(true).optional(),
|
|
7663
|
+
// The sender's own RUNTIME agent id (`origin.senderTaskId`) — kept so a LATER pass can finish the job
|
|
7664
|
+
// the fold could not. The paged transcript RPC folds a bounded window, so a report whose dispatch
|
|
7665
|
+
// scrolled above the page start has no description available at fold time; the tailer still holds the
|
|
7666
|
+
// pairing, and `projectTranscriptPeerNames` uses this id to ask it. Never a drawer key on its own —
|
|
7667
|
+
// that is `peerDispatchId`, which the same pass can also supply once this resolves.
|
|
7668
|
+
peerSenderTaskId: external_exports.string().optional()
|
|
7857
7669
|
});
|
|
7858
7670
|
TranscriptPageCursor = external_exports.string().min(1).max(2048).regex(/^[A-Za-z0-9_-]+$/);
|
|
7859
7671
|
TranscriptPage = external_exports.object({
|
|
@@ -7872,12 +7684,240 @@ ${DISPATCH_TASK_BANNER}
|
|
|
7872
7684
|
external_exports.object({ t: external_exports.literal("sub"), topic: external_exports.literal("transcript"), slug: SocketTranscriptSlug }).strict(),
|
|
7873
7685
|
external_exports.object({ t: external_exports.literal("unsub"), topic: external_exports.literal("transcript"), slug: SocketTranscriptSlug }).strict()
|
|
7874
7686
|
]);
|
|
7875
|
-
|
|
7687
|
+
ProjectCard = external_exports.object({
|
|
7688
|
+
id: external_exports.string(),
|
|
7689
|
+
slug: external_exports.string(),
|
|
7690
|
+
name: external_exports.string(),
|
|
7691
|
+
path: external_exports.string(),
|
|
7692
|
+
lastOpenedAt: external_exports.string(),
|
|
7693
|
+
/** The directory is gone — moved or deleted. The card stays so it can be reopened or forgotten. */
|
|
7694
|
+
stale: external_exports.boolean(),
|
|
7695
|
+
/**
|
|
7696
|
+
* When this project's icon was last established, or absent if it never has been.
|
|
7697
|
+
*
|
|
7698
|
+
* Carried purely so the client can hang it off the `/_frizz/project-icon` URL: the icon bytes are
|
|
7699
|
+
* cached hard (a rail of forty squares is forty requests, and none of them should recur), which
|
|
7700
|
+
* means a newly uploaded icon would otherwise stay invisible behind the cached old one. A changed
|
|
7701
|
+
* version is a changed URL, so the swap is immediate without weakening the caching for everyone.
|
|
7702
|
+
*
|
|
7703
|
+
* Deliberately NOT "does this project have an icon". Answering that for a project nobody has
|
|
7704
|
+
* scanned yet would mean scanning it, and this list is one file read on purpose.
|
|
7705
|
+
*/
|
|
7706
|
+
iconVersion: external_exports.string().optional(),
|
|
7707
|
+
/**
|
|
7708
|
+
* Whether this project HAS an icon — and crucially, whether we have even looked.
|
|
7709
|
+
*
|
|
7710
|
+
* Three states, not a boolean, because the two "no icon to draw right now" cases must behave
|
|
7711
|
+
* differently and a boolean collapses them:
|
|
7712
|
+
* · `icon` — one is stored; draw it.
|
|
7713
|
+
* · `none` — scanned, nothing found. Draw the monogram and DO NOT request the icon route,
|
|
7714
|
+
* which is what stops an iconless project flashing its initials and then swapping.
|
|
7715
|
+
* · `unknown` — never scanned. The monogram shows, but the request MUST still go out, because
|
|
7716
|
+
* that request is what triggers the (lazy, cached) scan in the first place.
|
|
7717
|
+
*
|
|
7718
|
+
* Collapsing `unknown` into `none` deadlocks the whole feature: no image element is rendered, so
|
|
7719
|
+
* the icon route is never called, so the scan never runs, so the project stays `unknown` forever.
|
|
7720
|
+
* Measured 2026-08-06 — a rail of 29 projects had scanned exactly ONE, and only because a probe
|
|
7721
|
+
* had fetched that one's URL by hand.
|
|
7722
|
+
*/
|
|
7723
|
+
iconStatus: external_exports.enum(["icon", "none", "unknown"]),
|
|
7724
|
+
/** An operator's uploaded icon, rather than one the scan found. Drives what the menu offers. */
|
|
7725
|
+
iconIsCustom: external_exports.boolean().optional()
|
|
7726
|
+
});
|
|
7727
|
+
PROJECT_ICON_MAX_BASE64_CHARS = 4 * 1024 * 1024;
|
|
7728
|
+
DirectoryPickResult = external_exports.discriminatedUnion("kind", [
|
|
7729
|
+
external_exports.object({ kind: external_exports.literal("picked"), project: ProjectCard }),
|
|
7730
|
+
external_exports.object({ kind: external_exports.literal("cancelled") }),
|
|
7731
|
+
external_exports.object({ kind: external_exports.literal("unavailable"), reason: external_exports.string() })
|
|
7732
|
+
]);
|
|
7733
|
+
ThreadLocation = external_exports.object({ projectSlug: external_exports.string(), projectName: external_exports.string() });
|
|
7734
|
+
FRIZZ_ROUTE_PREFIX = "/_frizz";
|
|
7735
|
+
DEFAULT_PORT = 9393;
|
|
7736
|
+
}
|
|
7737
|
+
});
|
|
7738
|
+
|
|
7739
|
+
// packages/server/src/project-registry.ts
|
|
7740
|
+
import { closeSync as closeSync4, existsSync as existsSync3, fsyncSync as fsyncSync4, mkdirSync as mkdirSync6, openSync as openSync5, readFileSync as readFileSync6, readdirSync as readdirSync2, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7741
|
+
import { homedir as homedir6 } from "node:os";
|
|
7742
|
+
import { basename as basename4, dirname as dirname6, join as join8 } from "node:path";
|
|
7743
|
+
function registryPath(home = homedir6()) {
|
|
7744
|
+
return join8(frizzPaths({ home }).data, "registry.json");
|
|
7745
|
+
}
|
|
7746
|
+
function readRegistry(home = homedir6()) {
|
|
7747
|
+
let raw;
|
|
7748
|
+
try {
|
|
7749
|
+
raw = readFileSync6(registryPath(home), "utf8");
|
|
7750
|
+
} catch {
|
|
7751
|
+
return { ...EMPTY, projects: [] };
|
|
7752
|
+
}
|
|
7753
|
+
try {
|
|
7754
|
+
const parsed = JSON.parse(raw);
|
|
7755
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.projects)) return { ...EMPTY, projects: [] };
|
|
7756
|
+
return {
|
|
7757
|
+
version: 1,
|
|
7758
|
+
projects: parsed.projects.filter(
|
|
7759
|
+
(p) => p && typeof p.id === "string" && typeof p.path === "string" && typeof p.slug === "string"
|
|
7760
|
+
)
|
|
7761
|
+
};
|
|
7762
|
+
} catch {
|
|
7763
|
+
return { ...EMPTY, projects: [] };
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7766
|
+
function writeRegistry(registry, home = homedir6()) {
|
|
7767
|
+
const path = registryPath(home);
|
|
7768
|
+
mkdirSync6(dirname6(path), { recursive: true });
|
|
7769
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
7770
|
+
let fd;
|
|
7771
|
+
try {
|
|
7772
|
+
fd = openSync5(temp, "w", 384);
|
|
7773
|
+
writeFileSync6(fd, `${JSON.stringify(registry, null, 2)}
|
|
7774
|
+
`, "utf8");
|
|
7775
|
+
fsyncSync4(fd);
|
|
7776
|
+
closeSync4(fd);
|
|
7777
|
+
fd = void 0;
|
|
7778
|
+
renameSync5(temp, path);
|
|
7779
|
+
} catch (error) {
|
|
7780
|
+
if (fd !== void 0) {
|
|
7781
|
+
try {
|
|
7782
|
+
closeSync4(fd);
|
|
7783
|
+
} catch {
|
|
7784
|
+
}
|
|
7785
|
+
}
|
|
7786
|
+
try {
|
|
7787
|
+
rmSync6(temp, { force: true });
|
|
7788
|
+
} catch {
|
|
7789
|
+
}
|
|
7790
|
+
throw error;
|
|
7791
|
+
}
|
|
7792
|
+
}
|
|
7793
|
+
function slugifyProject(value) {
|
|
7794
|
+
const s = slugify(value);
|
|
7795
|
+
return s === "thread" ? "project" : s;
|
|
7796
|
+
}
|
|
7797
|
+
function deriveSlug(dir, taken, options2 = {}) {
|
|
7798
|
+
const base = slugifyProject(basename4(dir));
|
|
7799
|
+
const parent = slugifyProject(basename4(dirname6(dir)));
|
|
7800
|
+
const qualified = GENERIC.has(base) && parent ? `${parent}-${base}` : base;
|
|
7801
|
+
const candidates = [
|
|
7802
|
+
qualified,
|
|
7803
|
+
...options2.remoteOwner ? [`${slugifyProject(options2.remoteOwner)}-${base}`] : [],
|
|
7804
|
+
...parent && parent !== qualified ? [`${parent}-${base}`] : []
|
|
7805
|
+
];
|
|
7806
|
+
for (const candidate of candidates) {
|
|
7807
|
+
if (candidate && !taken.has(candidate) && !RESERVED.has(candidate)) return candidate;
|
|
7808
|
+
}
|
|
7809
|
+
const root = candidates[0] || "project";
|
|
7810
|
+
for (let n = 2; ; n++) {
|
|
7811
|
+
const candidate = `${root}-${n}`;
|
|
7812
|
+
if (!taken.has(candidate) && !RESERVED.has(candidate)) return candidate;
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
function canonicalPath(dir) {
|
|
7816
|
+
try {
|
|
7817
|
+
return realpathSync3(dir);
|
|
7818
|
+
} catch {
|
|
7819
|
+
return dir;
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
function registerProject(input, home = homedir6()) {
|
|
7823
|
+
input = { ...input, dir: canonicalPath(input.dir) };
|
|
7824
|
+
const registry = readRegistry(home);
|
|
7825
|
+
const at = (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
7826
|
+
const byId = registry.projects.find((p) => p.id === input.id);
|
|
7827
|
+
if (byId) {
|
|
7828
|
+
if (byId.path === input.dir) {
|
|
7829
|
+
byId.lastOpenedAt = at;
|
|
7830
|
+
writeRegistry(registry, home);
|
|
7831
|
+
return { entry: byId, action: "reopened" };
|
|
7832
|
+
}
|
|
7833
|
+
if (existsSync3(join8(byId.path, ".frizz", ".id"))) return { action: "duplicate" };
|
|
7834
|
+
byId.path = input.dir;
|
|
7835
|
+
byId.lastOpenedAt = at;
|
|
7836
|
+
writeRegistry(registry, home);
|
|
7837
|
+
return { entry: byId, action: "moved" };
|
|
7838
|
+
}
|
|
7839
|
+
const byPath = registry.projects.find((p) => p.path === input.dir);
|
|
7840
|
+
if (byPath) {
|
|
7841
|
+
byPath.id = input.id;
|
|
7842
|
+
byPath.lastOpenedAt = at;
|
|
7843
|
+
writeRegistry(registry, home);
|
|
7844
|
+
return { entry: byPath, action: "rekeyed" };
|
|
7845
|
+
}
|
|
7846
|
+
const taken = new Set(registry.projects.map((p) => p.slug));
|
|
7847
|
+
const entry = {
|
|
7848
|
+
id: input.id,
|
|
7849
|
+
path: input.dir,
|
|
7850
|
+
slug: deriveSlug(input.dir, taken, { remoteOwner: input.remoteOwner }),
|
|
7851
|
+
lastOpenedAt: at
|
|
7852
|
+
};
|
|
7853
|
+
registry.projects.push(entry);
|
|
7854
|
+
writeRegistry(registry, home);
|
|
7855
|
+
return { entry, action: "created" };
|
|
7856
|
+
}
|
|
7857
|
+
function listProjects(home = homedir6()) {
|
|
7858
|
+
const byRecency = (a, b) => a.lastOpenedAt < b.lastOpenedAt ? 1 : a.lastOpenedAt > b.lastOpenedAt ? -1 : 0;
|
|
7859
|
+
return readRegistry(home).projects.map((p) => ({ ...p, stale: !existsSync3(p.path) })).sort((a, b) => {
|
|
7860
|
+
if (a.order === void 0 && b.order === void 0) return byRecency(a, b);
|
|
7861
|
+
if (a.order === void 0) return 1;
|
|
7862
|
+
if (b.order === void 0) return -1;
|
|
7863
|
+
return a.order - b.order;
|
|
7864
|
+
});
|
|
7865
|
+
}
|
|
7866
|
+
function findByPath(path, home = homedir6()) {
|
|
7867
|
+
const canonical = canonicalPath(path);
|
|
7868
|
+
return readRegistry(home).projects.find((p) => p.path === canonical || p.path === path);
|
|
7869
|
+
}
|
|
7870
|
+
var EMPTY, GENERIC, RESERVED;
|
|
7871
|
+
var init_project_registry = __esm({
|
|
7872
|
+
"packages/server/src/project-registry.ts"() {
|
|
7873
|
+
"use strict";
|
|
7874
|
+
init_src();
|
|
7875
|
+
init_frizz_paths();
|
|
7876
|
+
init_project_root();
|
|
7877
|
+
EMPTY = { version: 1, projects: [] };
|
|
7878
|
+
GENERIC = /* @__PURE__ */ new Set([
|
|
7879
|
+
"app",
|
|
7880
|
+
"src",
|
|
7881
|
+
"web",
|
|
7882
|
+
"www",
|
|
7883
|
+
"main",
|
|
7884
|
+
"repo",
|
|
7885
|
+
"code",
|
|
7886
|
+
"server",
|
|
7887
|
+
"client",
|
|
7888
|
+
"packages",
|
|
7889
|
+
"site",
|
|
7890
|
+
"scratch",
|
|
7891
|
+
"tmp",
|
|
7892
|
+
"test"
|
|
7893
|
+
]);
|
|
7894
|
+
RESERVED = /* @__PURE__ */ new Set([
|
|
7895
|
+
"_frizz",
|
|
7896
|
+
"rpc",
|
|
7897
|
+
"events",
|
|
7898
|
+
"ws",
|
|
7899
|
+
"term",
|
|
7900
|
+
"attach",
|
|
7901
|
+
"local-image",
|
|
7902
|
+
"local-visualization",
|
|
7903
|
+
// `control` answers `/control/stop` and `/control/status` — the supervisor channel the board polls
|
|
7904
|
+
// every few seconds. It was missing from this list, so a directory called `control` could have taken
|
|
7905
|
+
// the segment and routed the whole machine's supervisor polling into one project's app, which has no
|
|
7906
|
+
// such route. Only newly minted slugs consult this, so adding it strands nothing already registered.
|
|
7907
|
+
"control",
|
|
7908
|
+
"project-icon",
|
|
7909
|
+
"assets",
|
|
7910
|
+
"favicon",
|
|
7911
|
+
"manifest",
|
|
7912
|
+
"index",
|
|
7913
|
+
"api",
|
|
7914
|
+
"health"
|
|
7915
|
+
]);
|
|
7876
7916
|
}
|
|
7877
7917
|
});
|
|
7878
7918
|
|
|
7879
7919
|
// packages/server/src/local-image.ts
|
|
7880
|
-
import { readFileSync as
|
|
7920
|
+
import { readFileSync as readFileSync8, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
7881
7921
|
import { extname, isAbsolute as isAbsolute2 } from "node:path";
|
|
7882
7922
|
function resolveLocalImage(rawPath) {
|
|
7883
7923
|
if (!rawPath || !isAbsolute2(rawPath)) return { status: 400 };
|
|
@@ -7885,13 +7925,13 @@ function resolveLocalImage(rawPath) {
|
|
|
7885
7925
|
if (!contentType) return { status: 400 };
|
|
7886
7926
|
let real;
|
|
7887
7927
|
try {
|
|
7888
|
-
real =
|
|
7928
|
+
real = realpathSync5(rawPath);
|
|
7889
7929
|
} catch {
|
|
7890
7930
|
return { status: 404 };
|
|
7891
7931
|
}
|
|
7892
7932
|
try {
|
|
7893
7933
|
if (!statSync4(real).isFile()) return { status: 404 };
|
|
7894
|
-
return { status: 200, contentType, body:
|
|
7934
|
+
return { status: 200, contentType, body: readFileSync8(real) };
|
|
7895
7935
|
} catch {
|
|
7896
7936
|
return { status: 404 };
|
|
7897
7937
|
}
|
|
@@ -7911,7 +7951,7 @@ var init_local_image = __esm({
|
|
|
7911
7951
|
});
|
|
7912
7952
|
|
|
7913
7953
|
// src/production.ts
|
|
7914
|
-
import { readFileSync as
|
|
7954
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
7915
7955
|
import { homedir as homedir8 } from "node:os";
|
|
7916
7956
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7917
7957
|
import { join as join11 } from "node:path";
|
|
@@ -8076,12 +8116,12 @@ function manifestIdFor(url) {
|
|
|
8076
8116
|
return url.endsWith("/") ? url : `${url}/`;
|
|
8077
8117
|
}
|
|
8078
8118
|
function execFileP(cmd, args2) {
|
|
8079
|
-
return new Promise((
|
|
8119
|
+
return new Promise((resolve5, reject) => {
|
|
8080
8120
|
execFile(
|
|
8081
8121
|
cmd,
|
|
8082
8122
|
args2,
|
|
8083
8123
|
{ encoding: "utf8", timeout: 1e4 },
|
|
8084
|
-
(err, stdout) => err ? reject(err) :
|
|
8124
|
+
(err, stdout) => err ? reject(err) : resolve5(stdout)
|
|
8085
8125
|
);
|
|
8086
8126
|
});
|
|
8087
8127
|
}
|
|
@@ -8160,9 +8200,9 @@ function openCdpSession(browserPath, dataPath) {
|
|
|
8160
8200
|
cmdPipe.on("error", failAll);
|
|
8161
8201
|
resPipe.on("error", failAll);
|
|
8162
8202
|
child.on("exit", () => failAll(new Error("chrome exited before the CDP handshake completed")));
|
|
8163
|
-
const call = (method, params = {}, timeoutMs = 2e4) => new Promise((
|
|
8203
|
+
const call = (method, params = {}, timeoutMs = 2e4) => new Promise((resolve5, reject) => {
|
|
8164
8204
|
const id = ++seq;
|
|
8165
|
-
pending.set(id, { resolve:
|
|
8205
|
+
pending.set(id, { resolve: resolve5, reject });
|
|
8166
8206
|
cmdPipe.write(JSON.stringify({ id, method, params }) + "\0");
|
|
8167
8207
|
setTimeout(() => {
|
|
8168
8208
|
if (pending.has(id)) {
|
|
@@ -8175,15 +8215,15 @@ function openCdpSession(browserPath, dataPath) {
|
|
|
8175
8215
|
await call("Browser.close").catch(() => {
|
|
8176
8216
|
});
|
|
8177
8217
|
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
8178
|
-
await new Promise((
|
|
8218
|
+
await new Promise((resolve5) => {
|
|
8179
8219
|
const t = setTimeout(() => {
|
|
8180
8220
|
child.kill();
|
|
8181
|
-
|
|
8221
|
+
resolve5();
|
|
8182
8222
|
}, 1e4);
|
|
8183
8223
|
t.unref();
|
|
8184
8224
|
child.once("exit", () => {
|
|
8185
8225
|
clearTimeout(t);
|
|
8186
|
-
|
|
8226
|
+
resolve5();
|
|
8187
8227
|
});
|
|
8188
8228
|
});
|
|
8189
8229
|
};
|
|
@@ -8661,15 +8701,15 @@ init_project_launch();
|
|
|
8661
8701
|
init_local_origin();
|
|
8662
8702
|
init_boot_progress();
|
|
8663
8703
|
init_frizz_paths();
|
|
8664
|
-
init_migrate_fray();
|
|
8665
8704
|
init_project_root();
|
|
8666
8705
|
init_logging();
|
|
8667
8706
|
init_src();
|
|
8668
|
-
|
|
8707
|
+
init_project_registry();
|
|
8708
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
8669
8709
|
import { createServer } from "node:net";
|
|
8670
|
-
import { mkdirSync as
|
|
8710
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, realpathSync as realpathSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8671
8711
|
import { homedir as homedir7, networkInterfaces } from "node:os";
|
|
8672
|
-
import { basename as
|
|
8712
|
+
import { basename as basename5, join as join9, resolve as resolve3 } from "node:path";
|
|
8673
8713
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
8674
8714
|
var PORT_SCAN_COUNT = 100;
|
|
8675
8715
|
var LAUNCH_TIMEOUT_MS = 3e4;
|
|
@@ -8816,6 +8856,9 @@ function resolveBindSelection(options2, env = process.env) {
|
|
|
8816
8856
|
const publicOrigin = publicOriginRaw ? normalizePublicOrigin(publicOriginRaw) : void 0;
|
|
8817
8857
|
return { host, exposed: bindHostIsExposed(host), allowedHosts, ...publicOrigin ? { publicOrigin } : {} };
|
|
8818
8858
|
}
|
|
8859
|
+
function boardAddress(url) {
|
|
8860
|
+
return url.includes("?") || url.endsWith("/") ? url : `${url}/`;
|
|
8861
|
+
}
|
|
8819
8862
|
function networkUrls(port, host, interfaces = networkInterfaces) {
|
|
8820
8863
|
if (!bindHostIsExposed(host)) return [];
|
|
8821
8864
|
const wildcard = host === ALL_INTERFACES_BIND_HOST || host === "::";
|
|
@@ -8837,10 +8880,30 @@ function networkUrls(port, host, interfaces = networkInterfaces) {
|
|
|
8837
8880
|
}
|
|
8838
8881
|
return urls;
|
|
8839
8882
|
}
|
|
8840
|
-
function
|
|
8883
|
+
function resolveLaunchIntent(cwd = process.cwd(), home = homedir7(), env = process.env) {
|
|
8884
|
+
const candidate = realpathSync4(discoverProjectRoot(cwd, home));
|
|
8885
|
+
const known = isExistingProjectRoot(candidate) || findByPath(candidate, home) !== void 0;
|
|
8886
|
+
if ((known || hasProjectMarker(candidate)) && !isHomeDirectory(candidate, home))
|
|
8887
|
+
return { kind: "open", workspace: resolveWorkspace(cwd, home, env) };
|
|
8888
|
+
const host = mostRecentProject(home, env);
|
|
8889
|
+
const reason = isHomeDirectory(candidate, home) ? "home" : "unadopted";
|
|
8890
|
+
if (!host) return { kind: "empty", directory: candidate, reason };
|
|
8891
|
+
return reason === "home" ? { kind: "grid", workspace: host } : { kind: "offer", workspace: host, directory: candidate };
|
|
8892
|
+
}
|
|
8893
|
+
function mostRecentProject(home, env) {
|
|
8894
|
+
for (const entry of listProjects(home)) {
|
|
8895
|
+
if (entry.stale || isHomeDirectory(entry.path, home)) continue;
|
|
8896
|
+
try {
|
|
8897
|
+
return resolveWorkspace(entry.path, home, env);
|
|
8898
|
+
} catch {
|
|
8899
|
+
}
|
|
8900
|
+
}
|
|
8901
|
+
return void 0;
|
|
8902
|
+
}
|
|
8903
|
+
function resolveWorkspace(cwd = process.cwd(), home = homedir7(), env = process.env) {
|
|
8841
8904
|
let gitRoot;
|
|
8842
8905
|
try {
|
|
8843
|
-
gitRoot =
|
|
8906
|
+
gitRoot = execFileSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
8844
8907
|
cwd,
|
|
8845
8908
|
encoding: "utf8",
|
|
8846
8909
|
// stderr is CAPTURED, not ignored: it is the only thing that distinguishes "no worktree here"
|
|
@@ -8851,15 +8914,12 @@ function resolveWorkspace(cwd = process.cwd(), home = homedir7(), env = process.
|
|
|
8851
8914
|
if (!isNotAGitWorktree(error)) throw new Error("unable to resolve Git repository root");
|
|
8852
8915
|
gitRoot = void 0;
|
|
8853
8916
|
}
|
|
8854
|
-
|
|
8855
|
-
const root0 = realpathSync3(gitRoot ?? discoverProjectRoot(cwd, home));
|
|
8856
|
-
if (migrate) migrateFrayProjectId(root0, { home });
|
|
8917
|
+
const root0 = realpathSync4(gitRoot ?? discoverProjectRoot(cwd, home));
|
|
8857
8918
|
const identity = gitRoot ? resolveGitProjectIdentity(root0, home) : void 0;
|
|
8858
8919
|
const root = identity?.root ?? root0;
|
|
8859
|
-
if (migrate) migrateFrayProjectDir(root);
|
|
8860
8920
|
const id = ensureProjectIdFile(root, home, identity?.id);
|
|
8861
8921
|
const stateDir = projectStateDir(id, home);
|
|
8862
|
-
|
|
8922
|
+
mkdirSync7(stateDir, { recursive: true });
|
|
8863
8923
|
const target2 = {
|
|
8864
8924
|
projectId: id,
|
|
8865
8925
|
projectDir: root,
|
|
@@ -8870,7 +8930,7 @@ function resolveWorkspace(cwd = process.cwd(), home = homedir7(), env = process.
|
|
|
8870
8930
|
root,
|
|
8871
8931
|
id,
|
|
8872
8932
|
stateDir,
|
|
8873
|
-
name:
|
|
8933
|
+
name: basename5(root),
|
|
8874
8934
|
identityScope: identity?.scope ?? "repository"
|
|
8875
8935
|
};
|
|
8876
8936
|
}
|
|
@@ -8885,7 +8945,7 @@ function workspaceLaunchTarget(workspace2) {
|
|
|
8885
8945
|
function workspaceFromLaunchTarget(target2, env = process.env) {
|
|
8886
8946
|
let root;
|
|
8887
8947
|
try {
|
|
8888
|
-
root =
|
|
8948
|
+
root = realpathSync4(target2.projectDir);
|
|
8889
8949
|
} catch {
|
|
8890
8950
|
throw new Error("pinned Frizz workspace is no longer available");
|
|
8891
8951
|
}
|
|
@@ -8895,14 +8955,14 @@ function workspaceFromLaunchTarget(target2, env = process.env) {
|
|
|
8895
8955
|
root,
|
|
8896
8956
|
id: target2.projectId,
|
|
8897
8957
|
stateDir: target2.stateDir,
|
|
8898
|
-
name:
|
|
8958
|
+
name: basename5(root),
|
|
8899
8959
|
identityScope: target2.identityScope === "worktree" ? "worktree" : "repository"
|
|
8900
8960
|
};
|
|
8901
8961
|
}
|
|
8902
8962
|
function parseStatusFile(path, authoritative, expected2, adapter = defaultProcessPlatformAdapter) {
|
|
8903
8963
|
try {
|
|
8904
8964
|
const value = JSON.parse(
|
|
8905
|
-
|
|
8965
|
+
readFileSync7(path, "utf8")
|
|
8906
8966
|
);
|
|
8907
8967
|
if (!Number.isInteger(value.pid) || value.pid <= 0 || !Number.isInteger(value.port) || value.port < 1 || value.port > 65535)
|
|
8908
8968
|
return null;
|
|
@@ -8944,19 +9004,21 @@ function liveWorkspaceOwner(stateDir, expected2, adapter = defaultProcessPlatfor
|
|
|
8944
9004
|
function readPreferredPort(stateDir) {
|
|
8945
9005
|
try {
|
|
8946
9006
|
const value = JSON.parse(
|
|
8947
|
-
|
|
9007
|
+
readFileSync7(join9(stateDir, "launcher.json"), "utf8")
|
|
8948
9008
|
);
|
|
8949
9009
|
return Number.isInteger(value.port) && value.port > 0 && value.port <= 65535 ? value.port : void 0;
|
|
8950
9010
|
} catch {
|
|
8951
9011
|
return void 0;
|
|
8952
9012
|
}
|
|
8953
9013
|
}
|
|
8954
|
-
|
|
9014
|
+
var HEALTH_PROBE_TIMEOUT_MS = 1e3;
|
|
9015
|
+
async function probeFrizz(port, expected2, fetcher = fetch, timeoutMs = HEALTH_PROBE_TIMEOUT_MS) {
|
|
8955
9016
|
const controller = new AbortController();
|
|
8956
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
9017
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
8957
9018
|
timeout.unref?.();
|
|
8958
9019
|
try {
|
|
8959
|
-
const
|
|
9020
|
+
const scope = expected2.slug ? `${FRIZZ_ROUTE_PREFIX}/${expected2.slug}` : FRIZZ_ROUTE_PREFIX;
|
|
9021
|
+
const response = await fetcher(`http://127.0.0.1:${port}${scope}/health`, {
|
|
8960
9022
|
signal: controller.signal
|
|
8961
9023
|
});
|
|
8962
9024
|
if (!response.ok) return null;
|
|
@@ -8981,26 +9043,36 @@ function expectedOwnerHealth(target2, owner) {
|
|
|
8981
9043
|
...owner ? { ownerProof: projectLaunchTokenProof(target2, owner.token) } : {}
|
|
8982
9044
|
};
|
|
8983
9045
|
}
|
|
8984
|
-
async function
|
|
8985
|
-
|
|
9046
|
+
async function probeBindPort(port, host = LOOPBACK_BIND_HOST) {
|
|
9047
|
+
return new Promise((resolveBind) => {
|
|
8986
9048
|
const server = createServer();
|
|
8987
9049
|
server.unref();
|
|
8988
|
-
server.once(
|
|
8989
|
-
|
|
9050
|
+
server.once(
|
|
9051
|
+
"error",
|
|
9052
|
+
(error) => resolveBind(error.code ?? "EADDRINUSE")
|
|
9053
|
+
);
|
|
9054
|
+
server.listen(port, host, () => server.close(() => resolveBind(void 0)));
|
|
8990
9055
|
});
|
|
8991
|
-
return bind2(port);
|
|
8992
9056
|
}
|
|
8993
|
-
function
|
|
9057
|
+
async function canBindPort(port, host = LOOPBACK_BIND_HOST) {
|
|
9058
|
+
return await probeBindPort(port, host) === void 0;
|
|
9059
|
+
}
|
|
9060
|
+
function portUnavailableMessage(port, code) {
|
|
9061
|
+
if (code === "EACCES" || code === "EPERM")
|
|
9062
|
+
return `port ${port} is reserved by the system, not held by a process (bind returned ${code}); on Windows list the reservations with: netsh int ipv4 show excludedportrange protocol=tcp`;
|
|
9063
|
+
return `port ${port} is already in use`;
|
|
9064
|
+
}
|
|
9065
|
+
function portCandidates(preferred, base = DEFAULT_PORT) {
|
|
9066
|
+
const fallback = fallbackPort(base);
|
|
8994
9067
|
return [
|
|
9068
|
+
base,
|
|
8995
9069
|
preferred,
|
|
8996
|
-
...Array.from(
|
|
8997
|
-
{ length: PORT_SCAN_COUNT },
|
|
8998
|
-
(_, index) => DEFAULT_PORT + index
|
|
8999
|
-
)
|
|
9070
|
+
...Array.from({ length: PORT_SCAN_COUNT }, (_, index) => fallback + index)
|
|
9000
9071
|
].filter((port) => !!port && port <= 65535);
|
|
9001
9072
|
}
|
|
9002
|
-
function noFreePortMessage() {
|
|
9003
|
-
|
|
9073
|
+
function noFreePortMessage(base = DEFAULT_PORT) {
|
|
9074
|
+
const fallback = fallbackPort(base);
|
|
9075
|
+
return `no free Frizz port: ${base} is taken, and so is every port in ${fallback}-${fallback + PORT_SCAN_COUNT - 1}`;
|
|
9004
9076
|
}
|
|
9005
9077
|
async function allocatePort(explicit, preferred, options2 = {}) {
|
|
9006
9078
|
const host = options2.host ?? LOOPBACK_BIND_HOST;
|
|
@@ -9015,14 +9087,17 @@ async function allocatePort(explicit, preferred, options2 = {}) {
|
|
|
9015
9087
|
};
|
|
9016
9088
|
if (explicit !== void 0) {
|
|
9017
9089
|
const allocated = await claim(explicit);
|
|
9018
|
-
if (!allocated)
|
|
9090
|
+
if (!allocated)
|
|
9091
|
+
throw new Error(
|
|
9092
|
+
portUnavailableMessage(explicit, await probeBindPort(explicit, host))
|
|
9093
|
+
);
|
|
9019
9094
|
return allocated;
|
|
9020
9095
|
}
|
|
9021
|
-
for (const port of portCandidates(preferred)) {
|
|
9096
|
+
for (const port of portCandidates(preferred, options2.base)) {
|
|
9022
9097
|
const allocated = await claim(port);
|
|
9023
9098
|
if (allocated) return allocated;
|
|
9024
9099
|
}
|
|
9025
|
-
throw new Error(noFreePortMessage());
|
|
9100
|
+
throw new Error(noFreePortMessage(options2.base));
|
|
9026
9101
|
}
|
|
9027
9102
|
async function waitForWorkspace(port, expected2, timeoutMs = LAUNCH_TIMEOUT_MS, options2 = {}) {
|
|
9028
9103
|
const started = Date.now();
|
|
@@ -9059,15 +9134,16 @@ init_project_launch();
|
|
|
9059
9134
|
init_project_launch();
|
|
9060
9135
|
import { fork } from "node:child_process";
|
|
9061
9136
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
9062
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
9137
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
9063
9138
|
import { createServer as createNetServer } from "node:net";
|
|
9064
|
-
import { basename as
|
|
9139
|
+
import { basename as basename6, dirname as dirname7, extname as extname2, isAbsolute as isAbsolute3, relative, resolve as resolve4, sep as sep2 } from "node:path";
|
|
9065
9140
|
import { fileURLToPath } from "node:url";
|
|
9066
9141
|
import watcher from "@parcel/watcher";
|
|
9067
9142
|
|
|
9068
9143
|
// packages/server/src/restart-supervisor.ts
|
|
9069
9144
|
init_local_origin();
|
|
9070
9145
|
init_local_image();
|
|
9146
|
+
init_src();
|
|
9071
9147
|
import { request as requestHttp, createServer as createServer2 } from "node:http";
|
|
9072
9148
|
import { connect } from "node:net";
|
|
9073
9149
|
var SUPERVISOR_CONTROL_PREFIX = "/_frizz/control";
|
|
@@ -9097,8 +9173,11 @@ function isControlRequest(req) {
|
|
|
9097
9173
|
return url.pathname === SUPERVISOR_RESTART_PATH || url.pathname === SUPERVISOR_UPDATE_RESTART_PATH || url.pathname === SUPERVISOR_STATUS_PATH;
|
|
9098
9174
|
}
|
|
9099
9175
|
function isLocalImageRequest(req) {
|
|
9100
|
-
|
|
9176
|
+
if (req.method !== "GET" && req.method !== "HEAD") return false;
|
|
9177
|
+
const { pathname } = new URL(req.url ?? "/", "http://frizz.invalid");
|
|
9178
|
+
return LOCAL_IMAGE_PATH.test(pathname);
|
|
9101
9179
|
}
|
|
9180
|
+
var LOCAL_IMAGE_PATH = new RegExp(`^${FRIZZ_ROUTE_PREFIX}(?:/[^/]+)?/local-image$`);
|
|
9102
9181
|
var RestartSupervisorProxy = class {
|
|
9103
9182
|
server = null;
|
|
9104
9183
|
restartInFlight = null;
|
|
@@ -9436,8 +9515,8 @@ function createSupervisorShutdownHandler(options2) {
|
|
|
9436
9515
|
);
|
|
9437
9516
|
};
|
|
9438
9517
|
}
|
|
9439
|
-
var packagesDir =
|
|
9440
|
-
var workspaceDir =
|
|
9518
|
+
var packagesDir = resolve4(import.meta.dirname, "..", "..");
|
|
9519
|
+
var workspaceDir = resolve4(packagesDir, "..");
|
|
9441
9520
|
var INITIAL_BOOT_REASON = "initial boot";
|
|
9442
9521
|
function defaultDevWatchRoots() {
|
|
9443
9522
|
return [workspaceDir];
|
|
@@ -9447,17 +9526,17 @@ function isWithin(path, root) {
|
|
|
9447
9526
|
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute3(rel);
|
|
9448
9527
|
}
|
|
9449
9528
|
function ignoredDevPath(path) {
|
|
9450
|
-
const parts =
|
|
9529
|
+
const parts = resolve4(path).split(sep2);
|
|
9451
9530
|
const name = parts.at(-1) ?? "";
|
|
9452
9531
|
return parts.some((part) => GENERATED_DIRS.has(part) || part === "fixtures" || part.endsWith(".fixtures")) || /\.(?:test|spec)\.[^.]+$/.test(name) || name.includes(".golden.");
|
|
9453
9532
|
}
|
|
9454
9533
|
function classifyDevChange(path, roots = defaultDevWatchRoots()) {
|
|
9455
|
-
const absolute =
|
|
9456
|
-
const root = roots.find((candidate) => isWithin(absolute,
|
|
9534
|
+
const absolute = resolve4(path);
|
|
9535
|
+
const root = roots.find((candidate) => isWithin(absolute, resolve4(candidate)));
|
|
9457
9536
|
if (!root) return null;
|
|
9458
9537
|
if (ignoredDevPath(absolute)) return null;
|
|
9459
|
-
const name =
|
|
9460
|
-
const relToWorkspace = relative(
|
|
9538
|
+
const name = basename6(absolute);
|
|
9539
|
+
const relToWorkspace = relative(resolve4(root), absolute);
|
|
9461
9540
|
const parts = relToWorkspace.split(sep2);
|
|
9462
9541
|
const packageName = parts[0] === "packages" ? parts[1] : void 0;
|
|
9463
9542
|
if (name === "package.json" && parts.length === 3 && packageName && CHILD_PACKAGE_METADATA.has(packageName)) {
|
|
@@ -9478,12 +9557,12 @@ function classifyDevChange(path, roots = defaultDevWatchRoots()) {
|
|
|
9478
9557
|
return null;
|
|
9479
9558
|
}
|
|
9480
9559
|
function devConfigSyntaxError(path) {
|
|
9481
|
-
const name =
|
|
9560
|
+
const name = basename6(path);
|
|
9482
9561
|
const isTsconfig = /^tsconfig(?:\.[^.]+)?\.json$/.test(name);
|
|
9483
9562
|
if (name !== "package.json" && !isTsconfig) return null;
|
|
9484
9563
|
let text;
|
|
9485
9564
|
try {
|
|
9486
|
-
text =
|
|
9565
|
+
text = readFileSync9(path, "utf8");
|
|
9487
9566
|
} catch (err) {
|
|
9488
9567
|
return `${name}: ${err instanceof Error ? err.message : err}`;
|
|
9489
9568
|
}
|
|
@@ -9598,10 +9677,10 @@ var Supervisor = class {
|
|
|
9598
9677
|
if (launchOwner.pid !== callerGeneration.pid || launchOwner.processStart !== callerGeneration.processStart) throw new Error("dev supervisor caller is not the exact project launch owner");
|
|
9599
9678
|
this.port = opts.port;
|
|
9600
9679
|
this.launchTarget = opts.launchTarget;
|
|
9601
|
-
this.cwd =
|
|
9680
|
+
this.cwd = resolve4(opts.cwd ?? opts.launchTarget.projectDir);
|
|
9602
9681
|
if (this.cwd !== opts.launchTarget.projectDir) throw new Error("dev supervisor cwd does not match its owned project");
|
|
9603
9682
|
this.parentEnv = projectLaunchEnvironment(opts.env ?? process.env, opts.launchTarget, opts.launchOwnerToken);
|
|
9604
|
-
this.roots = (opts.watchRoots ?? defaultDevWatchRoots()).map((root) =>
|
|
9683
|
+
this.roots = (opts.watchRoots ?? defaultDevWatchRoots()).map((root) => resolve4(root));
|
|
9605
9684
|
this.watchEnabled = opts.watch !== false;
|
|
9606
9685
|
this.childEnvironment = opts.childEnvironment ?? (() => ({}));
|
|
9607
9686
|
this.debounceMs = opts.debounceMs ?? DEV_RESTART_DEBOUNCE_MS;
|
|
@@ -9611,10 +9690,10 @@ var Supervisor = class {
|
|
|
9611
9690
|
this.childArgs = opts.childArgs ?? [];
|
|
9612
9691
|
this.watchSubscribe = opts.watchSubscribe ?? ((root, callback, options2) => watcher.subscribe(root, callback, options2));
|
|
9613
9692
|
this.reexec = opts.reexec ?? (typeof process.execve === "function" ? (request) => process.execve(request.executable, request.argv, request.env) : void 0);
|
|
9614
|
-
if (opts.stateDir &&
|
|
9693
|
+
if (opts.stateDir && resolve4(opts.stateDir) !== opts.launchTarget.stateDir) {
|
|
9615
9694
|
throw new Error("dev supervisor state directory does not match its owned project");
|
|
9616
9695
|
}
|
|
9617
|
-
this.supervisorLock =
|
|
9696
|
+
this.supervisorLock = resolve4(opts.launchTarget.stateDir, "dev-supervisor.lock");
|
|
9618
9697
|
this.ownerToken = opts.launchOwnerToken;
|
|
9619
9698
|
this.logLine = opts.log ?? ((line) => log.info("supervisor", stripPrefix(line)));
|
|
9620
9699
|
this.errorLine = opts.error ?? ((line) => log.error("supervisor", stripPrefix(line)));
|
|
@@ -9650,7 +9729,7 @@ var Supervisor = class {
|
|
|
9650
9729
|
}
|
|
9651
9730
|
async start() {
|
|
9652
9731
|
if (this.supervisorLock) {
|
|
9653
|
-
|
|
9732
|
+
mkdirSync8(dirname7(this.supervisorLock), { recursive: true });
|
|
9654
9733
|
this.writeStatus("starting", "watcher initializing");
|
|
9655
9734
|
}
|
|
9656
9735
|
try {
|
|
@@ -10145,8 +10224,8 @@ var npmRegistryReleaseAdapter = {
|
|
|
10145
10224
|
// src/preflight.ts
|
|
10146
10225
|
import { spawnSync } from "node:child_process";
|
|
10147
10226
|
import { chmodSync, statSync as statSync5 } from "node:fs";
|
|
10148
|
-
import { createRequire
|
|
10149
|
-
import { dirname as
|
|
10227
|
+
import { createRequire } from "node:module";
|
|
10228
|
+
import { dirname as dirname8, join as join10 } from "node:path";
|
|
10150
10229
|
var SUPPORTED_NODE_LINES = [
|
|
10151
10230
|
{ major: 22, minor: 13 },
|
|
10152
10231
|
{ major: 23, minor: 4 }
|
|
@@ -10195,10 +10274,10 @@ function ensureNativeHelperPermissions(options2 = {}) {
|
|
|
10195
10274
|
if (platform === "win32") return;
|
|
10196
10275
|
const stat = options2.stat ?? ((path) => statSync5(path));
|
|
10197
10276
|
const chmod = options2.chmod ?? ((path, mode) => chmodSync(path, mode));
|
|
10198
|
-
const resolvePty = options2.resolvePty ?? (() =>
|
|
10277
|
+
const resolvePty = options2.resolvePty ?? (() => createRequire(import.meta.url).resolve("node-pty/package.json"));
|
|
10199
10278
|
try {
|
|
10200
10279
|
const helper = join10(
|
|
10201
|
-
|
|
10280
|
+
dirname8(resolvePty()),
|
|
10202
10281
|
"prebuilds",
|
|
10203
10282
|
`${platform}-${options2.arch ?? process.arch}`,
|
|
10204
10283
|
"spawn-helper"
|
|
@@ -10211,10 +10290,13 @@ function ensureNativeHelperPermissions(options2 = {}) {
|
|
|
10211
10290
|
}
|
|
10212
10291
|
|
|
10213
10292
|
// src/production.ts
|
|
10293
|
+
init_src();
|
|
10294
|
+
init_project_registry();
|
|
10295
|
+
init_project_identity();
|
|
10214
10296
|
var PACKAGE_NAME = process.env.FRIZZ_REGISTRY_PACKAGE ?? "frizz";
|
|
10215
10297
|
function resolvePackageVersion() {
|
|
10216
10298
|
try {
|
|
10217
|
-
const manifest = JSON.parse(
|
|
10299
|
+
const manifest = JSON.parse(readFileSync10(join11(import.meta.dirname, "..", "package.json"), "utf8"));
|
|
10218
10300
|
if (typeof manifest.version === "string" && manifest.version.trim()) return manifest.version.trim();
|
|
10219
10301
|
} catch {
|
|
10220
10302
|
}
|
|
@@ -10277,6 +10359,7 @@ var bind = (() => {
|
|
|
10277
10359
|
return fail(error);
|
|
10278
10360
|
}
|
|
10279
10361
|
})();
|
|
10362
|
+
var launchIntent;
|
|
10280
10363
|
var workspace = (() => {
|
|
10281
10364
|
try {
|
|
10282
10365
|
if (!reexec) {
|
|
@@ -10284,11 +10367,17 @@ var workspace = (() => {
|
|
|
10284
10367
|
else assertLaunchPrerequisites();
|
|
10285
10368
|
}
|
|
10286
10369
|
const pinned = projectLaunchTargetFromEnvironment(process.env);
|
|
10287
|
-
if (reexec) {
|
|
10370
|
+
if (reexec || process.env.FRIZZ_PRODUCTION_SUPERVISOR === "1") {
|
|
10288
10371
|
if (!pinned) throw new Error("registry successor is missing its pinned project identity");
|
|
10289
10372
|
return workspaceFromLaunchTarget(pinned);
|
|
10290
10373
|
}
|
|
10291
|
-
|
|
10374
|
+
const intent = resolveLaunchIntent(options.repoPath);
|
|
10375
|
+
if (intent.kind === "empty")
|
|
10376
|
+
throw new Error(
|
|
10377
|
+
intent.reason === "home" ? "frizz cannot open your home directory as a project, and there is no other project to show yet. cd into a repository and run frizz there." : `${intent.directory} is not a Frizz project yet, and there is no other project to show. Run frizz inside a repository, or add this one from the projects page once a board is open.`
|
|
10378
|
+
);
|
|
10379
|
+
launchIntent = intent;
|
|
10380
|
+
return intent.workspace;
|
|
10292
10381
|
} catch (error) {
|
|
10293
10382
|
return fail(error);
|
|
10294
10383
|
}
|
|
@@ -10313,8 +10402,39 @@ async function existingPort() {
|
|
|
10313
10402
|
for (const port of new Set(ports)) if (await probeFrizz(port, expected)) return port;
|
|
10314
10403
|
return void 0;
|
|
10315
10404
|
}
|
|
10316
|
-
|
|
10317
|
-
|
|
10405
|
+
function ownSlug() {
|
|
10406
|
+
try {
|
|
10407
|
+
return registerProject(
|
|
10408
|
+
{ dir: workspace.root, id: target.projectId, remoteOwner: resolveProjectLabel(workspace.root)?.split("/")[0] },
|
|
10409
|
+
homedir8()
|
|
10410
|
+
).entry?.slug;
|
|
10411
|
+
} catch {
|
|
10412
|
+
return void 0;
|
|
10413
|
+
}
|
|
10414
|
+
}
|
|
10415
|
+
var cachedSlugPath;
|
|
10416
|
+
function slugPath() {
|
|
10417
|
+
if (cachedSlugPath === void 0) {
|
|
10418
|
+
if (launchIntent?.kind === "grid") cachedSlugPath = "/";
|
|
10419
|
+
else if (launchIntent?.kind === "offer") cachedSlugPath = `/?add=${encodeURIComponent(launchIntent.directory)}`;
|
|
10420
|
+
else {
|
|
10421
|
+
const slug = ownSlug();
|
|
10422
|
+
cachedSlugPath = slug ? `/project/${slug}` : "";
|
|
10423
|
+
}
|
|
10424
|
+
}
|
|
10425
|
+
return cachedSlugPath;
|
|
10426
|
+
}
|
|
10427
|
+
async function joinRunningFrizz() {
|
|
10428
|
+
const slug = ownSlug();
|
|
10429
|
+
if (!slug) return void 0;
|
|
10430
|
+
for (const port of /* @__PURE__ */ new Set([DEFAULT_PORT, fallbackPort(DEFAULT_PORT)])) {
|
|
10431
|
+
if (await probeFrizz(port, { projectId: target.projectId, projectDir: target.projectDir, slug }))
|
|
10432
|
+
return { port, slug };
|
|
10433
|
+
}
|
|
10434
|
+
return void 0;
|
|
10435
|
+
}
|
|
10436
|
+
async function openOrPrint(port, reused, path = "") {
|
|
10437
|
+
const url = `http://127.0.0.1:${port}${path}`;
|
|
10318
10438
|
logger.info("launcher", `${reused ? "reusing" : "started"} Frizz at ${url}`);
|
|
10319
10439
|
readout?.settle("server", "done", reused ? `already running on port ${port}` : `port ${port}`);
|
|
10320
10440
|
let browser;
|
|
@@ -10354,7 +10474,7 @@ async function openOrPrint(port, reused) {
|
|
|
10354
10474
|
const home = homedir8();
|
|
10355
10475
|
readout.ready(
|
|
10356
10476
|
[
|
|
10357
|
-
{ label: "Local", value:
|
|
10477
|
+
{ label: "Local", value: boardAddress(url), accent: true },
|
|
10358
10478
|
...network.map((address) => ({ label: "Network", value: `${address}/`, accent: true })),
|
|
10359
10479
|
...publicOrigin ? [{ label: "Public", value: `${publicOrigin}/`, accent: true }] : [],
|
|
10360
10480
|
{ label: "Project", value: `${workspace.name} \u2014 ${tildePath(workspace.root, home)}` },
|
|
@@ -10444,7 +10564,7 @@ async function runSupervisor(port, token) {
|
|
|
10444
10564
|
log: ${tildePath(logger.file, homedir8())}` : "";
|
|
10445
10565
|
process.stdout.write(
|
|
10446
10566
|
code === 0 ? `
|
|
10447
|
-
Frizz stopped.
|
|
10567
|
+
Frizz stopped. Running agents keep going \u2014 they are detached daemons.${suffix}
|
|
10448
10568
|
|
|
10449
10569
|
` : `
|
|
10450
10570
|
Frizz stopped with errors (exit ${code}).${suffix}
|
|
@@ -10471,7 +10591,12 @@ try {
|
|
|
10471
10591
|
}
|
|
10472
10592
|
const existing = await existingPort();
|
|
10473
10593
|
if (existing) {
|
|
10474
|
-
await openOrPrint(existing, true);
|
|
10594
|
+
await openOrPrint(existing, true, slugPath());
|
|
10595
|
+
process.exit(0);
|
|
10596
|
+
}
|
|
10597
|
+
const joined = await joinRunningFrizz();
|
|
10598
|
+
if (joined) {
|
|
10599
|
+
await openOrPrint(joined.port, true, slugPath());
|
|
10475
10600
|
process.exit(0);
|
|
10476
10601
|
}
|
|
10477
10602
|
const claim = tryAcquireProjectLaunchOwner(target, "launcher");
|
|
@@ -10496,7 +10621,7 @@ try {
|
|
|
10496
10621
|
]);
|
|
10497
10622
|
portReservation();
|
|
10498
10623
|
portReservation = void 0;
|
|
10499
|
-
await openOrPrint(port, false);
|
|
10624
|
+
await openOrPrint(port, false, slugPath());
|
|
10500
10625
|
await running;
|
|
10501
10626
|
} finally {
|
|
10502
10627
|
release?.();
|