frayui 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-agent-broker.js +19 -3
- package/dist/codex-app-server-daemon.js +1 -1
- package/dist/dev-child.js +766 -329
- package/dist/frayui.js +1291 -193
- package/package.json +2 -2
- package/runtime/cc-worker/DECISIONS.md +86 -0
- package/runtime/cc-worker/hooks/agent-dispatch.mjs +18 -4
- package/runtime/cc-worker/hooks/fence-stop.mjs +290 -0
- package/runtime/cc-worker/hooks/hooks.json +2 -1
- package/runtime/cc-worker/hooks/scratchpad.mjs +10 -9
- package/runtime/cc-worker/hooks/session-seed.mjs +2 -1
- package/runtime/cc-worker/skills/handoff/SKILL.md +6 -0
- package/web-dist/assets/{TerminalPane-DyK0ncN8.js → TerminalPane-hMf6ifuS.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-DtotTgyK.js → abnfDiagram-VRR7QNED-CQj6fwXy.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-JX8_jS4I.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-CVl8mUt0.js → architectureDiagram-ZJ3FMSHR-CoE5ct03.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-VUZEsYBW.js → blockDiagram-677ZJIJ3-CEudy3zd.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-Dlg18lBF.js → c4Diagram-LMCZKHZV-ChW62IOH.js} +1 -1
- package/web-dist/assets/channel-y3u1yiAC.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-DG8gd151.js → chunk-32BRIVSS-Bh-A2le2.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-DKlahFJ2.js → chunk-52WLFC77-7Gia-F6f.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-Bk-BFDsQ.js → chunk-C7G6YPKG-C0MCv0Ym.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-DnXqd-jJ.js → chunk-EX3LRPZG-DPRO7Ss0.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-ujYyBebT.js → chunk-FWX5IMBZ-Cod9MA7c.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-Dk2XhPWQ.js → chunk-HOUHSVGY-D1oHrXQx.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-DoseDoV4.js → chunk-ICXQ74PX-DBLMA0ZN.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-B8dog4zz.js → chunk-MOJQB5TN-jiLvxNvx.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-D4GkHktS.js → chunk-OGEWGWER-w1RtF7G0.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-ChEIayRe.js → chunk-PUDLZKDR-DQlFMKm5.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-BtzMOr97.js → chunk-Q4XR5HBZ-CfD6vdr0.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-DQ7c8QAd.js → chunk-V7JOEXUC-BRB1iO1P.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-6jWzOU_J.js → chunk-VAUOI2AC-CCeJEpxW.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-TB0TNuya.js → chunk-VR4S4FIN-DfPRj9a1.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-CY7fL6F5.js → chunk-WYO6CB5R-BWd84n6L.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-vZO6zPtA.js → chunk-ZGVPDNZ5-Bm-3TA9N.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-BL8mggXA.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-BL8mggXA.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-D4NcAp7V.js → cynefin-VYW2F7L2-BylEqg5y.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-hLO8f53U.js → cynefinDiagram-TSTJHNR4-CRfGS7Ou.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-86z9AWRw.js → dagre-VKFMJZFB-CNeTu13Z.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-DXw3bj8G.js → diagram-FQU43EPY-BYZKetkJ.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-BtH3_eiQ.js → diagram-G47NLZAW-DSOzyl5U.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-BxWq2H6D.js → diagram-NH7WQ7WH-DFZCvfJN.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-BoU3I6Xx.js → diagram-OA4YK3LP-m0qY8Zlg.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-CkPD-vrA.js → diagram-WEI45ONY-Cg7Qz-0T.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-BudQTnvf.js → ebnfDiagram-CCIWWBDH-DGvTjoXK.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-CRFLgkaW.js → erDiagram-Q63AITRT-BFqTK5TB.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-BaCHCepY.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-Cjl6weoT.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-DFmOc45y.js → ganttDiagram-NO4QXBWP-KbR9IE0O.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-CMW6meDa.js → gitGraph-TEB2WS4Q-hCbzZO_l.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-CHelbJfg.js → gitGraphDiagram-IHSO6WYX-CNPRTwQG.js} +1 -1
- package/web-dist/assets/index-BUY5ijyI.css +1 -0
- package/web-dist/assets/{index-C2Tf3Ihs.js → index-Bj6YWd3g.js} +68 -68
- package/web-dist/assets/{info-DKCQHKI2-DZT4W61r.js → info-DKCQHKI2-Zd_TXMfr.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-Dj0tLhVO.js → infoDiagram-FWYZ7A6U-DQ9zmVhJ.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-D825ES0c.js → ishikawaDiagram-FXEZZL3T-BFyJ5E_L.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-D53Igq5c.js → journeyDiagram-5HDEW3XC-BgjbY7_T.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-CJ-F0vAo.js → kanban-definition-HUTT4EX6-mpUEl8xx.js} +1 -1
- package/web-dist/assets/{line-CuiC27jO.js → line-CM_FUYGm.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-rG6qAIn0.js → mermaid-parser.core-CTVz2V70.js} +3 -3
- package/web-dist/assets/{mermaid.core-DzNUOuDq.js → mermaid.core-BCLNmVvR.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DHfhQue9.js → mindmap-definition-LN4V7U3C-CgJU-6O7.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-BiUlKhVG.js → packet-7NZHBO7P-rkLFb4ZX.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-MQdiKZx3.js → pegDiagram-2B236MQR-Sy2oYuu1.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-dNHUvvE3.js → pie-RZYD4A2V-0yTxNtqZ.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-BwFxe1x1.js → pieDiagram-ENE6RG2P-CQ7YFWhH.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-9dic-8vv.js → quadrantDiagram-ABIIQ3AL-ClCTxrOj.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-CK3dl20X.js → radar-I7S5WNFK-JUOW5mdN.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-BuB2LkFj.js → railroad-3IZDKUUU-DJ2IS1Jg.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-CU4HK7Ek.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-CarBAd7R.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-ByEhVLyD.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-CuuspS_3.js → railroadDiagram-RFXS5EU6-BNsVGeA3.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-DHAILLdh.js → requirementDiagram-TGXJPOKE-BHa3IX0k.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-CztQ2t1b.js → sankeyDiagram-HTMAVEWB-CSbsLbtw.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-jk56eo0l.js → sequenceDiagram-DBY2YBRQ-BgVWj1nN.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-uwqBWnrI.js → stateDiagram-2N3HPSRC-BKHYNxA3.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-C-k0bbcG.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-BzuJ77j6.js → swimlanes-5IMT3BWC-BluloqBJ.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-DVjLYZbp.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-CVDNkzi9.js → timeline-definition-FHXFAJF6-Bj1WKNgB.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-CdkPxaDl.js → treeView-QDETBFTQ-Dj5T24Qr.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-lnksCHdn.js → treemap-6X3UGDF4-BPqewrXp.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-B533fRM0.js → vennDiagram-L72KCM5P-BhggGhQv.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-DwKf-2YJ.js → wardley-OPB4EBWU-Di9GWKYt.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-BDDN72B1.js → wardleyDiagram-EHGQE667-D-mtWLKf.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-CkvHmNVt.js → xychartDiagram-FW5EYKEG-CU0us6D_.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/architecture-TIHT7OUA-B23bEjC4.js +0 -1
- package/web-dist/assets/channel-BMFwXMMq.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-DiWatUgG.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-DiWatUgG.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-C7ZRnE7-.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-DKS-kFcF.js +0 -1
- package/web-dist/assets/index-CeAC3wUW.css +0 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-C1-MJAeY.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-crQCD9rJ.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-Ba-_QGKi.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-DOPbAEV3.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-BRh1ca21.js +0 -8
package/dist/dev-child.js
CHANGED
|
@@ -1722,6 +1722,186 @@ var init_shutdown = __esm({
|
|
|
1722
1722
|
}
|
|
1723
1723
|
});
|
|
1724
1724
|
|
|
1725
|
+
// packages/server/src/logging.ts
|
|
1726
|
+
import {
|
|
1727
|
+
appendFileSync,
|
|
1728
|
+
mkdirSync as mkdirSync5,
|
|
1729
|
+
openSync as openSync4,
|
|
1730
|
+
readdirSync,
|
|
1731
|
+
rmSync as rmSync4,
|
|
1732
|
+
statSync as statSync4,
|
|
1733
|
+
symlinkSync,
|
|
1734
|
+
writeFileSync as writeFileSync4,
|
|
1735
|
+
writeSync
|
|
1736
|
+
} from "node:fs";
|
|
1737
|
+
import { homedir as homedir3 } from "node:os";
|
|
1738
|
+
import { join as join5 } from "node:path";
|
|
1739
|
+
function defaultLogRoot(stateDir, home = homedir3()) {
|
|
1740
|
+
return stateDir ? join5(stateDir, "logs") : join5(home, ".fray", "logs");
|
|
1741
|
+
}
|
|
1742
|
+
function runStamp(at) {
|
|
1743
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1744
|
+
return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}T${pad(at.getHours())}-${pad(at.getMinutes())}-${pad(at.getSeconds())}`;
|
|
1745
|
+
}
|
|
1746
|
+
function runLogPath(stateDir, at = /* @__PURE__ */ new Date(), pid = process.pid, home = homedir3(), env = process.env) {
|
|
1747
|
+
const name = `fray-${runStamp(at)}-${pid}.log`;
|
|
1748
|
+
const override = env[LOG_PATH_ENV]?.trim();
|
|
1749
|
+
if (override) return override.endsWith(".log") ? override : join5(override, name);
|
|
1750
|
+
return join5(defaultLogRoot(stateDir, home), name);
|
|
1751
|
+
}
|
|
1752
|
+
function latestLogPath(dir) {
|
|
1753
|
+
return join5(dir, "latest.log");
|
|
1754
|
+
}
|
|
1755
|
+
function linkLatest(dir, target) {
|
|
1756
|
+
const link = latestLogPath(dir);
|
|
1757
|
+
try {
|
|
1758
|
+
rmSync4(link, { force: true });
|
|
1759
|
+
symlinkSync(target, link);
|
|
1760
|
+
} catch {
|
|
1761
|
+
try {
|
|
1762
|
+
writeFileSync4(link, `${target}
|
|
1763
|
+
`, { mode: 384 });
|
|
1764
|
+
} catch {
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Date.now()) {
|
|
1769
|
+
let entries;
|
|
1770
|
+
try {
|
|
1771
|
+
entries = readdirSync(dir).filter((name) => name.endsWith(".log") && name !== "latest.log");
|
|
1772
|
+
} catch {
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
const dated = entries.map((name) => {
|
|
1776
|
+
try {
|
|
1777
|
+
return { name, at: statSync4(join5(dir, name)).mtimeMs };
|
|
1778
|
+
} catch {
|
|
1779
|
+
return { name, at: 0 };
|
|
1780
|
+
}
|
|
1781
|
+
}).sort((a, b) => b.at - a.at);
|
|
1782
|
+
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
1783
|
+
const stale = dated.filter((entry, index) => index >= keep || entry.at < cutoff);
|
|
1784
|
+
for (const entry of stale) {
|
|
1785
|
+
try {
|
|
1786
|
+
rmSync4(join5(dir, entry.name), { force: true });
|
|
1787
|
+
} catch {
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
function formatDiskLine(record) {
|
|
1792
|
+
const at = new Date(record.at);
|
|
1793
|
+
const pad = (value, width = 2) => String(value).padStart(width, "0");
|
|
1794
|
+
const clock = `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}.${pad(at.getMilliseconds(), 3)}`;
|
|
1795
|
+
return `${clock} ${record.level.toUpperCase().padEnd(5)} ${record.scope.padEnd(12)} ${record.message}
|
|
1796
|
+
`;
|
|
1797
|
+
}
|
|
1798
|
+
function openLogFile(path) {
|
|
1799
|
+
try {
|
|
1800
|
+
mkdirSync5(join5(path, ".."), { recursive: true, mode: 448 });
|
|
1801
|
+
return openSync4(path, "a", 384);
|
|
1802
|
+
} catch {
|
|
1803
|
+
return null;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
function createLogger(options = {}) {
|
|
1807
|
+
const path = options.file === null ? null : options.file ?? runLogPath(options.stateDir);
|
|
1808
|
+
const level = options.level ?? "debug";
|
|
1809
|
+
const now = options.now ?? Date.now;
|
|
1810
|
+
const threshold = LEVEL_ORDER[level];
|
|
1811
|
+
const maxBytes = options.maxBytes ?? MAX_LOG_BYTES;
|
|
1812
|
+
let fd = path === null ? null : openLogFile(path);
|
|
1813
|
+
if (fd !== null && path !== null && options.owner !== false) {
|
|
1814
|
+
const dir = join5(path, "..");
|
|
1815
|
+
pruneRunLogs(dir);
|
|
1816
|
+
linkLatest(dir, path);
|
|
1817
|
+
}
|
|
1818
|
+
let written = 0;
|
|
1819
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1820
|
+
const emit2 = (recordLevel, scope, message) => {
|
|
1821
|
+
if (LEVEL_ORDER[recordLevel] < threshold) return;
|
|
1822
|
+
const record = { at: now(), level: recordLevel, scope, message };
|
|
1823
|
+
if (fd !== null) {
|
|
1824
|
+
try {
|
|
1825
|
+
const line = formatDiskLine(record);
|
|
1826
|
+
if (written + line.length > maxBytes) {
|
|
1827
|
+
writeSync(fd, `--- log truncated at ${maxBytes} bytes ---
|
|
1828
|
+
`);
|
|
1829
|
+
fd = null;
|
|
1830
|
+
} else {
|
|
1831
|
+
writeSync(fd, line);
|
|
1832
|
+
written += line.length;
|
|
1833
|
+
}
|
|
1834
|
+
} catch {
|
|
1835
|
+
fd = null;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
for (const listener of listeners) {
|
|
1839
|
+
try {
|
|
1840
|
+
listener(record);
|
|
1841
|
+
} catch {
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
return {
|
|
1846
|
+
debug: (scope, message) => emit2("debug", scope, message),
|
|
1847
|
+
info: (scope, message) => emit2("info", scope, message),
|
|
1848
|
+
warn: (scope, message) => emit2("warn", scope, message),
|
|
1849
|
+
error: (scope, message) => emit2("error", scope, message),
|
|
1850
|
+
get file() {
|
|
1851
|
+
return fd === null ? null : path;
|
|
1852
|
+
},
|
|
1853
|
+
onRecord(listener) {
|
|
1854
|
+
listeners.add(listener);
|
|
1855
|
+
return () => listeners.delete(listener);
|
|
1856
|
+
},
|
|
1857
|
+
close() {
|
|
1858
|
+
listeners.clear();
|
|
1859
|
+
fd = null;
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
function formatFeedLine(record) {
|
|
1864
|
+
return formatDiskLine(record).trimEnd();
|
|
1865
|
+
}
|
|
1866
|
+
function attachTerminalMirror(logger, enabled = process.env.FRAY_DEBUG === "1") {
|
|
1867
|
+
if (!enabled) return () => {
|
|
1868
|
+
};
|
|
1869
|
+
return logger.onRecord((record) => process.stderr.write(`${formatFeedLine(record)}
|
|
1870
|
+
`));
|
|
1871
|
+
}
|
|
1872
|
+
function ambientLogger() {
|
|
1873
|
+
if (ambient) return ambient;
|
|
1874
|
+
const inherited = process.env[LOG_FILE_ENV] ?? process.env[LOG_PATH_ENV];
|
|
1875
|
+
const level = process.env[LOG_LEVEL_ENV];
|
|
1876
|
+
const logger = createLogger({
|
|
1877
|
+
...inherited ? { file: inherited, owner: false } : { file: null },
|
|
1878
|
+
...level && level in LEVEL_ORDER ? { level } : {}
|
|
1879
|
+
});
|
|
1880
|
+
attachTerminalMirror(logger);
|
|
1881
|
+
ambient = logger;
|
|
1882
|
+
return ambient;
|
|
1883
|
+
}
|
|
1884
|
+
var LEVEL_ORDER, LOG_FILE_ENV, LOG_LEVEL_ENV, LOG_PATH_ENV, RETAINED_RUNS, RETAINED_DAYS, MAX_LOG_BYTES, ambient, log;
|
|
1885
|
+
var init_logging = __esm({
|
|
1886
|
+
"packages/server/src/logging.ts"() {
|
|
1887
|
+
"use strict";
|
|
1888
|
+
LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
1889
|
+
LOG_FILE_ENV = "FRAY_LOG_FILE";
|
|
1890
|
+
LOG_LEVEL_ENV = "FRAY_LOG_LEVEL";
|
|
1891
|
+
LOG_PATH_ENV = "FRAY_LOG_PATH";
|
|
1892
|
+
RETAINED_RUNS = 20;
|
|
1893
|
+
RETAINED_DAYS = 14;
|
|
1894
|
+
MAX_LOG_BYTES = 32 * 1024 * 1024;
|
|
1895
|
+
ambient = null;
|
|
1896
|
+
log = {
|
|
1897
|
+
debug: (scope, message) => ambientLogger().debug(scope, message),
|
|
1898
|
+
info: (scope, message) => ambientLogger().info(scope, message),
|
|
1899
|
+
warn: (scope, message) => ambientLogger().warn(scope, message),
|
|
1900
|
+
error: (scope, message) => ambientLogger().error(scope, message)
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
|
|
1725
1905
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
1726
1906
|
var util, objectUtil, ZodParsedType, getParsedType;
|
|
1727
1907
|
var init_util = __esm({
|
|
@@ -6151,6 +6331,14 @@ var init_interactions = __esm({
|
|
|
6151
6331
|
...CommonPayload,
|
|
6152
6332
|
kind: external_exports.literal("permission-approval"),
|
|
6153
6333
|
permission: InteractionOpaqueId,
|
|
6334
|
+
// The exact text of what is being authorized — a command, a path, a request body. Multi-line and
|
|
6335
|
+
// rendered monospace, because a Bash command is the one thing the operator actually reads and a
|
|
6336
|
+
// single-line label cannot hold a heredoc. Adapters redact and clamp it before it gets here.
|
|
6337
|
+
preview: InteractionPreview.optional(),
|
|
6338
|
+
// A shell prompt to render in FRONT of `preview`, which is how a terminal shows a command and where
|
|
6339
|
+
// the working directory belongs when there is one. Set it only when the preview genuinely IS a shell
|
|
6340
|
+
// command line: a file path or an MCP argument behind a `❯` would be a lie about what runs.
|
|
6341
|
+
promptLabel: plainText(256, 512).optional(),
|
|
6154
6342
|
resourceLabel: plainText(1024, 2048).optional(),
|
|
6155
6343
|
workingDirectoryLabel: plainText(1024, 2048).optional(),
|
|
6156
6344
|
scopeLabel: InteractionDescription.optional(),
|
|
@@ -6582,7 +6770,37 @@ function formatGithubWakeSteer({ ref, items, omitted }) {
|
|
|
6582
6770
|
|
|
6583
6771
|
${lines}${more}${reviewTail}`;
|
|
6584
6772
|
}
|
|
6585
|
-
|
|
6773
|
+
function parseGithubWakeSteer(text) {
|
|
6774
|
+
const item = (label, actor, bot, at, url) => ({
|
|
6775
|
+
label,
|
|
6776
|
+
actor,
|
|
6777
|
+
bot,
|
|
6778
|
+
...at ? { at } : {},
|
|
6779
|
+
...url ? { url } : {}
|
|
6780
|
+
});
|
|
6781
|
+
const lines = text.trim().split("\n").map((line) => line.trim()).filter(Boolean);
|
|
6782
|
+
const single = WAKE_SINGLE.exec(lines[0] ?? "");
|
|
6783
|
+
if (single) {
|
|
6784
|
+
return { ref: single[3], omitted: 0, items: [item(single[2], single[4], single[1] === "\u{1F916}", single[5], single[6])] };
|
|
6785
|
+
}
|
|
6786
|
+
const head = WAKE_MULTI_HEAD.exec(lines[0] ?? "");
|
|
6787
|
+
if (!head) return null;
|
|
6788
|
+
const items = [];
|
|
6789
|
+
let omitted = 0;
|
|
6790
|
+
for (const line of lines.slice(1)) {
|
|
6791
|
+
const more = WAKE_MORE.exec(line);
|
|
6792
|
+
if (more) {
|
|
6793
|
+
omitted = Number(more[1]);
|
|
6794
|
+
continue;
|
|
6795
|
+
}
|
|
6796
|
+
const m = WAKE_ITEM.exec(line);
|
|
6797
|
+
if (!m) continue;
|
|
6798
|
+
items.push(item(m[2], m[3], m[1] === "\u{1F916}", m[4], m[5]));
|
|
6799
|
+
}
|
|
6800
|
+
if (!items.length || items.length + omitted !== Number(head[2])) return null;
|
|
6801
|
+
return { ref: head[3], omitted, items };
|
|
6802
|
+
}
|
|
6803
|
+
var ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrayStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, NativeInputRequired, AwaitingHint, AWAITING_TIMER_RE, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, ThreadFence, PlanView, LimitWindow, LimitPause, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, 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, SetThreadSnoozeInput, ConfirmAwaitingInput, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, WAKE_DELIVERY_TOKEN_TAIL, AGENT_MESSAGE_WRAPPER, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REVIEW_LEAD, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, WAKE_ITEM, WAKE_MORE, GithubStatus, GithubItem, GithubListInput, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, THOUGHT_EVENT_PREFIX, TranscriptPart, TranscriptMessage, TranscriptPageCursor, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketClientMsg, DEFAULT_PORT, tmuxSessionName;
|
|
6586
6804
|
var init_src = __esm({
|
|
6587
6805
|
"packages/shared/src/index.ts"() {
|
|
6588
6806
|
"use strict";
|
|
@@ -7216,7 +7434,13 @@ var init_src = __esm({
|
|
|
7216
7434
|
sessionId: external_exports.string().min(1),
|
|
7217
7435
|
message: external_exports.string().min(1),
|
|
7218
7436
|
// Generated once before the optimistic clear so a transport replay can be idempotent.
|
|
7219
|
-
deliveryId: external_exports.string().min(1).max(200).optional()
|
|
7437
|
+
deliveryId: external_exports.string().min(1).max(200).optional(),
|
|
7438
|
+
// Retire the worker's live process before delivering, so this message lands in a `claude` that has
|
|
7439
|
+
// just started. The operator's "Restart worker" verb — the ONLY caller that sets it — exists because
|
|
7440
|
+
// a worker inherits its plugin/hooks AND its system prompt at process start and can never pick up a
|
|
7441
|
+
// newer fray build in place (hooks are read once, at startup). Everything else that needs a fresh
|
|
7442
|
+
// process derives it server-side; see needsFreshProcessForLimit.
|
|
7443
|
+
freshProcess: external_exports.boolean().optional()
|
|
7220
7444
|
});
|
|
7221
7445
|
UnqueueFollowUpInput = external_exports.object({
|
|
7222
7446
|
slug: ThreadSlug,
|
|
@@ -7287,6 +7511,25 @@ ${DISPATCH_TASK_BANNER}
|
|
|
7287
7511
|
GITHUB_DISPATCH_UI_BOUNDARY = "<!-- fray-ui:github-dispatch-ui-boundary:v1 -->";
|
|
7288
7512
|
WAKE_DELIVERY_TOKEN_TAIL = /\n*<!-- fray-wake:[A-Za-z0-9_-]+ -->\s*$/;
|
|
7289
7513
|
AGENT_MESSAGE_WRAPPER = /^<agent-message from="([^"]*)">\n?([\s\S]*?)\n?<\/agent-message>\s*$/;
|
|
7514
|
+
GithubWakeItem = external_exports.object({
|
|
7515
|
+
label: external_exports.string(),
|
|
7516
|
+
// the activity's noun ("comment", "approval", "change request", …)
|
|
7517
|
+
actor: external_exports.string(),
|
|
7518
|
+
// GitHub login, no leading @
|
|
7519
|
+
bot: external_exports.boolean(),
|
|
7520
|
+
// drives the 🤖/👤 icon; an app files most of what wakes this watcher
|
|
7521
|
+
at: external_exports.string().optional(),
|
|
7522
|
+
// ISO8601
|
|
7523
|
+
url: external_exports.string().optional()
|
|
7524
|
+
// the item's own permalink
|
|
7525
|
+
});
|
|
7526
|
+
GithubWakeSteer = external_exports.object({
|
|
7527
|
+
ref: external_exports.string(),
|
|
7528
|
+
// owner/repo#N
|
|
7529
|
+
items: external_exports.array(GithubWakeItem),
|
|
7530
|
+
omitted: external_exports.number()
|
|
7531
|
+
// fresh items counted but not named (the enumeration cap)
|
|
7532
|
+
});
|
|
7290
7533
|
WAKE_SCOPE = "ignore older activity you have already handled";
|
|
7291
7534
|
WAKE_REVIEW_LEAD = "A review's body is often empty because its substance is inline comments. Read them, one call each:";
|
|
7292
7535
|
WAKE_REF = String.raw`[A-Za-z0-9][\w.-]*\/[A-Za-z0-9][\w.-]*#\d+`;
|
|
@@ -7296,6 +7539,8 @@ ${DISPATCH_TASK_BANNER}
|
|
|
7296
7539
|
WAKE_MULTI_HEAD = new RegExp(
|
|
7297
7540
|
String.raw`^(👤|🤖) (\d+) new GitHub items on (${WAKE_REF})\. Read exactly these — ` + WAKE_SCOPE + String.raw` — and continue:$`
|
|
7298
7541
|
);
|
|
7542
|
+
WAKE_ITEM = /^- (👤|🤖) (.+?) from @(\S+?)(?: at (\S+?))?(?:: (\S+))?$/;
|
|
7543
|
+
WAKE_MORE = /^- …and (\d+) more not listed — check .+ for the rest$/;
|
|
7299
7544
|
GithubStatus = external_exports.object({
|
|
7300
7545
|
installed: external_exports.boolean(),
|
|
7301
7546
|
inRepo: external_exports.boolean(),
|
|
@@ -7585,11 +7830,20 @@ ${DISPATCH_TASK_BANNER}
|
|
|
7585
7830
|
// non-reasoning messages and on any reasoning block whose timing couldn't be derived.
|
|
7586
7831
|
durationMs: external_exports.number().nonnegative().optional(),
|
|
7587
7832
|
// A turn-BOUNDARY marker: this `kind:"event"` line was emitted at the position an EXTERNAL wake — a
|
|
7588
|
-
//
|
|
7589
|
-
//
|
|
7590
|
-
//
|
|
7591
|
-
//
|
|
7592
|
-
|
|
7833
|
+
// This event opened a fresh turn, so it renders as a centered divider rule carrying the cause label —
|
|
7834
|
+
// without it, two consecutive assistant turns (each with its own trailing signal) paint as one
|
|
7835
|
+
// seamless bubble. Additive + optional: an old client ignores it and shows the plain quiet event line
|
|
7836
|
+
// (graceful degrade).
|
|
7837
|
+
//
|
|
7838
|
+
// It names WHICH KIND of boundary, because two unrelated events earn the divider and the client has to
|
|
7839
|
+
// tell them apart to put the right glyph on each:
|
|
7840
|
+
// wake — a background task/shell completion `<task-notification>` re-invoked the agent
|
|
7841
|
+
// compaction — the provider rewrote the conversation and dropped everything above this point
|
|
7842
|
+
// It was a bare boolean until the dividers grew icons (a shell glyph on a compaction line is simply
|
|
7843
|
+
// wrong), and the kind has to come from the SERVER: the alternative is the client sniffing the label
|
|
7844
|
+
// text, which is the guess this codebase refuses everywhere else. A string stays truthy, so any
|
|
7845
|
+
// surviving `if (boundary)` reads exactly as it did.
|
|
7846
|
+
boundary: external_exports.enum(["wake", "compaction"]).optional(),
|
|
7593
7847
|
// Block-ordered content for an assistant turn (see TranscriptPart). Defaults to [] so a pre-restart
|
|
7594
7848
|
// server (which ships only text/tools) parses; the client renders `parts` when non-empty and falls
|
|
7595
7849
|
// back to the legacy tools-then-text layout when it's empty. `text`/`tools` stay populated for that
|
|
@@ -7618,6 +7872,17 @@ ${DISPATCH_TASK_BANNER}
|
|
|
7618
7872
|
// old client ignores it and shows the plain bubble (the previous behavior), and an old server simply
|
|
7619
7873
|
// never sets it.
|
|
7620
7874
|
wake: external_exports.boolean().optional(),
|
|
7875
|
+
// The STRUCTURED wake, parsed by the server from the same text the same build formatted. The chat
|
|
7876
|
+
// renders the divider from this rather than re-deriving it from prose in the browser.
|
|
7877
|
+
//
|
|
7878
|
+
// It exists because re-deriving it in the browser is version-skewed by construction. `web/api/boot.ts`
|
|
7879
|
+
// adopts a new server boot id IN PLACE (so an unsent draft survives a restart), so a promoted artifact
|
|
7880
|
+
// swaps the server under tabs that keep their old bundle — and on 2026-07-31 a steer that gained two
|
|
7881
|
+
// agent-facing lines met parsers that predated them, which cost every open tab its card and dumped the
|
|
7882
|
+
// raw `gh api …` text into the transcript instead. Server-side, formatter and parser can never
|
|
7883
|
+
// disagree. Additive + optional: absent from a legacy transcript or an older server, and the client
|
|
7884
|
+
// falls back to `parseGithubWakeSteer` on the text.
|
|
7885
|
+
wakeSteer: GithubWakeSteer.optional(),
|
|
7621
7886
|
// A SUB-AGENT (or peer session) wrote this user turn, not the human — the same defect class `wake`
|
|
7622
7887
|
// above corrects. Claude Code's agent-to-agent channel (a background child calling
|
|
7623
7888
|
// `SendMessage({to:"main"})`) delivers UPWARD into the parent's queue like any follow-up, so the
|
|
@@ -10010,15 +10275,15 @@ var init_settings = __esm({
|
|
|
10010
10275
|
});
|
|
10011
10276
|
|
|
10012
10277
|
// packages/server/src/discover.ts
|
|
10013
|
-
import { readdirSync, statSync as
|
|
10014
|
-
import { join as
|
|
10278
|
+
import { readdirSync as readdirSync2, statSync as statSync5, openSync as openSync5, readSync, closeSync as closeSync4 } from "node:fs";
|
|
10279
|
+
import { join as join6 } from "node:path";
|
|
10015
10280
|
function sentinelFor(sessionId) {
|
|
10016
10281
|
return `threads/${sessionId}/scratch.md`;
|
|
10017
10282
|
}
|
|
10018
10283
|
function readHead(path) {
|
|
10019
10284
|
let fd;
|
|
10020
10285
|
try {
|
|
10021
|
-
fd =
|
|
10286
|
+
fd = openSync5(path, "r");
|
|
10022
10287
|
const buf = Buffer.allocUnsafe(HEAD_BYTES);
|
|
10023
10288
|
const n = readSync(fd, buf, 0, HEAD_BYTES, 0);
|
|
10024
10289
|
return buf.toString("utf8", 0, n);
|
|
@@ -10039,7 +10304,7 @@ function discoverTranscriptId(logDir, sessionId, opts = {}) {
|
|
|
10039
10304
|
const sentinel = sentinelFor(sessionId);
|
|
10040
10305
|
let names;
|
|
10041
10306
|
try {
|
|
10042
|
-
names =
|
|
10307
|
+
names = readdirSync2(logDir);
|
|
10043
10308
|
} catch {
|
|
10044
10309
|
return void 0;
|
|
10045
10310
|
}
|
|
@@ -10048,10 +10313,10 @@ function discoverTranscriptId(logDir, sessionId, opts = {}) {
|
|
|
10048
10313
|
if (name.startsWith(".") || !name.endsWith(".jsonl")) continue;
|
|
10049
10314
|
const id = name.slice(0, -".jsonl".length);
|
|
10050
10315
|
if (!id || id === sessionId || exclude?.has(id)) continue;
|
|
10051
|
-
const path =
|
|
10316
|
+
const path = join6(logDir, name);
|
|
10052
10317
|
let mtime;
|
|
10053
10318
|
try {
|
|
10054
|
-
mtime =
|
|
10319
|
+
mtime = statSync5(path).mtimeMs;
|
|
10055
10320
|
} catch {
|
|
10056
10321
|
continue;
|
|
10057
10322
|
}
|
|
@@ -10423,7 +10688,7 @@ function spawnWithRunner(slug, cmd, cwd, env, options, runner) {
|
|
|
10423
10688
|
invalidateLiveness();
|
|
10424
10689
|
return identity;
|
|
10425
10690
|
} catch {
|
|
10426
|
-
|
|
10691
|
+
log.error("tmux", `worker spawn failed (stage=${stage}, created=${identity ? "yes" : "no"})`);
|
|
10427
10692
|
throw new TmuxSpawnError(stage, identity);
|
|
10428
10693
|
}
|
|
10429
10694
|
}
|
|
@@ -10993,6 +11258,7 @@ var init_tmux = __esm({
|
|
|
10993
11258
|
"use strict";
|
|
10994
11259
|
init_src();
|
|
10995
11260
|
init_tmux_socket();
|
|
11261
|
+
init_logging();
|
|
10996
11262
|
init_tmux_socket();
|
|
10997
11263
|
execFileAsync = promisify(execFile);
|
|
10998
11264
|
socket = "fray";
|
|
@@ -11316,6 +11582,28 @@ A \` \`\`\`question \` block IS the handback \u2014 do not also emit a \`done\`/
|
|
|
11316
11582
|
as your next user message, possibly as terse as "1: A, 2: B".`;
|
|
11317
11583
|
STOP_CRITERION = `## The stop criterion
|
|
11318
11584
|
|
|
11585
|
+
**COMING TO REST IS A STOP, and it needs the same justification as a question.** Everything below is
|
|
11586
|
+
about when to ASK \u2014 but the far more common failure is quieter: you finish one part of a multi-part
|
|
11587
|
+
instruction, write it up, and rest with the rest of the mandate untouched. Nobody asked you to stop;
|
|
11588
|
+
you simply produced a handoff because a handoff was the shape closest to hand. That is not a report,
|
|
11589
|
+
it is an abandonment, and it costs the human the same hours a needless question does.
|
|
11590
|
+
|
|
11591
|
+
So: **if the human's instruction still has parts left, do not write up \u2014 do the next part in this same
|
|
11592
|
+
turn.** Keep issuing tool calls. A verified milestone is not a stopping point, a green test run is not
|
|
11593
|
+
a stopping point, and neither is a long turn \u2014 the conversation is summarized automatically, so turn
|
|
11594
|
+
length is never a reason to pause. The only things that end a turn are the stop criteria below and
|
|
11595
|
+
actually being finished.
|
|
11596
|
+
|
|
11597
|
+
Three specific traps, each of which has ended a turn that should have continued:
|
|
11598
|
+
|
|
11599
|
+
- **Announcing the next step instead of taking it.** "Now starting X" followed by no tool call is a
|
|
11600
|
+
stop wearing the costume of progress. If you can write the sentence, you can make the call instead.
|
|
11601
|
+
- **Writing the next action into the scratchpad and feeling done.** Recording work is not doing work.
|
|
11602
|
+
The pad is crash insurance, never a handoff \u2014 see the scratchpad section. If the pad's next action
|
|
11603
|
+
is something the human already told you to do, the only correct move is to do it NOW.
|
|
11604
|
+
- **Pre-emptively worrying about context budget.** You do not get to stop early because the turn feels
|
|
11605
|
+
long. If context genuinely runs out, the harness summarizes and you continue.
|
|
11606
|
+
|
|
11319
11607
|
**Your default is to DECIDE.** A reversible call costs minutes to redo; a round-trip to the human costs
|
|
11320
11608
|
hours \u2014 they may not read your question for hours, and the whole effort sits idle until they do. If it
|
|
11321
11609
|
is derivable from the code, the conventions, or ordinary engineering judgment, it is yours: make it,
|
|
@@ -11369,6 +11657,12 @@ rate-limited to once every two minutes so it cannot create a tight stop loop. Re
|
|
|
11369
11657
|
\`stop_hook\` to deregister it before genuinely finishing. This is a rest-time nudge, not a timer and
|
|
11370
11658
|
cannot wake a turn that already stopped.
|
|
11371
11659
|
|
|
11660
|
+
- **IT IS OPTIONAL, IT IS NOT A DELIVERABLE, AND WRITING IN IT IS NOT DOING THE WORK.** The pad exists
|
|
11661
|
+
in case you want it \u2014 crash insurance for a long effort, and the file watchers and stop hooks read
|
|
11662
|
+
it. A single direct task usually needs NOTHING here: just do the task. Never let a pad entry stand in
|
|
11663
|
+
for an action. Recording "next: X" when the human asked for X is not progress on X, and a turn that
|
|
11664
|
+
ends right after a pad update is nearly always a turn that stopped for no reason. When it is worth
|
|
11665
|
+
writing, write it AS YOU GO, mid-work, and then keep working.
|
|
11372
11666
|
- **It is your compaction-survival mechanism, so use it deliberately.** A long effort WILL be
|
|
11373
11667
|
compacted, and compaction drops the REASONING first \u2014 the plan, the alternatives you ruled out, why
|
|
11374
11668
|
the human chose what they chose. A summary preserves what you did, not why. So write that here AS
|
|
@@ -11408,6 +11702,13 @@ rate-limited to once every two minutes so it cannot create a tight stop loop. Re
|
|
|
11408
11702
|
\`stop_hook\` to deregister it before genuinely finishing. This is a rest-time nudge, not a timer and
|
|
11409
11703
|
cannot wake a turn that already stopped.
|
|
11410
11704
|
|
|
11705
|
+
**IT IS OPTIONAL, IT IS NOT A DELIVERABLE, AND WRITING IN IT IS NOT DOING THE WORK.** The pad exists in
|
|
11706
|
+
case you want it \u2014 crash insurance for a long effort, and the file watchers and stop hooks read it. A
|
|
11707
|
+
single direct task usually needs NOTHING here: just do the task. Never let a pad entry stand in for an
|
|
11708
|
+
action. Recording "next: X" when the human asked for X is not progress on X, and a turn that ends right
|
|
11709
|
+
after a pad update is nearly always a turn that stopped for no reason. When it is worth writing, write
|
|
11710
|
+
it AS YOU GO, mid-work, and then keep working.
|
|
11711
|
+
|
|
11411
11712
|
**It is your compaction-survival mechanism, so use it deliberately.** A long effort WILL be compacted,
|
|
11412
11713
|
and compaction drops the REASONING first \u2014 the plan, the alternatives you ruled out, why the human
|
|
11413
11714
|
chose what they chose. A summary preserves what you did, not why. So write that here AS YOU GO rather
|
|
@@ -11735,10 +12036,10 @@ var init_detached_daemons = __esm({
|
|
|
11735
12036
|
});
|
|
11736
12037
|
|
|
11737
12038
|
// packages/server/src/backend/claude-broker-diagnostics.ts
|
|
11738
|
-
import { appendFileSync, mkdirSync as
|
|
11739
|
-
import { join as
|
|
12039
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync4, statSync as statSync6 } from "node:fs";
|
|
12040
|
+
import { join as join7 } from "node:path";
|
|
11740
12041
|
function claudeBrokerDiagnosticLogPath(stateDir, sessionId) {
|
|
11741
|
-
return
|
|
12042
|
+
return join7(stateDir, "claude-broker", `${sessionId}.diagnostics.log`);
|
|
11742
12043
|
}
|
|
11743
12044
|
function readClaudeBrokerExit(stateDir, sessionId) {
|
|
11744
12045
|
let newest = null;
|
|
@@ -11782,14 +12083,14 @@ var init_claude_broker_diagnostics = __esm({
|
|
|
11782
12083
|
// packages/server/src/backend/claude-broker-host.ts
|
|
11783
12084
|
import { spawn as spawn2 } from "node:child_process";
|
|
11784
12085
|
import { createHash as createHash4, randomUUID as randomUUID8 } from "node:crypto";
|
|
11785
|
-
import { accessSync, constants as fsConstants, mkdirSync as
|
|
11786
|
-
import { delimiter, dirname as dirname4, isAbsolute as isAbsolute4, join as
|
|
12086
|
+
import { accessSync, constants as fsConstants, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync3, unlinkSync } from "node:fs";
|
|
12087
|
+
import { delimiter, dirname as dirname4, isAbsolute as isAbsolute4, join as join8 } from "node:path";
|
|
11787
12088
|
function resolveClaudeExecutableAbsolute(bin, env = process.env) {
|
|
11788
12089
|
const candidate = bin && bin.length > 0 ? bin : "claude";
|
|
11789
12090
|
if (isAbsolute4(candidate)) return candidate;
|
|
11790
12091
|
for (const dir of (env.PATH ?? "").split(delimiter)) {
|
|
11791
12092
|
if (!dir) continue;
|
|
11792
|
-
const full =
|
|
12093
|
+
const full = join8(dir, candidate);
|
|
11793
12094
|
try {
|
|
11794
12095
|
accessSync(full, fsConstants.X_OK);
|
|
11795
12096
|
return full;
|
|
@@ -11801,11 +12102,11 @@ function resolveClaudeExecutableAbsolute(bin, env = process.env) {
|
|
|
11801
12102
|
function claudeBrokerSocketPath(stateDir, sessionId) {
|
|
11802
12103
|
const key = createHash4("sha256").update(stateDir).update("\0").update(sessionId).digest("hex").slice(0, 16);
|
|
11803
12104
|
if (process.platform === "win32") return `\\\\.\\pipe\\fray-claude-${key}`;
|
|
11804
|
-
return
|
|
12105
|
+
return join8(process.env.TMPDIR ?? "/tmp", `fray-claude-${key}.sock`);
|
|
11805
12106
|
}
|
|
11806
12107
|
function claudeBrokerRecordPath(stateDir, sessionId) {
|
|
11807
12108
|
const key = createHash4("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
11808
|
-
return
|
|
12109
|
+
return join8(stateDir, "claude-broker", `${key}.json`);
|
|
11809
12110
|
}
|
|
11810
12111
|
function pidAlive(pid) {
|
|
11811
12112
|
try {
|
|
@@ -11834,17 +12135,17 @@ function liveBrokerRecord(recordPath2) {
|
|
|
11834
12135
|
return null;
|
|
11835
12136
|
}
|
|
11836
12137
|
function liveBrokerRecords(stateDir) {
|
|
11837
|
-
const dir =
|
|
12138
|
+
const dir = join8(stateDir, "claude-broker");
|
|
11838
12139
|
let names;
|
|
11839
12140
|
try {
|
|
11840
|
-
names =
|
|
12141
|
+
names = readdirSync3(dir);
|
|
11841
12142
|
} catch {
|
|
11842
12143
|
return [];
|
|
11843
12144
|
}
|
|
11844
12145
|
const out = [];
|
|
11845
12146
|
for (const name of names) {
|
|
11846
12147
|
if (!name.endsWith(".json")) continue;
|
|
11847
|
-
const record = liveBrokerRecord(
|
|
12148
|
+
const record = liveBrokerRecord(join8(dir, name));
|
|
11848
12149
|
if (record) out.push(record);
|
|
11849
12150
|
}
|
|
11850
12151
|
return out;
|
|
@@ -11852,7 +12153,7 @@ function liveBrokerRecords(stateDir) {
|
|
|
11852
12153
|
function forkBroker(options) {
|
|
11853
12154
|
const socketPath = claudeBrokerSocketPath(options.stateDir, options.sessionId);
|
|
11854
12155
|
const recordPath2 = claudeBrokerRecordPath(options.stateDir, options.sessionId);
|
|
11855
|
-
|
|
12156
|
+
mkdirSync7(dirname4(recordPath2), { recursive: true });
|
|
11856
12157
|
const config = {
|
|
11857
12158
|
socketPath,
|
|
11858
12159
|
cwd: options.cwd,
|
|
@@ -12071,6 +12372,17 @@ var init_claude_broker_client = __esm({
|
|
|
12071
12372
|
});
|
|
12072
12373
|
|
|
12073
12374
|
// packages/server/src/backend/claude-agent-sdk-protocol.ts
|
|
12375
|
+
function hasUndeliverableInputText(value) {
|
|
12376
|
+
for (const character of value) {
|
|
12377
|
+
const code = character.codePointAt(0);
|
|
12378
|
+
if (code >= 55296 && code <= 57343) return true;
|
|
12379
|
+
if (code === 8232 || code === 8233) return true;
|
|
12380
|
+
if (code <= 31 || code >= 127 && code <= 159) {
|
|
12381
|
+
if (!DELIVERABLE_CONTROL_CODES.has(code)) return true;
|
|
12382
|
+
}
|
|
12383
|
+
}
|
|
12384
|
+
return false;
|
|
12385
|
+
}
|
|
12074
12386
|
function utf8Bytes(value) {
|
|
12075
12387
|
return encoder2.encode(value).byteLength;
|
|
12076
12388
|
}
|
|
@@ -12151,8 +12463,8 @@ function validateInputMessage(value) {
|
|
|
12151
12463
|
if (utf8Bytes(value.text) > CLAUDE_AGENT_SDK_MAX_INPUT_BYTES) {
|
|
12152
12464
|
throw new ClaudeAgentSdkProtocolError(`input.text exceeds ${CLAUDE_AGENT_SDK_MAX_INPUT_BYTES} bytes`);
|
|
12153
12465
|
}
|
|
12154
|
-
const text =
|
|
12155
|
-
if (text
|
|
12466
|
+
const text = value.text;
|
|
12467
|
+
if (hasUndeliverableInputText(text)) throw new ClaudeAgentSdkProtocolError("input.text contains unsafe text");
|
|
12156
12468
|
const parentToolUseId = boundedOptionalId(value.parentToolUseId, "input.parentToolUseId");
|
|
12157
12469
|
return parentToolUseId === void 0 ? { id, text } : { id, text, parentToolUseId };
|
|
12158
12470
|
}
|
|
@@ -12182,7 +12494,7 @@ function validatePermissionDecision(value) {
|
|
|
12182
12494
|
}
|
|
12183
12495
|
throw new ClaudeAgentSdkProtocolError("permission decision behavior is unsupported");
|
|
12184
12496
|
}
|
|
12185
|
-
var CLAUDE_AGENT_SDK_MAX_INPUT_BYTES, CLAUDE_AGENT_SDK_MAX_JSON_BYTES, CLAUDE_AGENT_SDK_MAX_EVENT_TEXT_BYTES, CLAUDE_AGENT_SDK_MAX_DIAGNOSTIC_BYTES, encoder2, UNSAFE_TEXT2, ID_PATTERN, RESERVED_KEYS, CLAUDE_BROKER_CAPABILITY_SUBAGENT_STEER, CLAUDE_BROKER_CAPABILITY_CANCEL_INPUT, CLAUDE_BROKER_CAPABILITY_STOP_TASK, ClaudeAgentSdkProtocolError;
|
|
12497
|
+
var CLAUDE_AGENT_SDK_MAX_INPUT_BYTES, CLAUDE_AGENT_SDK_MAX_JSON_BYTES, CLAUDE_AGENT_SDK_MAX_EVENT_TEXT_BYTES, CLAUDE_AGENT_SDK_MAX_DIAGNOSTIC_BYTES, encoder2, UNSAFE_TEXT2, ID_PATTERN, RESERVED_KEYS, DELIVERABLE_CONTROL_CODES, CLAUDE_BROKER_CAPABILITY_SUBAGENT_STEER, CLAUDE_BROKER_CAPABILITY_CANCEL_INPUT, CLAUDE_BROKER_CAPABILITY_STOP_TASK, ClaudeAgentSdkProtocolError;
|
|
12186
12498
|
var init_claude_agent_sdk_protocol = __esm({
|
|
12187
12499
|
"packages/server/src/backend/claude-agent-sdk-protocol.ts"() {
|
|
12188
12500
|
"use strict";
|
|
@@ -12194,6 +12506,7 @@ var init_claude_agent_sdk_protocol = __esm({
|
|
|
12194
12506
|
UNSAFE_TEXT2 = /[\p{Cf}\p{Cs}\p{Zl}\p{Zp}\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
|
|
12195
12507
|
ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/;
|
|
12196
12508
|
RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
12509
|
+
DELIVERABLE_CONTROL_CODES = /* @__PURE__ */ new Set([9, 10, 13]);
|
|
12197
12510
|
CLAUDE_BROKER_CAPABILITY_SUBAGENT_STEER = "subagent-steer-v1";
|
|
12198
12511
|
CLAUDE_BROKER_CAPABILITY_CANCEL_INPUT = "cancel-input-v1";
|
|
12199
12512
|
CLAUDE_BROKER_CAPABILITY_STOP_TASK = "stop-task-v1";
|
|
@@ -12353,28 +12666,35 @@ var init_credential_redaction = __esm({
|
|
|
12353
12666
|
});
|
|
12354
12667
|
|
|
12355
12668
|
// packages/server/src/backend/claude-permission-interactions.ts
|
|
12669
|
+
import { homedir as homedir4 } from "node:os";
|
|
12356
12670
|
function clip(text, max) {
|
|
12357
12671
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
12358
12672
|
}
|
|
12673
|
+
function tildePath(path) {
|
|
12674
|
+
const home = homedir4();
|
|
12675
|
+
if (!path.startsWith(home)) return path;
|
|
12676
|
+
const rest = path.slice(home.length);
|
|
12677
|
+
return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path;
|
|
12678
|
+
}
|
|
12679
|
+
function inputPreview(input, shown) {
|
|
12680
|
+
if (!input) return "";
|
|
12681
|
+
const subject = SUBJECT_KEYS.find((key) => typeof input[key] === "string" && input[key] !== "");
|
|
12682
|
+
const lines = Object.entries(input).filter((entry) => entry[0] !== subject && entry[1] !== "" && entry[1] !== null && entry[1] !== shown).map((entry) => `${entry[0]}: ${typeof entry[1] === "string" ? entry[1] : JSON.stringify(entry[1])}`);
|
|
12683
|
+
if (subject) lines.unshift(String(input[subject]));
|
|
12684
|
+
const text = redactCredentialSyntax(lines.join("\n\n")).split("\n");
|
|
12685
|
+
return clip(text.length > PREVIEW_MAX_LINES ? `${text.slice(0, PREVIEW_MAX_LINES).join("\n")}
|
|
12686
|
+
\u2026` : text.join("\n"), PREVIEW_MAX_CHARS);
|
|
12687
|
+
}
|
|
12359
12688
|
function buildClaudePermissionInteraction(request, owner) {
|
|
12360
12689
|
const tool = request.toolName || "tool";
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
} catch {
|
|
12365
|
-
inputSummary = "(uninspectable input)";
|
|
12366
|
-
}
|
|
12367
|
-
const title = clip(`Approve ${tool}?`, 150);
|
|
12690
|
+
const command = typeof request.input?.command === "string" ? request.input.command : "";
|
|
12691
|
+
const title = clip(command ? "Run a command?" : `Approve ${tool}?`, 150);
|
|
12692
|
+
const description = request.description ? clip(redactCredentialSyntax(request.description), 1e3) : "";
|
|
12368
12693
|
const message = clip(
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
${clip(redactCredentialSyntax(request.description), 1e3)}` : "") + (inputSummary ? `
|
|
12372
|
-
|
|
12373
|
-
Input: ${inputSummary}` : "") + (request.blockedPath ? `
|
|
12374
|
-
|
|
12375
|
-
Path: ${clip(request.blockedPath, 500)}` : ""),
|
|
12376
|
-
7900
|
|
12694
|
+
[description, request.blockedPath ? `Blocked path: ${clip(request.blockedPath, 500)}` : ""].filter(Boolean).join("\n\n"),
|
|
12695
|
+
2e3
|
|
12377
12696
|
);
|
|
12697
|
+
const preview = inputPreview(request.input, request.description);
|
|
12378
12698
|
const parsed = InteractionRequest.safeParse({
|
|
12379
12699
|
protocolVersion: INTERACTION_PROTOCOL_VERSION,
|
|
12380
12700
|
contentFormat: "plain-text",
|
|
@@ -12394,12 +12714,14 @@ Path: ${clip(request.blockedPath, 500)}` : ""),
|
|
|
12394
12714
|
providerRequestId: clip(request.requestId, 500),
|
|
12395
12715
|
allowedDecisions: ALLOWED_DECISIONS,
|
|
12396
12716
|
payload: {
|
|
12397
|
-
title,
|
|
12398
|
-
message,
|
|
12399
12717
|
kind: "permission-approval",
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12718
|
+
title,
|
|
12719
|
+
// `permission` is the card's readable identity, the way Codex's is "network+filesystem". It was
|
|
12720
|
+
// the requestId, which put a bare UUID on screen and told the operator nothing.
|
|
12721
|
+
permission: singleLine(tool, 250, "tool"),
|
|
12722
|
+
...message ? { message } : {},
|
|
12723
|
+
...preview ? { preview } : {},
|
|
12724
|
+
...command ? { promptLabel: singleLine(tildePath(owner.cwd), 250, "~", false) } : { workingDirectoryLabel: clip(owner.cwd, 2e3) }
|
|
12403
12725
|
},
|
|
12404
12726
|
expiresAt: null
|
|
12405
12727
|
});
|
|
@@ -12553,7 +12875,7 @@ function claudeQuestionDecisionFor(spec, decisionId, values) {
|
|
|
12553
12875
|
message: "Fray could not return the operator's answer in a form this tool accepts. Ask the question in your final message instead."
|
|
12554
12876
|
};
|
|
12555
12877
|
}
|
|
12556
|
-
var CLAUDE_PERMISSION_DECISIONS, ALLOWED_DECISIONS, CLAUDE_ASK_USER_QUESTION_TOOL, CLAUDE_QUESTION_DECISIONS, QUESTION_DECISIONS, MAX_QUESTIONS, MAX_OPTIONS, MAX_QUESTION_KEY_BYTES, encoder3, UNSAFE_TEXT_SOURCE, SCRUB_UNSAFE_TEXT, HAS_UNSAFE_TEXT, CLAUDE_NOTES_ONLY, CLAUDE_QUESTION_DECLINED;
|
|
12878
|
+
var CLAUDE_PERMISSION_DECISIONS, ALLOWED_DECISIONS, SUBJECT_KEYS, PREVIEW_MAX_CHARS, PREVIEW_MAX_LINES, CLAUDE_ASK_USER_QUESTION_TOOL, CLAUDE_QUESTION_DECISIONS, QUESTION_DECISIONS, MAX_QUESTIONS, MAX_OPTIONS, MAX_QUESTION_KEY_BYTES, encoder3, UNSAFE_TEXT_SOURCE, SCRUB_UNSAFE_TEXT, HAS_UNSAFE_TEXT, CLAUDE_NOTES_ONLY, CLAUDE_QUESTION_DECLINED;
|
|
12557
12879
|
var init_claude_permission_interactions = __esm({
|
|
12558
12880
|
"packages/server/src/backend/claude-permission-interactions.ts"() {
|
|
12559
12881
|
"use strict";
|
|
@@ -12568,6 +12890,9 @@ var init_claude_permission_interactions = __esm({
|
|
|
12568
12890
|
{ id: CLAUDE_PERMISSION_DECISIONS.allow, semantic: "approve", label: "Grant for turn", description: "Allow this tool call to run once." },
|
|
12569
12891
|
{ id: CLAUDE_PERMISSION_DECISIONS.deny, semantic: "deny", label: "Deny", description: "Block this tool call." }
|
|
12570
12892
|
];
|
|
12893
|
+
SUBJECT_KEYS = ["command", "file_path", "notebook_path", "path", "url", "pattern", "query", "prompt"];
|
|
12894
|
+
PREVIEW_MAX_CHARS = 6e3;
|
|
12895
|
+
PREVIEW_MAX_LINES = 200;
|
|
12571
12896
|
CLAUDE_ASK_USER_QUESTION_TOOL = "AskUserQuestion";
|
|
12572
12897
|
CLAUDE_QUESTION_DECISIONS = { answer: "answer", decline: "decline" };
|
|
12573
12898
|
QUESTION_DECISIONS = [
|
|
@@ -12594,6 +12919,20 @@ function claudeBrokerBridgeEnabled(env = process.env) {
|
|
|
12594
12919
|
function inputIdFor(deliveryId) {
|
|
12595
12920
|
return deliveryId && UUID_SHAPE.test(deliveryId) ? deliveryId : randomUUID9();
|
|
12596
12921
|
}
|
|
12922
|
+
function validatedInput(message) {
|
|
12923
|
+
try {
|
|
12924
|
+
return validateInputMessage(message);
|
|
12925
|
+
} catch (error) {
|
|
12926
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
12927
|
+
if (/exceeds/.test(detail)) {
|
|
12928
|
+
throw new Error(`This message is too long to send (the limit is ${CLAUDE_AGENT_SDK_MAX_INPUT_BYTES / 1024}KB). Shorten it and resend.`);
|
|
12929
|
+
}
|
|
12930
|
+
if (/unsafe text/.test(detail)) {
|
|
12931
|
+
throw new Error("This message holds a control or incomplete character fray can't send. Edit and resend.");
|
|
12932
|
+
}
|
|
12933
|
+
throw new Error(`fray could not send this message: ${detail}`);
|
|
12934
|
+
}
|
|
12935
|
+
}
|
|
12597
12936
|
function createClaudeAgentBrokerBridge(deps) {
|
|
12598
12937
|
const sessions = /* @__PURE__ */ new Map();
|
|
12599
12938
|
const executablePath = resolveClaudeExecutableAbsolute(deps.executablePath, deps.env);
|
|
@@ -12713,11 +13052,13 @@ function createClaudeAgentBrokerBridge(deps) {
|
|
|
12713
13052
|
killBroker(deps.stateDir, prior.sessionId);
|
|
12714
13053
|
sessions.delete(input.threadSlug);
|
|
12715
13054
|
}
|
|
13055
|
+
const message = validatedInput({ id: randomUUID9(), text: input.prompt });
|
|
12716
13056
|
const session = await attach3(input.threadSlug, input.sessionId, input.cwd, input.permissionMode ?? "default", { appendSystemPrompt: input.appendSystemPrompt, model: input.model, effort: input.effort });
|
|
12717
|
-
session.client.sendInput(
|
|
13057
|
+
session.client.sendInput(message);
|
|
12718
13058
|
return { binding: { threadSlug: input.threadSlug, sessionId: input.sessionId, cwd: input.cwd, generation: session.generation, state: "active" } };
|
|
12719
13059
|
},
|
|
12720
13060
|
async followUp(input) {
|
|
13061
|
+
const message = validatedInput({ id: inputIdFor(input.deliveryId), text: input.text });
|
|
12721
13062
|
let held = current(input.threadSlug, input.sessionId);
|
|
12722
13063
|
if (held && !holdsLiveDaemon(held)) {
|
|
12723
13064
|
held.client.close();
|
|
@@ -12737,7 +13078,7 @@ function createClaudeAgentBrokerBridge(deps) {
|
|
|
12737
13078
|
input.permissionMode ?? "default",
|
|
12738
13079
|
{ resume: true, appendSystemPrompt: input.appendSystemPrompt, model: input.model, effort: input.effort }
|
|
12739
13080
|
);
|
|
12740
|
-
session.client.sendInput(
|
|
13081
|
+
session.client.sendInput(message);
|
|
12741
13082
|
},
|
|
12742
13083
|
async cancelFollowUp(input) {
|
|
12743
13084
|
const held = current(input.threadSlug, input.sessionId);
|
|
@@ -12859,15 +13200,15 @@ var init_claude_agent_broker_bridge = __esm({
|
|
|
12859
13200
|
});
|
|
12860
13201
|
|
|
12861
13202
|
// packages/server/src/backend/claude-quota.ts
|
|
12862
|
-
import { join as
|
|
12863
|
-
import { homedir as
|
|
13203
|
+
import { join as join9 } from "node:path";
|
|
13204
|
+
import { homedir as homedir5, platform } from "node:os";
|
|
12864
13205
|
import { createHash as createHash5 } from "node:crypto";
|
|
12865
13206
|
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
12866
13207
|
import { execFile as execFile2 } from "node:child_process";
|
|
12867
13208
|
import { promisify as promisify2 } from "node:util";
|
|
12868
13209
|
function claudeConfigDir() {
|
|
12869
13210
|
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
12870
|
-
return override && override.trim() ? override :
|
|
13211
|
+
return override && override.trim() ? override : join9(homedir5(), ".claude");
|
|
12871
13212
|
}
|
|
12872
13213
|
function tokenFromCredentialsJson(raw2) {
|
|
12873
13214
|
let doc;
|
|
@@ -12896,7 +13237,7 @@ async function readKeychainToken() {
|
|
|
12896
13237
|
}
|
|
12897
13238
|
async function readAccessToken(configDir) {
|
|
12898
13239
|
try {
|
|
12899
|
-
const fromFile = tokenFromCredentialsJson(await readFile(
|
|
13240
|
+
const fromFile = tokenFromCredentialsJson(await readFile(join9(configDir, ".credentials.json"), "utf8"));
|
|
12900
13241
|
if (fromFile) return fromFile;
|
|
12901
13242
|
} catch {
|
|
12902
13243
|
}
|
|
@@ -12965,8 +13306,8 @@ function parseClaudeUsage(body, planType) {
|
|
|
12965
13306
|
function cachePaths(cacheDir, configDir) {
|
|
12966
13307
|
const profile = createHash5("sha256").update(configDir).digest("hex").slice(0, 12);
|
|
12967
13308
|
return {
|
|
12968
|
-
data:
|
|
12969
|
-
lock:
|
|
13309
|
+
data: join9(cacheDir, `claude-${profile}.json`),
|
|
13310
|
+
lock: join9(cacheDir, `claude-${profile}.lock`)
|
|
12970
13311
|
};
|
|
12971
13312
|
}
|
|
12972
13313
|
async function readShared(path) {
|
|
@@ -13158,7 +13499,7 @@ function refreshSharedInBackground(paths, claudeBin, deps, now) {
|
|
|
13158
13499
|
async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
|
|
13159
13500
|
const now = (deps.now ?? Date.now)();
|
|
13160
13501
|
const configDir = claudeConfigDir();
|
|
13161
|
-
const cacheDir = deps.cacheDir ??
|
|
13502
|
+
const cacheDir = deps.cacheDir ?? join9(homedir5(), ".fray", "quota-cache");
|
|
13162
13503
|
try {
|
|
13163
13504
|
await mkdir(cacheDir, { recursive: true, mode: 448 });
|
|
13164
13505
|
} catch {
|
|
@@ -13169,7 +13510,7 @@ async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
|
|
|
13169
13510
|
async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
|
|
13170
13511
|
const now = (deps.now ?? Date.now)();
|
|
13171
13512
|
const configDir = claudeConfigDir();
|
|
13172
|
-
const cacheDir = deps.cacheDir ??
|
|
13513
|
+
const cacheDir = deps.cacheDir ?? join9(homedir5(), ".fray", "quota-cache");
|
|
13173
13514
|
const paths = cachePaths(cacheDir, configDir);
|
|
13174
13515
|
try {
|
|
13175
13516
|
await mkdir(cacheDir, { recursive: true, mode: 448 });
|
|
@@ -13257,19 +13598,19 @@ var init_claude_quota = __esm({
|
|
|
13257
13598
|
});
|
|
13258
13599
|
|
|
13259
13600
|
// packages/server/src/backend/auth-status.ts
|
|
13260
|
-
import { join as
|
|
13261
|
-
import { homedir as
|
|
13601
|
+
import { join as join10 } from "node:path";
|
|
13602
|
+
import { homedir as homedir6, platform as platform2 } from "node:os";
|
|
13262
13603
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
13263
13604
|
import { execFile as execFile3 } from "node:child_process";
|
|
13264
13605
|
import { promisify as promisify3 } from "node:util";
|
|
13265
13606
|
function claudeConfigDir2() {
|
|
13266
13607
|
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
13267
|
-
return override && override.trim() ? override :
|
|
13608
|
+
return override && override.trim() ? override : join10(homedir6(), ".claude");
|
|
13268
13609
|
}
|
|
13269
13610
|
function claudeFileState(configDir) {
|
|
13270
13611
|
let raw2;
|
|
13271
13612
|
try {
|
|
13272
|
-
raw2 = readFileSync7(
|
|
13613
|
+
raw2 = readFileSync7(join10(configDir, ".credentials.json"), "utf8");
|
|
13273
13614
|
} catch (err) {
|
|
13274
13615
|
return err.code === "ENOENT" ? "absent" : "error";
|
|
13275
13616
|
}
|
|
@@ -13302,7 +13643,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
|
|
|
13302
13643
|
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
|
|
13303
13644
|
let raw2;
|
|
13304
13645
|
try {
|
|
13305
|
-
raw2 = readFileSync7(
|
|
13646
|
+
raw2 = readFileSync7(join10(codexHome, "auth.json"), "utf8");
|
|
13306
13647
|
} catch (err) {
|
|
13307
13648
|
return err.code === "ENOENT" ? "signed-out" : "unknown";
|
|
13308
13649
|
}
|
|
@@ -13392,7 +13733,7 @@ var init_auth_status = __esm({
|
|
|
13392
13733
|
// packages/server/src/fray.ts
|
|
13393
13734
|
import { execFile as execFile4 } from "node:child_process";
|
|
13394
13735
|
import { lstatSync, realpathSync as realpathSync4 } from "node:fs";
|
|
13395
|
-
import { basename as basename3, dirname as dirname5, join as
|
|
13736
|
+
import { basename as basename3, dirname as dirname5, join as join11, resolve as resolve5 } from "node:path";
|
|
13396
13737
|
import { promisify as promisify4 } from "node:util";
|
|
13397
13738
|
function frayScriptsDir() {
|
|
13398
13739
|
if (process.env.FRAY_SCRIPTS_DIR) return process.env.FRAY_SCRIPTS_DIR;
|
|
@@ -13401,7 +13742,7 @@ function frayScriptsDir() {
|
|
|
13401
13742
|
function directFrayRoot(projectDir) {
|
|
13402
13743
|
try {
|
|
13403
13744
|
const projectRoot = realpathSync4(projectDir);
|
|
13404
|
-
const path =
|
|
13745
|
+
const path = join11(projectRoot, ".fray");
|
|
13405
13746
|
const stat2 = lstatSync(path);
|
|
13406
13747
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
|
|
13407
13748
|
const real = realpathSync4(path);
|
|
@@ -13415,7 +13756,7 @@ function frayDirExists(projectDir) {
|
|
|
13415
13756
|
}
|
|
13416
13757
|
async function readBoard(projectDir, scriptsDir = frayScriptsDir()) {
|
|
13417
13758
|
if (!directFrayRoot(projectDir)) throw new Error("unsafe or missing .fray directory");
|
|
13418
|
-
const { stdout } = await execFileP("node", [
|
|
13759
|
+
const { stdout } = await execFileP("node", [join11(scriptsDir, "index.mjs"), "--json"], {
|
|
13419
13760
|
cwd: projectDir,
|
|
13420
13761
|
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
|
|
13421
13762
|
maxBuffer: 32 * 1024 * 1024
|
|
@@ -13440,7 +13781,7 @@ async function readBoard(projectDir, scriptsDir = frayScriptsDir()) {
|
|
|
13440
13781
|
};
|
|
13441
13782
|
}
|
|
13442
13783
|
async function runThreadUpdate(projectDir, slug, args, scriptsDir = frayScriptsDir()) {
|
|
13443
|
-
await execFileP("node", [
|
|
13784
|
+
await execFileP("node", [join11(scriptsDir, "thread-update.mjs"), slug, ...args], {
|
|
13444
13785
|
cwd: projectDir,
|
|
13445
13786
|
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
|
|
13446
13787
|
});
|
|
@@ -13455,12 +13796,12 @@ var init_fray = __esm({
|
|
|
13455
13796
|
});
|
|
13456
13797
|
|
|
13457
13798
|
// packages/server/src/session-files.ts
|
|
13458
|
-
import { lstatSync as lstatSync2, rmSync as
|
|
13799
|
+
import { lstatSync as lstatSync2, rmSync as rmSync5 } from "node:fs";
|
|
13459
13800
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
13460
|
-
import { join as
|
|
13801
|
+
import { join as join12 } from "node:path";
|
|
13461
13802
|
function systemPromptPath(sessionId) {
|
|
13462
13803
|
if (!SESSION_ID_RE.test(sessionId)) throw new Error("invalid session id");
|
|
13463
|
-
return
|
|
13804
|
+
return join12(SYSTEM_PROMPT_DIR, `${sessionId}.md`);
|
|
13464
13805
|
}
|
|
13465
13806
|
function isDirectDirectory(path) {
|
|
13466
13807
|
try {
|
|
@@ -13480,9 +13821,9 @@ function pathAbsent(path) {
|
|
|
13480
13821
|
}
|
|
13481
13822
|
function unlinkDirectChild(parent, filename) {
|
|
13482
13823
|
if (!isDirectDirectory(parent)) return pathAbsent(parent);
|
|
13483
|
-
const child =
|
|
13824
|
+
const child = join12(parent, filename);
|
|
13484
13825
|
try {
|
|
13485
|
-
|
|
13826
|
+
rmSync5(child, { force: true });
|
|
13486
13827
|
} catch {
|
|
13487
13828
|
return false;
|
|
13488
13829
|
}
|
|
@@ -13490,14 +13831,14 @@ function unlinkDirectChild(parent, filename) {
|
|
|
13490
13831
|
}
|
|
13491
13832
|
function cleanupAdoptionSessionFiles(projectDir, sessionId) {
|
|
13492
13833
|
if (!SESSION_ID_RE.test(sessionId)) return false;
|
|
13493
|
-
const frayDir =
|
|
13834
|
+
const frayDir = join12(projectDir, ".fray");
|
|
13494
13835
|
let clean = true;
|
|
13495
13836
|
if (isDirectDirectory(frayDir)) {
|
|
13496
|
-
const threads =
|
|
13837
|
+
const threads = join12(frayDir, "threads");
|
|
13497
13838
|
if (isDirectDirectory(threads)) {
|
|
13498
|
-
const child =
|
|
13839
|
+
const child = join12(threads, sessionId);
|
|
13499
13840
|
try {
|
|
13500
|
-
|
|
13841
|
+
rmSync5(child, { recursive: true, force: true });
|
|
13501
13842
|
} catch {
|
|
13502
13843
|
clean = false;
|
|
13503
13844
|
}
|
|
@@ -13510,7 +13851,7 @@ var SYSTEM_PROMPT_DIR, SESSION_ID_RE;
|
|
|
13510
13851
|
var init_session_files = __esm({
|
|
13511
13852
|
"packages/server/src/session-files.ts"() {
|
|
13512
13853
|
"use strict";
|
|
13513
|
-
SYSTEM_PROMPT_DIR =
|
|
13854
|
+
SYSTEM_PROMPT_DIR = join12(tmpdir2(), "fray-sysprompts");
|
|
13514
13855
|
SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/;
|
|
13515
13856
|
}
|
|
13516
13857
|
});
|
|
@@ -13747,8 +14088,8 @@ var init_adoption_recovery = __esm({
|
|
|
13747
14088
|
});
|
|
13748
14089
|
|
|
13749
14090
|
// packages/server/src/dispatch.ts
|
|
13750
|
-
import { closeSync as closeSync5, constants, existsSync as existsSync3, fstatSync, lstatSync as lstatSync3, openSync as
|
|
13751
|
-
import { basename as basename4, join as
|
|
14091
|
+
import { closeSync as closeSync5, constants, existsSync as existsSync3, fstatSync, lstatSync as lstatSync3, openSync as openSync6, readFileSync as readFileSync8, realpathSync as realpathSync5, statSync as statSync7, writeFileSync as writeFileSync5, renameSync as renameSync5, mkdirSync as mkdirSync8, rmSync as rmSync6 } from "node:fs";
|
|
14092
|
+
import { basename as basename4, join as join13, dirname as dirname6 } from "node:path";
|
|
13752
14093
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13753
14094
|
import { createHash as createHash6, randomUUID as randomUUID11 } from "node:crypto";
|
|
13754
14095
|
function fallbackTitle(prompt) {
|
|
@@ -13768,7 +14109,7 @@ function fallbackTitle(prompt) {
|
|
|
13768
14109
|
}
|
|
13769
14110
|
function resolveSlug(frayDir, base, taken) {
|
|
13770
14111
|
base = ThreadSlug.parse(base);
|
|
13771
|
-
const isTaken = (slug) => existsSync3(
|
|
14112
|
+
const isTaken = (slug) => existsSync3(join13(frayDir, `${slug}.md`)) || (taken?.(slug) ?? false);
|
|
13772
14113
|
if (!isTaken(base)) return base;
|
|
13773
14114
|
for (let n = 2; ; n++) {
|
|
13774
14115
|
const suffix = `-${n}`;
|
|
@@ -13785,12 +14126,12 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
13785
14126
|
if (!parsed.success) return null;
|
|
13786
14127
|
try {
|
|
13787
14128
|
const projectRoot = realpathSync5(projectDir);
|
|
13788
|
-
const frayPath =
|
|
14129
|
+
const frayPath = join13(projectRoot, ".fray");
|
|
13789
14130
|
const frayStat = lstatSync3(frayPath);
|
|
13790
14131
|
if (!frayStat.isDirectory() || frayStat.isSymbolicLink()) return null;
|
|
13791
14132
|
const realFray = realpathSync5(frayPath);
|
|
13792
14133
|
if (dirname6(realFray) !== projectRoot || basename4(realFray) !== ".fray") return null;
|
|
13793
|
-
const path =
|
|
14134
|
+
const path = join13(realFray, `${parsed.data}.md`);
|
|
13794
14135
|
const before = lstatSync3(path);
|
|
13795
14136
|
if (!before.isFile() || before.isSymbolicLink()) return null;
|
|
13796
14137
|
const realPath = realpathSync5(path);
|
|
@@ -13798,7 +14139,7 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
13798
14139
|
let contents;
|
|
13799
14140
|
let openedBefore;
|
|
13800
14141
|
let openedAfter;
|
|
13801
|
-
const fd =
|
|
14142
|
+
const fd = openSync6(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
13802
14143
|
try {
|
|
13803
14144
|
openedBefore = fstatSync(fd);
|
|
13804
14145
|
contents = readFileSync8(fd);
|
|
@@ -13834,9 +14175,9 @@ function boardAuthorizesAdoption(board, slug) {
|
|
|
13834
14175
|
return !board.errorItems.some((item) => item.file === `${slug}.md`);
|
|
13835
14176
|
}
|
|
13836
14177
|
function ensureSafeDirectDirectory(parent, name) {
|
|
13837
|
-
const path =
|
|
14178
|
+
const path = join13(parent, name);
|
|
13838
14179
|
try {
|
|
13839
|
-
|
|
14180
|
+
mkdirSync8(path);
|
|
13840
14181
|
} catch (error) {
|
|
13841
14182
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
13842
14183
|
if (code !== "EEXIST") throw error;
|
|
@@ -13907,14 +14248,14 @@ function writeScratchpad(projectDir, sessionId, title, kind = "claude") {
|
|
|
13907
14248
|
const threadsDir = ensureSafeDirectDirectory(frayDir, "threads");
|
|
13908
14249
|
const dir = ensureSafeDirectDirectory(threadsDir, sessionId);
|
|
13909
14250
|
const rel = scratchpadRelPath(sessionId);
|
|
13910
|
-
const path =
|
|
13911
|
-
const tmp =
|
|
14251
|
+
const path = join13(dir, "scratch.md");
|
|
14252
|
+
const tmp = join13(dir, ".scratch.tmp");
|
|
13912
14253
|
try {
|
|
13913
|
-
|
|
14254
|
+
writeFileSync5(tmp, scratchpadContent(title, kind), { flag: "wx", mode: 384 });
|
|
13914
14255
|
if (existsSync3(path)) throw new Error("scratchpad already exists");
|
|
13915
14256
|
renameSync5(tmp, path);
|
|
13916
14257
|
} catch (error) {
|
|
13917
|
-
|
|
14258
|
+
rmSync6(tmp, { force: true });
|
|
13918
14259
|
throw error;
|
|
13919
14260
|
}
|
|
13920
14261
|
return rel;
|
|
@@ -13932,8 +14273,9 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
13932
14273
|
}
|
|
13933
14274
|
]
|
|
13934
14275
|
});
|
|
13935
|
-
const bashBackgroundHook =
|
|
13936
|
-
const scratchpadStopHook =
|
|
14276
|
+
const bashBackgroundHook = join13(dirname6(hookScript), "bash-background.mjs");
|
|
14277
|
+
const scratchpadStopHook = join13(dirname6(hookScript), "scratchpad-stop.mjs");
|
|
14278
|
+
const fenceStopHook = join13(dirname6(hookScript), "fence-stop.mjs");
|
|
13937
14279
|
return {
|
|
13938
14280
|
bypass_hook_trust: true,
|
|
13939
14281
|
hooks: {
|
|
@@ -13948,12 +14290,19 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
13948
14290
|
}]
|
|
13949
14291
|
}],
|
|
13950
14292
|
// An optional scratchpad-frontmatter reminder can keep a worker from forgetting owned work when
|
|
13951
|
-
// it tries to rest
|
|
14293
|
+
// it tries to rest, and the fence check catches a rest that hands a decision back in prose
|
|
14294
|
+
// instead of a ```question card. Each hook persists its own anti-loop guard.
|
|
13952
14295
|
Stop: [{
|
|
13953
|
-
hooks: [
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
14296
|
+
hooks: [
|
|
14297
|
+
{
|
|
14298
|
+
type: "command",
|
|
14299
|
+
command: `node ${JSON.stringify(scratchpadStopHook)} --session=${JSON.stringify(sessionId)}`
|
|
14300
|
+
},
|
|
14301
|
+
{
|
|
14302
|
+
type: "command",
|
|
14303
|
+
command: `node ${JSON.stringify(fenceStopHook)} --session=${JSON.stringify(sessionId)}`
|
|
14304
|
+
}
|
|
14305
|
+
]
|
|
13957
14306
|
}],
|
|
13958
14307
|
// Native Codex children inherit the root scratchpad mandate even with `fork_turns:"none"`.
|
|
13959
14308
|
// Constrain it structurally at child start: shared writes, but only merge-style scoped edits.
|
|
@@ -13966,10 +14315,10 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
13966
14315
|
}
|
|
13967
14316
|
function scratchpadHookScript() {
|
|
13968
14317
|
const plugin = workerPluginDir();
|
|
13969
|
-
return plugin ?
|
|
14318
|
+
return plugin ? join13(plugin, "hooks", "scratchpad.mjs") : void 0;
|
|
13970
14319
|
}
|
|
13971
14320
|
function composePrompt(sessionId, prompt, kind = "claude") {
|
|
13972
|
-
const scratch = kind === "codex" ? `Your scratchpad is \`.fray/threads/${sessionId}/scratch.md\` \u2014
|
|
14321
|
+
const scratch = kind === "codex" ? `Your scratchpad is \`.fray/threads/${sessionId}/scratch.md\` \u2014 an OPTIONAL scratch file kept for you, not a deliverable. A single direct task usually needs nothing in it, and writing in it never substitutes for doing the work. On a long effort it is useful crash insurance and a shared progress document for native sub-agents: keep the approach, what you rejected, and the human's decisions in it as you go, mid-work, then keep working; re-read it after any compaction or resume before asserting anything. Each native sub-agent should merge its own scoped progress into it rather than leaving the root as its sole writer, but must re-read before each edit, preserve all existing content, and never delete, truncate, reinitialize, move, or replace the whole file.` : `Your scratchpad is \`.fray/threads/${sessionId}/scratch.md\` \u2014 an OPTIONAL scratch file kept for you, not a deliverable. A single direct task usually needs nothing in it, and writing in it never substitutes for doing the work. On a long effort it is useful crash insurance and the shared blackboard for your sub-agents: keep the approach, what you rejected, and the human's decisions in it as you go, mid-work, then keep working; re-read it after any compaction or resume, and pass its path to every sub-agent you dispatch. Each sub-agent should merge its own scoped progress into it rather than leaving the root as its sole writer, but must re-read before each edit, preserve all existing content, and never delete, truncate, reinitialize, move, or replace the whole file.`;
|
|
13973
14322
|
const handoff = "\n\nEverything above the banner below is fray system orientation. Everything below it is the human operator's own prompt, verbatim \u2014 that, and nothing else, is your task.";
|
|
13974
14323
|
return `${scratch}${handoff}
|
|
13975
14324
|
|
|
@@ -13977,16 +14326,16 @@ function composePrompt(sessionId, prompt, kind = "claude") {
|
|
|
13977
14326
|
${DISPATCH_TASK_BANNER_MARKER}${prompt}`;
|
|
13978
14327
|
}
|
|
13979
14328
|
function scratchpadOrientation(sessionId, planPath, kind = "claude") {
|
|
13980
|
-
const scratch = kind === "codex" ? `SCRATCHPAD: .fray/threads/${sessionId}/scratch.md \u2014
|
|
14329
|
+
const scratch = kind === "codex" ? `SCRATCHPAD (optional): .fray/threads/${sessionId}/scratch.md \u2014 a scratch file kept FOR YOU, not a deliverable. A single direct task usually needs nothing in it; writing in it is never a substitute for doing the work. Useful on a long effort as crash insurance, and a shared progress document for native sub-agents (write your task list, the approach and what you rejected, and anything that must outlive your context there, as you go; re-read it after any compaction or resume). Each native sub-agent should merge its own scoped progress into it rather than leaving the root as its sole writer, but must re-read before each edit, preserve all existing content, and never delete, truncate, reinitialize, move, or replace the whole file.` : `SCRATCHPAD (optional): .fray/threads/${sessionId}/scratch.md \u2014 a scratch file kept FOR YOU, not a deliverable. A single direct task usually needs nothing in it; writing in it is never a substitute for doing the work. Useful on a long effort as crash insurance, and the shared blackboard for your sub-agents (write your task list, the approach and what you rejected, and anything that must outlive your context there, as you go; re-read it after any compaction or resume; pass this path in every sub-agent prompt). Each sub-agent should merge its own scoped progress into it rather than leaving the root as its sole writer, but must re-read before each edit, preserve all existing content, and never delete, truncate, reinitialize, move, or replace the whole file.`;
|
|
13981
14330
|
const lines = [scratch];
|
|
13982
14331
|
if (planPath) lines.push(`PLAN: ${planPath} \u2014 the durable plan artifact this thread works from; read it FIRST.`);
|
|
13983
14332
|
return lines.join("\n");
|
|
13984
14333
|
}
|
|
13985
14334
|
function frayConfigBlock(projectDir) {
|
|
13986
|
-
const path =
|
|
14335
|
+
const path = join13(projectDir, "FRAY.md");
|
|
13987
14336
|
let body;
|
|
13988
14337
|
try {
|
|
13989
|
-
const st =
|
|
14338
|
+
const st = statSync7(path);
|
|
13990
14339
|
if (!st.isFile() || st.size > FRAY_MD_MAX_BYTES) return "";
|
|
13991
14340
|
body = readFileSync8(path, "utf8").trim();
|
|
13992
14341
|
} catch {
|
|
@@ -14002,7 +14351,7 @@ ${clipped}`;
|
|
|
14002
14351
|
}
|
|
14003
14352
|
function validPlanPath(projectDir, planPath) {
|
|
14004
14353
|
if (!planPath || !PLAN_PATH_RE.test(planPath)) return null;
|
|
14005
|
-
return existsSync3(
|
|
14354
|
+
return existsSync3(join13(projectDir, planPath)) ? planPath : null;
|
|
14006
14355
|
}
|
|
14007
14356
|
function workerPermissionMode(m) {
|
|
14008
14357
|
return m === "plan" ? "auto" : m;
|
|
@@ -14014,15 +14363,15 @@ function effectivePermissionMode(kind, mode) {
|
|
|
14014
14363
|
}
|
|
14015
14364
|
function systemPromptFlags(sessionId, system) {
|
|
14016
14365
|
if (!system) return [];
|
|
14017
|
-
|
|
14366
|
+
mkdirSync8(SYSTEM_PROMPT_DIR, { recursive: true });
|
|
14018
14367
|
const path = systemPromptPath(sessionId);
|
|
14019
|
-
|
|
14368
|
+
writeFileSync5(path, system);
|
|
14020
14369
|
return ["--append-system-prompt-file", path];
|
|
14021
14370
|
}
|
|
14022
14371
|
function resolveFrayMcp(stateDir, moduleUrl = import.meta.url, env = process.env) {
|
|
14023
14372
|
const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
|
|
14024
14373
|
if (!pluginDir) return void 0;
|
|
14025
|
-
const scriptPath =
|
|
14374
|
+
const scriptPath = join13(pluginDir, "bin", FRAY_MCP.script);
|
|
14026
14375
|
if (!existsSync3(scriptPath)) return void 0;
|
|
14027
14376
|
return { scriptPath, stateDir };
|
|
14028
14377
|
}
|
|
@@ -14060,12 +14409,12 @@ function buildClaudeCommand(opts) {
|
|
|
14060
14409
|
}
|
|
14061
14410
|
function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
|
|
14062
14411
|
const override = env.FRAY_WORKER_PLUGIN_DIR;
|
|
14063
|
-
if (override && existsSync3(
|
|
14412
|
+
if (override && existsSync3(join13(override, ".claude-plugin", "plugin.json")))
|
|
14064
14413
|
return override;
|
|
14065
14414
|
let current = dirname6(fileURLToPath2(moduleUrl));
|
|
14066
14415
|
for (; ; ) {
|
|
14067
|
-
const candidate =
|
|
14068
|
-
if (existsSync3(
|
|
14416
|
+
const candidate = join13(current, "cc-worker");
|
|
14417
|
+
if (existsSync3(join13(candidate, ".claude-plugin", "plugin.json"))) return candidate;
|
|
14069
14418
|
const parent = dirname6(current);
|
|
14070
14419
|
if (parent === current) return void 0;
|
|
14071
14420
|
current = parent;
|
|
@@ -14109,7 +14458,7 @@ function createDispatcher(deps) {
|
|
|
14109
14458
|
const killPane2 = deps.killPane ?? killPane;
|
|
14110
14459
|
const killSession2 = deps.killSession ?? killSession;
|
|
14111
14460
|
const readBoardSource = deps.readBoard ?? readBoard;
|
|
14112
|
-
const frayDir =
|
|
14461
|
+
const frayDir = join13(deps.project.dir, ".fray");
|
|
14113
14462
|
const adoptionRuntime = deps.adoptionRuntime ?? {
|
|
14114
14463
|
lookupAdoptionPane,
|
|
14115
14464
|
findAdoptionPane,
|
|
@@ -14149,14 +14498,14 @@ function createDispatcher(deps) {
|
|
|
14149
14498
|
}
|
|
14150
14499
|
function writePrewrites(built) {
|
|
14151
14500
|
for (const file of built.prewrite) {
|
|
14152
|
-
if (file.mode === void 0)
|
|
14153
|
-
else
|
|
14501
|
+
if (file.mode === void 0) writeFileSync5(file.path, file.contents);
|
|
14502
|
+
else writeFileSync5(file.path, file.contents, { mode: file.mode });
|
|
14154
14503
|
}
|
|
14155
14504
|
}
|
|
14156
14505
|
function cleanupPrewrites(built) {
|
|
14157
14506
|
for (const path of new Set(built.prewrite.map((file) => file.path))) {
|
|
14158
14507
|
try {
|
|
14159
|
-
|
|
14508
|
+
rmSync6(path, { force: true });
|
|
14160
14509
|
} catch {
|
|
14161
14510
|
}
|
|
14162
14511
|
}
|
|
@@ -14164,7 +14513,7 @@ function createDispatcher(deps) {
|
|
|
14164
14513
|
function cleanupDispatchFiles(scratchRel, built, sessionId) {
|
|
14165
14514
|
cleanupPrewrites(built);
|
|
14166
14515
|
try {
|
|
14167
|
-
|
|
14516
|
+
rmSync6(join13(deps.project.dir, scratchRel), { force: true });
|
|
14168
14517
|
} catch {
|
|
14169
14518
|
}
|
|
14170
14519
|
cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
|
|
@@ -14952,10 +15301,10 @@ var init_permission_controller = __esm({
|
|
|
14952
15301
|
});
|
|
14953
15302
|
|
|
14954
15303
|
// packages/server/src/backend/codex-models.ts
|
|
14955
|
-
import { join as
|
|
15304
|
+
import { join as join14 } from "node:path";
|
|
14956
15305
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
14957
15306
|
function cachePath(codexHome) {
|
|
14958
|
-
return
|
|
15307
|
+
return join14(codexHome, "models_cache.json");
|
|
14959
15308
|
}
|
|
14960
15309
|
function toCodexModel(raw2) {
|
|
14961
15310
|
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
@@ -15717,7 +16066,7 @@ var init_delivery_ledger = __esm({
|
|
|
15717
16066
|
});
|
|
15718
16067
|
|
|
15719
16068
|
// packages/server/src/codex-subagents.ts
|
|
15720
|
-
import { closeSync as closeSync6, openSync as
|
|
16069
|
+
import { closeSync as closeSync6, openSync as openSync7, readSync as readSync2, statSync as statSync8 } from "node:fs";
|
|
15721
16070
|
function createCodexSubAgentTracker(deps) {
|
|
15722
16071
|
const findRollouts = deps.findRollouts ?? ((ids) => findRolloutsByIds(ids, deps.codexHome));
|
|
15723
16072
|
const readAppended = deps.readAppended ?? defaultReadAppended;
|
|
@@ -15900,7 +16249,7 @@ function agentLabel(path) {
|
|
|
15900
16249
|
function defaultReadAppended(path, offset) {
|
|
15901
16250
|
let size;
|
|
15902
16251
|
try {
|
|
15903
|
-
size =
|
|
16252
|
+
size = statSync8(path).size;
|
|
15904
16253
|
} catch {
|
|
15905
16254
|
return void 0;
|
|
15906
16255
|
}
|
|
@@ -15908,7 +16257,7 @@ function defaultReadAppended(path, offset) {
|
|
|
15908
16257
|
const from = restarted ? 0 : offset;
|
|
15909
16258
|
if (size <= from) return { text: "", offset: from, restarted };
|
|
15910
16259
|
try {
|
|
15911
|
-
const fd =
|
|
16260
|
+
const fd = openSync7(path, "r");
|
|
15912
16261
|
try {
|
|
15913
16262
|
const buf = Buffer.allocUnsafe(size - from);
|
|
15914
16263
|
const read = readSync2(fd, buf, 0, buf.length, from);
|
|
@@ -16011,15 +16360,15 @@ var init_completion_relay = __esm({
|
|
|
16011
16360
|
|
|
16012
16361
|
// packages/server/src/tail-cache.ts
|
|
16013
16362
|
import { createHash as createHash7 } from "node:crypto";
|
|
16014
|
-
import { closeSync as closeSync7, openSync as
|
|
16015
|
-
import { join as
|
|
16363
|
+
import { closeSync as closeSync7, openSync as openSync8, readFileSync as readFileSync10, readSync as readSync3, statSync as statSync9 } from "node:fs";
|
|
16364
|
+
import { join as join15 } from "node:path";
|
|
16016
16365
|
function foldSchemaDigest(dir = import.meta.dirname) {
|
|
16017
16366
|
if (foldSchemaMemo) return foldSchemaMemo;
|
|
16018
16367
|
const hash = createHash7("sha256").update("fray-tail-state-v1\0");
|
|
16019
16368
|
let read = 0;
|
|
16020
16369
|
for (const name of FOLD_SOURCES) {
|
|
16021
16370
|
try {
|
|
16022
|
-
hash.update(name).update("\0").update(readFileSync10(
|
|
16371
|
+
hash.update(name).update("\0").update(readFileSync10(join15(dir, name)));
|
|
16023
16372
|
read++;
|
|
16024
16373
|
} catch {
|
|
16025
16374
|
hash.update(name).update("\0missing\0");
|
|
@@ -16049,9 +16398,9 @@ function measureFence(path, offset) {
|
|
|
16049
16398
|
if (!Number.isSafeInteger(offset) || offset <= 0) return null;
|
|
16050
16399
|
let fd;
|
|
16051
16400
|
try {
|
|
16052
|
-
const stat2 =
|
|
16401
|
+
const stat2 = statSync9(path);
|
|
16053
16402
|
if (stat2.size < offset) return null;
|
|
16054
|
-
fd =
|
|
16403
|
+
fd = openSync8(path, "r");
|
|
16055
16404
|
const window = Math.min(SAMPLE_BYTES, offset);
|
|
16056
16405
|
const head = Buffer.allocUnsafe(window);
|
|
16057
16406
|
const headRead = readSync3(fd, head, 0, window, 0);
|
|
@@ -16215,9 +16564,9 @@ var init_tail_cache = __esm({
|
|
|
16215
16564
|
});
|
|
16216
16565
|
|
|
16217
16566
|
// packages/server/src/tailer.ts
|
|
16218
|
-
import { statSync as
|
|
16219
|
-
import { join as
|
|
16220
|
-
import { homedir as
|
|
16567
|
+
import { statSync as statSync10, openSync as openSync9, readSync as readSync4, closeSync as closeSync8, readdirSync as readdirSync4, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6, readFileSync as readFileSync11 } from "node:fs";
|
|
16568
|
+
import { join as join16 } from "node:path";
|
|
16569
|
+
import { homedir as homedir7, tmpdir as tmpdir3 } from "node:os";
|
|
16221
16570
|
function activityMinute(at) {
|
|
16222
16571
|
if (!at) return "";
|
|
16223
16572
|
const ms = Date.parse(at);
|
|
@@ -16471,7 +16820,7 @@ function launchTaskId(text) {
|
|
|
16471
16820
|
function readDescendantSidecars(sessionDir, mtimeMs) {
|
|
16472
16821
|
let names;
|
|
16473
16822
|
try {
|
|
16474
|
-
names =
|
|
16823
|
+
names = readdirSync4(join16(sessionDir, "subagents"));
|
|
16475
16824
|
} catch {
|
|
16476
16825
|
return [];
|
|
16477
16826
|
}
|
|
@@ -16482,7 +16831,7 @@ function readDescendantSidecars(sessionDir, mtimeMs) {
|
|
|
16482
16831
|
if (!agentId) continue;
|
|
16483
16832
|
let parsed;
|
|
16484
16833
|
try {
|
|
16485
|
-
parsed = JSON.parse(readFileSync11(
|
|
16834
|
+
parsed = JSON.parse(readFileSync11(join16(sessionDir, "subagents", name), "utf8"));
|
|
16486
16835
|
} catch {
|
|
16487
16836
|
continue;
|
|
16488
16837
|
}
|
|
@@ -16496,7 +16845,7 @@ function readDescendantSidecars(sessionDir, mtimeMs) {
|
|
|
16496
16845
|
agentType: text(meta.agentType),
|
|
16497
16846
|
parentAgentId: text(meta.parentAgentId),
|
|
16498
16847
|
spawnDepth: typeof meta.spawnDepth === "number" && Number.isFinite(meta.spawnDepth) ? meta.spawnDepth : void 0,
|
|
16499
|
-
spawnedAtMs: mtimeMs(
|
|
16848
|
+
spawnedAtMs: mtimeMs(join16(sessionDir, "subagents", name))
|
|
16500
16849
|
});
|
|
16501
16850
|
}
|
|
16502
16851
|
return out;
|
|
@@ -16932,13 +17281,13 @@ function isPermMarker(v) {
|
|
|
16932
17281
|
}
|
|
16933
17282
|
function defaultMtimeMs(path) {
|
|
16934
17283
|
try {
|
|
16935
|
-
return
|
|
17284
|
+
return statSync10(path).mtimeMs;
|
|
16936
17285
|
} catch {
|
|
16937
17286
|
return void 0;
|
|
16938
17287
|
}
|
|
16939
17288
|
}
|
|
16940
17289
|
function defaultLogDir(project) {
|
|
16941
|
-
return
|
|
17290
|
+
return join16(homedir7(), ".claude", "projects", project.cwdSlug);
|
|
16942
17291
|
}
|
|
16943
17292
|
function defaultReadPermMarker(project) {
|
|
16944
17293
|
if (!project.stateDir) return () => void 0;
|
|
@@ -17087,7 +17436,7 @@ function createTailer(deps) {
|
|
|
17087
17436
|
return captured.kind === "captured" ? captured.text : "";
|
|
17088
17437
|
}
|
|
17089
17438
|
const defaultBackend = {
|
|
17090
|
-
transcriptPath: (sessionId) =>
|
|
17439
|
+
transcriptPath: (sessionId) => join16(logDir, `${sessionId}.jsonl`),
|
|
17091
17440
|
foldLine: (state, line) => {
|
|
17092
17441
|
const rec = parseLine(line);
|
|
17093
17442
|
if (rec) applyRecord(state, rec);
|
|
@@ -17189,7 +17538,7 @@ ${ask}`;
|
|
|
17189
17538
|
return state.path.replace(/\.jsonl$/, "");
|
|
17190
17539
|
}
|
|
17191
17540
|
function descendantSidecars(state) {
|
|
17192
|
-
const at = mtimeMs(
|
|
17541
|
+
const at = mtimeMs(join16(sessionDirOf(state), "subagents"));
|
|
17193
17542
|
const cached = descendantIndex.get(state.slug);
|
|
17194
17543
|
if (cached && cached.at === at) return cached.all;
|
|
17195
17544
|
const all = readDescendantSidecars(sessionDirOf(state), mtimeMs);
|
|
@@ -17203,7 +17552,7 @@ ${ask}`;
|
|
|
17203
17552
|
return descendantIndex.get(state.slug)?.byToolUse.get(id);
|
|
17204
17553
|
}
|
|
17205
17554
|
function descendantTranscript(state, meta) {
|
|
17206
|
-
return
|
|
17555
|
+
return join16(sessionDirOf(state), "subagents", `agent-${meta.agentId}.jsonl`);
|
|
17207
17556
|
}
|
|
17208
17557
|
function descendantState(state, meta) {
|
|
17209
17558
|
const task = deps.runtimeTasks?.(state.sessionId).find((entry) => entry.taskId === meta.agentId);
|
|
@@ -17428,7 +17777,7 @@ ${ask}`;
|
|
|
17428
17777
|
function scanForeign(nowMs) {
|
|
17429
17778
|
let names;
|
|
17430
17779
|
try {
|
|
17431
|
-
names =
|
|
17780
|
+
names = readdirSync4(logDir);
|
|
17432
17781
|
} catch {
|
|
17433
17782
|
return [];
|
|
17434
17783
|
}
|
|
@@ -17443,10 +17792,10 @@ ${ask}`;
|
|
|
17443
17792
|
if (name.startsWith(".") || !name.endsWith(".jsonl")) continue;
|
|
17444
17793
|
const id = name.slice(0, -".jsonl".length);
|
|
17445
17794
|
if (!id || registered.has(id)) continue;
|
|
17446
|
-
const path =
|
|
17795
|
+
const path = join16(logDir, name);
|
|
17447
17796
|
let mtime;
|
|
17448
17797
|
try {
|
|
17449
|
-
mtime =
|
|
17798
|
+
mtime = statSync10(path).mtimeMs;
|
|
17450
17799
|
} catch {
|
|
17451
17800
|
continue;
|
|
17452
17801
|
}
|
|
@@ -17636,7 +17985,7 @@ ${ask}`;
|
|
|
17636
17985
|
function consume(state, backend, onLine) {
|
|
17637
17986
|
let size;
|
|
17638
17987
|
try {
|
|
17639
|
-
size =
|
|
17988
|
+
size = statSync10(state.path).size;
|
|
17640
17989
|
} catch {
|
|
17641
17990
|
return;
|
|
17642
17991
|
}
|
|
@@ -17647,7 +17996,7 @@ ${ask}`;
|
|
|
17647
17996
|
if (size <= state.offset) return;
|
|
17648
17997
|
let chunk = "";
|
|
17649
17998
|
try {
|
|
17650
|
-
const fd =
|
|
17999
|
+
const fd = openSync9(state.path, "r");
|
|
17651
18000
|
try {
|
|
17652
18001
|
const buf = Buffer.allocUnsafe(size - state.offset);
|
|
17653
18002
|
const read = readSync4(fd, buf, 0, buf.length, state.offset);
|
|
@@ -17687,13 +18036,14 @@ ${ask}`;
|
|
|
17687
18036
|
const authFailure = row.backend !== "codex" && isClaudeAuthErrorText(pane);
|
|
17688
18037
|
if (authFailure) state.authFault = "authentication_rejected";
|
|
17689
18038
|
const detail = authFailure ? "(claude authentication failure \u2014 pane content redacted; sign in and retry)" : pane.trim() || "(pane empty / unavailable)";
|
|
17690
|
-
|
|
17691
|
-
|
|
18039
|
+
log.error(
|
|
18040
|
+
"tailer",
|
|
18041
|
+
`thread ${row.slug} (session ${row.session_id}): no transcript ${DISCOVERY_GRACE_MS / 1e3}s after dispatch \u2014 likely a boot failure. Pane:
|
|
17692
18042
|
${detail.slice(0, 4e3)}`
|
|
17693
18043
|
);
|
|
17694
18044
|
try {
|
|
17695
|
-
|
|
17696
|
-
|
|
18045
|
+
mkdirSync9(STALL_LOG_DIR, { recursive: true });
|
|
18046
|
+
writeFileSync6(join16(STALL_LOG_DIR, `${row.slug}.stall.log`), `session_id: ${row.session_id}
|
|
17697
18047
|
captured_at: ${new Date(now()).toISOString()}
|
|
17698
18048
|
|
|
17699
18049
|
${detail}
|
|
@@ -17705,7 +18055,7 @@ ${detail}
|
|
|
17705
18055
|
if (state.offset > 0) return true;
|
|
17706
18056
|
let size = 0;
|
|
17707
18057
|
try {
|
|
17708
|
-
size =
|
|
18058
|
+
size = statSync10(state.path).size;
|
|
17709
18059
|
} catch {
|
|
17710
18060
|
size = 0;
|
|
17711
18061
|
}
|
|
@@ -17732,7 +18082,7 @@ ${detail}
|
|
|
17732
18082
|
committed = false;
|
|
17733
18083
|
}
|
|
17734
18084
|
if (!committed) return false;
|
|
17735
|
-
state.path =
|
|
18085
|
+
state.path = join16(logDir, `${found}.jsonl`);
|
|
17736
18086
|
state.offset = 0;
|
|
17737
18087
|
state.partial = "";
|
|
17738
18088
|
state.primed = false;
|
|
@@ -17760,7 +18110,7 @@ ${detail}
|
|
|
17760
18110
|
let state = states.get(row.slug);
|
|
17761
18111
|
const runtimeGeneration = row.runtime_generation ?? 0;
|
|
17762
18112
|
if (!state || state.sessionId !== row.session_id || state.nativeSessionId !== nativeId || state.runtimeGeneration !== runtimeGeneration) {
|
|
17763
|
-
const path = backend.transcriptPath(nativeId) ??
|
|
18113
|
+
const path = backend.transcriptPath(nativeId) ?? join16(logDir, `${nativeId}.jsonl`);
|
|
17764
18114
|
state = newTailState(row.slug, row.session_id, path, false, nativeId, runtimeGeneration);
|
|
17765
18115
|
hydrateFromCache(state, row, nativeId);
|
|
17766
18116
|
states.set(row.slug, state);
|
|
@@ -17999,8 +18349,9 @@ ${detail}
|
|
|
17999
18349
|
if (elapsed > POLL_MS3) {
|
|
18000
18350
|
overBudgetTicks++;
|
|
18001
18351
|
if (overBudgetTicks === 1 || overBudgetTicks % 30 === 0) {
|
|
18002
|
-
|
|
18003
|
-
|
|
18352
|
+
log.warn(
|
|
18353
|
+
"tailer",
|
|
18354
|
+
`tick took ${Math.round(elapsed)}ms (poll ${POLL_MS3}ms, ${states.size} sessions) \u2014 the event loop is blocked for that long, so RPCs and board pushes are delayed (occurrence ${overBudgetTicks})`
|
|
18004
18355
|
);
|
|
18005
18356
|
}
|
|
18006
18357
|
}
|
|
@@ -18010,7 +18361,7 @@ ${detail}
|
|
|
18010
18361
|
function reportTickFailure(error) {
|
|
18011
18362
|
tickFailures++;
|
|
18012
18363
|
if (tickFailures === 1 || tickFailures % 50 === 0) {
|
|
18013
|
-
|
|
18364
|
+
log.error("tailer", `tick threw (occurrence ${tickFailures}; the loop keeps running): ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
18014
18365
|
}
|
|
18015
18366
|
}
|
|
18016
18367
|
function scheduleTick() {
|
|
@@ -18130,6 +18481,7 @@ var init_tailer = __esm({
|
|
|
18130
18481
|
init_codex_subagents();
|
|
18131
18482
|
init_completion_relay();
|
|
18132
18483
|
init_tail_cache();
|
|
18484
|
+
init_logging();
|
|
18133
18485
|
IDLE_BACKSTOP_MS = 5e3;
|
|
18134
18486
|
RUNTIME_CHASE_MAX = 20;
|
|
18135
18487
|
POLL_MS3 = 1e3;
|
|
@@ -18143,7 +18495,7 @@ var init_tailer = __esm({
|
|
|
18143
18495
|
CACHE_FLUSH_MS = 3e4;
|
|
18144
18496
|
PRIME_PROGRESS_EVERY = 20;
|
|
18145
18497
|
DISCOVER_RETRY_MS = 15e3;
|
|
18146
|
-
STALL_LOG_DIR =
|
|
18498
|
+
STALL_LOG_DIR = join16(tmpdir3(), "fray-worker-logs");
|
|
18147
18499
|
PERM_YES_OPTION = /(^|\n)\s*(❯\s*)?1\.\s+Yes\b/;
|
|
18148
18500
|
PERM_QUESTION = /\b(?:Do you want|Would you like)\b/;
|
|
18149
18501
|
PERM_FOOTER = /\bEsc to (cancel|reject)\b/;
|
|
@@ -18203,14 +18555,14 @@ var init_tailer = __esm({
|
|
|
18203
18555
|
});
|
|
18204
18556
|
|
|
18205
18557
|
// packages/server/src/backend/codex.ts
|
|
18206
|
-
import { join as
|
|
18207
|
-
import { homedir as
|
|
18208
|
-
import { readdirSync as
|
|
18558
|
+
import { join as join17 } from "node:path";
|
|
18559
|
+
import { homedir as homedir8 } from "node:os";
|
|
18560
|
+
import { readdirSync as readdirSync5, statSync as statSync11 } from "node:fs";
|
|
18209
18561
|
function defaultCodexHome() {
|
|
18210
|
-
return process.env.CODEX_HOME && process.env.CODEX_HOME.trim() ? process.env.CODEX_HOME :
|
|
18562
|
+
return process.env.CODEX_HOME && process.env.CODEX_HOME.trim() ? process.env.CODEX_HOME : join17(homedir8(), ".codex");
|
|
18211
18563
|
}
|
|
18212
18564
|
function sessionsDir(codexHome) {
|
|
18213
|
-
return
|
|
18565
|
+
return join17(codexHome, "sessions");
|
|
18214
18566
|
}
|
|
18215
18567
|
function codexSandbox(mode) {
|
|
18216
18568
|
switch (mode) {
|
|
@@ -18627,23 +18979,23 @@ function collectRollouts(dir, out, budget) {
|
|
|
18627
18979
|
const files = entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).sort(descByName);
|
|
18628
18980
|
for (const d of dirs) {
|
|
18629
18981
|
if (budget.n <= 0) return;
|
|
18630
|
-
collectRollouts(
|
|
18982
|
+
collectRollouts(join17(dir, d.name), out, budget);
|
|
18631
18983
|
}
|
|
18632
18984
|
for (const f of files) {
|
|
18633
18985
|
if (budget.n <= 0) return;
|
|
18634
18986
|
let mtimeMs;
|
|
18635
18987
|
try {
|
|
18636
|
-
mtimeMs =
|
|
18988
|
+
mtimeMs = statSync11(join17(dir, f.name)).mtimeMs;
|
|
18637
18989
|
} catch {
|
|
18638
18990
|
continue;
|
|
18639
18991
|
}
|
|
18640
|
-
out.push({ path:
|
|
18992
|
+
out.push({ path: join17(dir, f.name), mtimeMs });
|
|
18641
18993
|
budget.n--;
|
|
18642
18994
|
}
|
|
18643
18995
|
}
|
|
18644
18996
|
function safeReaddir(dir) {
|
|
18645
18997
|
try {
|
|
18646
|
-
return
|
|
18998
|
+
return readdirSync5(dir, { withFileTypes: true });
|
|
18647
18999
|
} catch {
|
|
18648
19000
|
return [];
|
|
18649
19001
|
}
|
|
@@ -22491,23 +22843,64 @@ var init_wrapper = __esm({
|
|
|
22491
22843
|
}
|
|
22492
22844
|
});
|
|
22493
22845
|
|
|
22846
|
+
// packages/server/src/backend/codex-mcp.ts
|
|
22847
|
+
function tomlString(value) {
|
|
22848
|
+
return JSON.stringify(value);
|
|
22849
|
+
}
|
|
22850
|
+
function inlineTable(entries) {
|
|
22851
|
+
return `{${entries.map(([k, v]) => `${k}=${v}`).join(",")}}`;
|
|
22852
|
+
}
|
|
22853
|
+
function serverTable(command, args, env) {
|
|
22854
|
+
const entries = [
|
|
22855
|
+
["command", tomlString(command)],
|
|
22856
|
+
["args", `[${args.map(tomlString).join(",")}]`]
|
|
22857
|
+
];
|
|
22858
|
+
if (env && Object.keys(env).length > 0) {
|
|
22859
|
+
entries.push(["env", inlineTable(Object.entries(env).map(([k, v]) => [k, tomlString(v)]))]);
|
|
22860
|
+
}
|
|
22861
|
+
return inlineTable(entries);
|
|
22862
|
+
}
|
|
22863
|
+
function codexMcpConfigArgs(frayMcp, nodeBin = process.execPath) {
|
|
22864
|
+
const args = [
|
|
22865
|
+
"-c",
|
|
22866
|
+
`mcp_servers.${CHROME_DEVTOOLS_MCP.name}=${serverTable(CHROME_DEVTOOLS_MCP.command, CHROME_DEVTOOLS_MCP.args)}`
|
|
22867
|
+
];
|
|
22868
|
+
if (frayMcp) {
|
|
22869
|
+
args.push(
|
|
22870
|
+
"-c",
|
|
22871
|
+
`mcp_servers.${FRAY_MCP.name}=${serverTable(nodeBin, [frayMcp.scriptPath], { FRAY_STATE_DIR: frayMcp.stateDir })}`
|
|
22872
|
+
);
|
|
22873
|
+
}
|
|
22874
|
+
args.push("-c", `default_tools_approval_mode=${tomlString("approve")}`);
|
|
22875
|
+
return args;
|
|
22876
|
+
}
|
|
22877
|
+
function codexAppServerArgv(transport, frayMcp) {
|
|
22878
|
+
return ["app-server", ...codexMcpConfigArgs(frayMcp), ...transport];
|
|
22879
|
+
}
|
|
22880
|
+
var init_codex_mcp = __esm({
|
|
22881
|
+
"packages/server/src/backend/codex-mcp.ts"() {
|
|
22882
|
+
"use strict";
|
|
22883
|
+
init_types2();
|
|
22884
|
+
}
|
|
22885
|
+
});
|
|
22886
|
+
|
|
22494
22887
|
// packages/server/src/backend/codex-app-server-native.ts
|
|
22495
22888
|
import { spawn as spawn3 } from "node:child_process";
|
|
22496
22889
|
import { connect } from "node:net";
|
|
22497
22890
|
import { createHash as createHash8, randomUUID as randomUUID12 } from "node:crypto";
|
|
22498
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
22499
|
-
import { join as
|
|
22891
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync10, readFileSync as readFileSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
22892
|
+
import { join as join18 } from "node:path";
|
|
22500
22893
|
import { PassThrough, Writable } from "node:stream";
|
|
22501
22894
|
import { StringDecoder } from "node:string_decoder";
|
|
22502
22895
|
function nativeDir(stateDir) {
|
|
22503
|
-
return
|
|
22896
|
+
return join18(stateDir, "codex-app-server-native");
|
|
22504
22897
|
}
|
|
22505
22898
|
function nativeRecordPath(stateDir, projectId) {
|
|
22506
|
-
return
|
|
22899
|
+
return join18(nativeDir(stateDir), `${projectId}.json`);
|
|
22507
22900
|
}
|
|
22508
22901
|
function nativeListenSocketPath(stateDir, projectId) {
|
|
22509
22902
|
const key = createHash8("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
|
|
22510
|
-
return
|
|
22903
|
+
return join18(process.env.TMPDIR ?? "/tmp", `fray-codex-native-${key}.sock`);
|
|
22511
22904
|
}
|
|
22512
22905
|
function pidAlive2(pid) {
|
|
22513
22906
|
try {
|
|
@@ -22666,7 +23059,7 @@ function attach(record, timeoutMs) {
|
|
|
22666
23059
|
}
|
|
22667
23060
|
async function startListener(options) {
|
|
22668
23061
|
const { stateDir, projectId } = options;
|
|
22669
|
-
|
|
23062
|
+
mkdirSync10(nativeDir(stateDir), { recursive: true });
|
|
22670
23063
|
const socketPath = nativeListenSocketPath(stateDir, projectId);
|
|
22671
23064
|
if (existsSync4(socketPath) && !await socketAccepting(socketPath)) {
|
|
22672
23065
|
try {
|
|
@@ -22675,7 +23068,7 @@ async function startListener(options) {
|
|
|
22675
23068
|
}
|
|
22676
23069
|
}
|
|
22677
23070
|
const generation = randomUUID12();
|
|
22678
|
-
const child = spawn3(options.codexBin, ["
|
|
23071
|
+
const child = spawn3(options.codexBin, codexAppServerArgv(["--listen", `unix://${socketPath}`], options.frayMcp), {
|
|
22679
23072
|
cwd: options.cwd,
|
|
22680
23073
|
env: options.env,
|
|
22681
23074
|
detached: true,
|
|
@@ -22689,7 +23082,7 @@ async function startListener(options) {
|
|
|
22689
23082
|
socketPath,
|
|
22690
23083
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
22691
23084
|
};
|
|
22692
|
-
|
|
23085
|
+
writeFileSync7(nativeRecordPath(stateDir, projectId), JSON.stringify(record));
|
|
22693
23086
|
const deadline = Date.now() + (options.timeoutMs ?? 3e4);
|
|
22694
23087
|
for (; ; ) {
|
|
22695
23088
|
if (existsSync4(socketPath) && await socketAccepting(socketPath)) return record;
|
|
@@ -22703,6 +23096,8 @@ var init_codex_app_server_native = __esm({
|
|
|
22703
23096
|
"packages/server/src/backend/codex-app-server-native.ts"() {
|
|
22704
23097
|
"use strict";
|
|
22705
23098
|
init_wrapper();
|
|
23099
|
+
init_codex_mcp();
|
|
23100
|
+
init_logging();
|
|
22706
23101
|
PRESUMED_LOSSY_REJOIN = 1;
|
|
22707
23102
|
nativeListenCodexAppServerHost = async (options) => {
|
|
22708
23103
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
@@ -22743,8 +23138,8 @@ var init_codex_app_server_native = __esm({
|
|
|
22743
23138
|
droppedWhileDetached: PRESUMED_LOSSY_REJOIN
|
|
22744
23139
|
};
|
|
22745
23140
|
}
|
|
22746
|
-
|
|
22747
|
-
const child = spawn3(options.codexBin, ["
|
|
23141
|
+
log.error("codex", `codex app-server native listener unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a fray restart`);
|
|
23142
|
+
const child = spawn3(options.codexBin, codexAppServerArgv(["--stdio"], options.frayMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
22748
23143
|
return { process: child, generation: randomUUID12(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0 };
|
|
22749
23144
|
}
|
|
22750
23145
|
};
|
|
@@ -22755,20 +23150,20 @@ var init_codex_app_server_native = __esm({
|
|
|
22755
23150
|
import { spawn as spawn4 } from "node:child_process";
|
|
22756
23151
|
import { createConnection } from "node:net";
|
|
22757
23152
|
import { createHash as createHash9, randomUUID as randomUUID13 } from "node:crypto";
|
|
22758
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
22759
|
-
import { join as
|
|
23153
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync11, readFileSync as readFileSync13, unlinkSync as unlinkSync3 } from "node:fs";
|
|
23154
|
+
import { join as join19 } from "node:path";
|
|
22760
23155
|
import { PassThrough as PassThrough2, Writable as Writable2 } from "node:stream";
|
|
22761
23156
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
22762
23157
|
function daemonDir(stateDir) {
|
|
22763
|
-
return
|
|
23158
|
+
return join19(stateDir, "codex-app-server");
|
|
22764
23159
|
}
|
|
22765
23160
|
function recordPath(stateDir, projectId) {
|
|
22766
|
-
return
|
|
23161
|
+
return join19(daemonDir(stateDir), `${projectId}.json`);
|
|
22767
23162
|
}
|
|
22768
23163
|
function codexAppServerSocketPath(stateDir, projectId) {
|
|
22769
23164
|
const key = createHash9("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
|
|
22770
23165
|
if (process.platform === "win32") return `\\\\.\\pipe\\fray-codex-${key}`;
|
|
22771
|
-
return
|
|
23166
|
+
return join19(process.env.TMPDIR ?? "/tmp", `fray-codex-${key}.sock`);
|
|
22772
23167
|
}
|
|
22773
23168
|
function pidAlive3(pid) {
|
|
22774
23169
|
try {
|
|
@@ -22852,7 +23247,7 @@ async function stopCodexAppServerDaemon(stateDir, projectId, timeoutMs = 1e4) {
|
|
|
22852
23247
|
}
|
|
22853
23248
|
function forkDaemon(options) {
|
|
22854
23249
|
const { stateDir, projectId } = options;
|
|
22855
|
-
|
|
23250
|
+
mkdirSync11(daemonDir(stateDir), { recursive: true });
|
|
22856
23251
|
const record = recordPath(stateDir, projectId);
|
|
22857
23252
|
try {
|
|
22858
23253
|
unlinkSync3(record);
|
|
@@ -22872,6 +23267,10 @@ function forkDaemon(options) {
|
|
|
22872
23267
|
generation,
|
|
22873
23268
|
clientInfo: options.clientInfo,
|
|
22874
23269
|
capabilities: options.capabilities,
|
|
23270
|
+
// The argv is built HERE, not in the daemon: the daemon is a bare forked entry that should not have
|
|
23271
|
+
// to resolve fray's MCP descriptors, and building it once keeps this transport identical to the
|
|
23272
|
+
// native listener's.
|
|
23273
|
+
appServerArgs: codexAppServerArgv(["--stdio"], options.frayMcp),
|
|
22875
23274
|
...options.reachabilityCheckMs === void 0 ? {} : { reachabilityCheckMs: options.reachabilityCheckMs }
|
|
22876
23275
|
});
|
|
22877
23276
|
const child = spawn4(process.execPath, [options.daemonEntry ?? daemonEntry()], {
|
|
@@ -22982,7 +23381,7 @@ function attach2(record, timeoutMs) {
|
|
|
22982
23381
|
});
|
|
22983
23382
|
}
|
|
22984
23383
|
function inProcessCodexAppServer(options) {
|
|
22985
|
-
const child = spawn4(options.codexBin, ["
|
|
23384
|
+
const child = spawn4(options.codexBin, codexAppServerArgv(["--stdio"], options.frayMcp), {
|
|
22986
23385
|
cwd: options.cwd,
|
|
22987
23386
|
env: options.env,
|
|
22988
23387
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -22991,7 +23390,7 @@ function inProcessCodexAppServer(options) {
|
|
|
22991
23390
|
}
|
|
22992
23391
|
function directChildHost(spawnChild, generationId = randomUUID13) {
|
|
22993
23392
|
return async (options) => ({
|
|
22994
|
-
process: spawnChild(options.codexBin, ["
|
|
23393
|
+
process: spawnChild(options.codexBin, codexAppServerArgv(["--stdio"], options.frayMcp), { cwd: options.cwd, env: options.env }),
|
|
22995
23394
|
generation: generationId(),
|
|
22996
23395
|
reattached: false,
|
|
22997
23396
|
daemonPid: process.pid,
|
|
@@ -23004,6 +23403,8 @@ var init_codex_app_server_host = __esm({
|
|
|
23004
23403
|
"use strict";
|
|
23005
23404
|
init_detached_daemons();
|
|
23006
23405
|
init_codex_app_server_native();
|
|
23406
|
+
init_codex_mcp();
|
|
23407
|
+
init_logging();
|
|
23007
23408
|
daemonEntry = () => resolveDetachedDaemonEntry(import.meta.url, "codex-app-server-daemon");
|
|
23008
23409
|
daemonCodexAppServerHost = async (options) => {
|
|
23009
23410
|
const existing = liveDaemonRecord(options.stateDir, options.projectId);
|
|
@@ -23033,7 +23434,7 @@ var init_codex_app_server_host = __esm({
|
|
|
23033
23434
|
const attached = await attach2(record, timeoutMs);
|
|
23034
23435
|
return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid };
|
|
23035
23436
|
} catch (error) {
|
|
23036
|
-
|
|
23437
|
+
log.error("codex", `codex app-server daemon unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a fray restart`);
|
|
23037
23438
|
return inProcessCodexAppServer(options);
|
|
23038
23439
|
}
|
|
23039
23440
|
};
|
|
@@ -23597,6 +23998,7 @@ var init_codex_app_server = __esm({
|
|
|
23597
23998
|
init_credential_redaction();
|
|
23598
23999
|
init_codex_app_server_host();
|
|
23599
24000
|
init_codex_app_server_native();
|
|
24001
|
+
init_logging();
|
|
23600
24002
|
CODEX_APP_SERVER_PROVIDER = "codex-app-server";
|
|
23601
24003
|
CODEX_NATIVE_LISTEN_FLAG = "FRAY_CODEX_NATIVE_LISTEN";
|
|
23602
24004
|
CODEX_APP_SERVER_SUPPORTED_VERSION = "0.146.0";
|
|
@@ -24922,7 +25324,8 @@ var init_codex_app_server = __esm({
|
|
|
24922
25324
|
// environment; no value is copied into argv, SQLite, diagnostics, or logs.
|
|
24923
25325
|
env: codexAppServerEnvironment(),
|
|
24924
25326
|
clientInfo: CLIENT_INFO,
|
|
24925
|
-
capabilities: CLIENT_CAPABILITIES
|
|
25327
|
+
capabilities: CLIENT_CAPABILITIES,
|
|
25328
|
+
frayMcp: this.options.frayMcp
|
|
24926
25329
|
});
|
|
24927
25330
|
const child = attachment.process;
|
|
24928
25331
|
let connection;
|
|
@@ -24961,7 +25364,7 @@ var init_codex_app_server = __esm({
|
|
|
24961
25364
|
});
|
|
24962
25365
|
if (version !== aheadVersionWarned) {
|
|
24963
25366
|
aheadVersionWarned = version;
|
|
24964
|
-
|
|
25367
|
+
log.warn("codex", verdict.message);
|
|
24965
25368
|
}
|
|
24966
25369
|
}
|
|
24967
25370
|
handshaking = false;
|
|
@@ -25832,17 +26235,17 @@ var init_codex_app_server = __esm({
|
|
|
25832
26235
|
});
|
|
25833
26236
|
|
|
25834
26237
|
// packages/server/src/backend/codex-quota.ts
|
|
25835
|
-
import { join as
|
|
25836
|
-
import { readdirSync as
|
|
26238
|
+
import { join as join20 } from "node:path";
|
|
26239
|
+
import { readdirSync as readdirSync6, statSync as statSync12, openSync as openSync10, readSync as readSync5, fstatSync as fstatSync2, closeSync as closeSync9 } from "node:fs";
|
|
25837
26240
|
import { spawn as spawn5 } from "node:child_process";
|
|
25838
26241
|
function sessionsDir2(codexHome) {
|
|
25839
|
-
return
|
|
26242
|
+
return join20(codexHome, "sessions");
|
|
25840
26243
|
}
|
|
25841
26244
|
function newestRollouts(dir, out, budget) {
|
|
25842
26245
|
if (budget.n <= 0) return;
|
|
25843
26246
|
let entries;
|
|
25844
26247
|
try {
|
|
25845
|
-
entries =
|
|
26248
|
+
entries = readdirSync6(dir, { withFileTypes: true });
|
|
25846
26249
|
} catch {
|
|
25847
26250
|
return;
|
|
25848
26251
|
}
|
|
@@ -25850,18 +26253,18 @@ function newestRollouts(dir, out, budget) {
|
|
|
25850
26253
|
const files = entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
|
|
25851
26254
|
for (const d of dirs) {
|
|
25852
26255
|
if (budget.n <= 0) return;
|
|
25853
|
-
newestRollouts(
|
|
26256
|
+
newestRollouts(join20(dir, d), out, budget);
|
|
25854
26257
|
}
|
|
25855
26258
|
for (const f of files) {
|
|
25856
26259
|
if (budget.n <= 0) return;
|
|
25857
|
-
out.push(
|
|
26260
|
+
out.push(join20(dir, f));
|
|
25858
26261
|
budget.n--;
|
|
25859
26262
|
}
|
|
25860
26263
|
}
|
|
25861
26264
|
function readTail(path) {
|
|
25862
26265
|
let fd;
|
|
25863
26266
|
try {
|
|
25864
|
-
fd =
|
|
26267
|
+
fd = openSync10(path, "r");
|
|
25865
26268
|
const size = fstatSync2(fd).size;
|
|
25866
26269
|
const start = Math.max(0, size - TAIL_BYTES);
|
|
25867
26270
|
const len = size - start;
|
|
@@ -25926,7 +26329,7 @@ function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
|
|
|
25926
26329
|
newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
|
|
25927
26330
|
const byMtime = rollouts.map((path) => {
|
|
25928
26331
|
try {
|
|
25929
|
-
return { path, mtimeMs:
|
|
26332
|
+
return { path, mtimeMs: statSync12(path).mtimeMs };
|
|
25930
26333
|
} catch {
|
|
25931
26334
|
return void 0;
|
|
25932
26335
|
}
|
|
@@ -26102,7 +26505,7 @@ async function readQuota(opts = {}) {
|
|
|
26102
26505
|
if (fx && process.env.NODE_ENV !== "production") {
|
|
26103
26506
|
const snap = await fixture(fx);
|
|
26104
26507
|
if (snap) {
|
|
26105
|
-
|
|
26508
|
+
log.warn("quota", `serving FIXTURE "${fx}" (FRAY_QUOTA_FIXTURE set; dev-only seam)`);
|
|
26106
26509
|
return snap;
|
|
26107
26510
|
}
|
|
26108
26511
|
}
|
|
@@ -26117,6 +26520,7 @@ var init_quota = __esm({
|
|
|
26117
26520
|
"use strict";
|
|
26118
26521
|
init_codex_quota();
|
|
26119
26522
|
init_claude_quota();
|
|
26523
|
+
init_logging();
|
|
26120
26524
|
}
|
|
26121
26525
|
});
|
|
26122
26526
|
|
|
@@ -26126,20 +26530,20 @@ import {
|
|
|
26126
26530
|
constants as constants2,
|
|
26127
26531
|
fstatSync as fstatSync3,
|
|
26128
26532
|
lstatSync as lstatSync4,
|
|
26129
|
-
openSync as
|
|
26533
|
+
openSync as openSync11,
|
|
26130
26534
|
readFileSync as readFileSync14,
|
|
26131
|
-
readdirSync as
|
|
26535
|
+
readdirSync as readdirSync7,
|
|
26132
26536
|
realpathSync as realpathSync6,
|
|
26133
26537
|
unlinkSync as unlinkSync4
|
|
26134
26538
|
} from "node:fs";
|
|
26135
26539
|
import { createHash as createHash11 } from "node:crypto";
|
|
26136
|
-
import { basename as basename5, dirname as dirname7, join as
|
|
26540
|
+
import { basename as basename5, dirname as dirname7, join as join21 } from "node:path";
|
|
26137
26541
|
function sameStat(a, b) {
|
|
26138
26542
|
return a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.size === b.size && a.mtimeMs === b.mtimeMs && a.ctimeMs === b.ctimeMs;
|
|
26139
26543
|
}
|
|
26140
26544
|
function directDirectory(parentRealPath, name) {
|
|
26141
26545
|
try {
|
|
26142
|
-
const path =
|
|
26546
|
+
const path = join21(parentRealPath, name);
|
|
26143
26547
|
const stat2 = lstatSync4(path);
|
|
26144
26548
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
|
|
26145
26549
|
const realPath = realpathSync6(path);
|
|
@@ -26177,7 +26581,7 @@ function resolvePlanFile(projectDir, value, hooks = {}) {
|
|
|
26177
26581
|
const directoryBefore = planDirectory(projectDir);
|
|
26178
26582
|
if (!directoryBefore) return null;
|
|
26179
26583
|
hooks.afterDirectoryCheck?.();
|
|
26180
|
-
const path =
|
|
26584
|
+
const path = join21(directoryBefore.plans.realPath, filename);
|
|
26181
26585
|
const before = lstatSync4(path);
|
|
26182
26586
|
if (!before.isFile() || before.isSymbolicLink()) return null;
|
|
26183
26587
|
const realPath = realpathSync6(path);
|
|
@@ -26186,7 +26590,7 @@ function resolvePlanFile(projectDir, value, hooks = {}) {
|
|
|
26186
26590
|
let contents;
|
|
26187
26591
|
let openedBefore;
|
|
26188
26592
|
let openedAfter;
|
|
26189
|
-
const fd =
|
|
26593
|
+
const fd = openSync11(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
26190
26594
|
try {
|
|
26191
26595
|
openedBefore = fstatSync3(fd);
|
|
26192
26596
|
contents = readFileSync14(fd);
|
|
@@ -26231,7 +26635,7 @@ function listPlanFiles(projectDir, hooks = {}) {
|
|
|
26231
26635
|
if (!directoryBefore) return [];
|
|
26232
26636
|
try {
|
|
26233
26637
|
hooks.afterDirectoryCheck?.();
|
|
26234
|
-
const names =
|
|
26638
|
+
const names = readdirSync7(directoryBefore.plans.realPath).sort();
|
|
26235
26639
|
const files = names.flatMap((filename) => {
|
|
26236
26640
|
const relativePath = `.fray/plans/${filename}`;
|
|
26237
26641
|
if (!filenameFromPlanPath(relativePath)) return [];
|
|
@@ -26256,7 +26660,7 @@ import {
|
|
|
26256
26660
|
existsSync as existsSync6,
|
|
26257
26661
|
watch as fsWatch
|
|
26258
26662
|
} from "node:fs";
|
|
26259
|
-
import { join as
|
|
26663
|
+
import { join as join22 } from "node:path";
|
|
26260
26664
|
import watcher from "@parcel/watcher";
|
|
26261
26665
|
function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
|
|
26262
26666
|
if (!liveness) return false;
|
|
@@ -26357,7 +26761,7 @@ function deriveAwaitingBackground(row, tele, runtime, hasActionableInteraction =
|
|
|
26357
26761
|
}
|
|
26358
26762
|
function scratchpadPathIfExists(projectDir, sessionId) {
|
|
26359
26763
|
const rel = scratchpadRelPath(sessionId);
|
|
26360
|
-
return existsSync6(
|
|
26764
|
+
return existsSync6(join22(projectDir, rel)) ? rel : void 0;
|
|
26361
26765
|
}
|
|
26362
26766
|
function resolveSessionProfile(row, tele) {
|
|
26363
26767
|
const persistedModel = row.model?.trim() || void 0;
|
|
@@ -26716,7 +27120,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
26716
27120
|
if (parcelSub || stopped) return Promise.resolve();
|
|
26717
27121
|
if (watchSetup) return watchSetup;
|
|
26718
27122
|
const setup = (async () => {
|
|
26719
|
-
const next = await subscribe(
|
|
27123
|
+
const next = await subscribe(join22(project.dir, ".fray"), () => scheduleRebuild());
|
|
26720
27124
|
if (stopped) {
|
|
26721
27125
|
await next.unsubscribe();
|
|
26722
27126
|
return;
|
|
@@ -27514,7 +27918,7 @@ function createScheduler(deps) {
|
|
|
27514
27918
|
const autoResumeOnLimit = deps.autoResumeOnLimit ?? (() => true);
|
|
27515
27919
|
const fetchPr = deps.fetchPr ?? defaultFetchPr;
|
|
27516
27920
|
const fetchGithubReview = deps.fetchGithubReview ?? createGithubReviewFetcher({ now });
|
|
27517
|
-
const
|
|
27921
|
+
const log2 = deps.log ?? ((m) => log.info("scheduler", m));
|
|
27518
27922
|
const tickMs = deps.tickMs ?? 1e4;
|
|
27519
27923
|
const pollMs = deps.pollMs ?? 6e4;
|
|
27520
27924
|
const deliveryLeaseMs = Math.max(1, deps.deliveryLeaseMs ?? 3e4);
|
|
@@ -27744,14 +28148,14 @@ function createScheduler(deps) {
|
|
|
27744
28148
|
return;
|
|
27745
28149
|
}
|
|
27746
28150
|
const suppressed = prior?.suppressed ? ` (${prior.suppressed} identical repeats suppressed)` : "";
|
|
27747
|
-
|
|
28151
|
+
log2(`waker: GitHub review check failed for ${key} (${slug}) [${result.failure.kind}] \u2014 ${result.failure.message}${suppressed}`);
|
|
27748
28152
|
reviewFailures.set(key, { signature, loggedAt: at, suppressed: 0 });
|
|
27749
28153
|
}
|
|
27750
28154
|
function recordReviewSuccess(key, slug) {
|
|
27751
28155
|
const prior = reviewFailures.get(key);
|
|
27752
28156
|
if (!prior) return;
|
|
27753
28157
|
const suppressed = prior.suppressed ? `; ${prior.suppressed} identical repeats were suppressed` : "";
|
|
27754
|
-
|
|
28158
|
+
log2(`waker: GitHub review check recovered for ${key} (${slug})${suppressed}`);
|
|
27755
28159
|
reviewFailures.delete(key);
|
|
27756
28160
|
}
|
|
27757
28161
|
async function evalThread(slug, sessionId, fence, nowMs, fenceAt) {
|
|
@@ -27799,9 +28203,9 @@ function createScheduler(deps) {
|
|
|
27799
28203
|
try {
|
|
27800
28204
|
const s = await fetchPr(ref);
|
|
27801
28205
|
if (s) st.prCache.set(k, s);
|
|
27802
|
-
else
|
|
28206
|
+
else log2(`waker: gh check skipped for ${k} (${slug}) \u2014 gh unavailable / not authed / rate-limited`);
|
|
27803
28207
|
} catch (err) {
|
|
27804
|
-
|
|
28208
|
+
log2(`waker: gh check errored for ${k} (${slug}): ${err instanceof Error ? err.message : String(err)}`);
|
|
27805
28209
|
}
|
|
27806
28210
|
}),
|
|
27807
28211
|
...[...reviewRefs].map(async ([k, ref]) => {
|
|
@@ -27846,7 +28250,7 @@ function createScheduler(deps) {
|
|
|
27846
28250
|
}, nowMs).delivery;
|
|
27847
28251
|
st.fired = true;
|
|
27848
28252
|
if (isPrWatchHint(h.kind)) deps.storage.setSnoozedUntil(slug, null);
|
|
27849
|
-
|
|
28253
|
+
log2(`waker: queued ${slug} \u2014 ${verdict.reason}`);
|
|
27850
28254
|
checkpoint("after-enqueue", item);
|
|
27851
28255
|
return;
|
|
27852
28256
|
}
|
|
@@ -27897,7 +28301,7 @@ function createScheduler(deps) {
|
|
|
27897
28301
|
try {
|
|
27898
28302
|
quota = await deps.readQuota();
|
|
27899
28303
|
} catch (err) {
|
|
27900
|
-
|
|
28304
|
+
log2(`waker: quota read failed while checking limit resumes: ${err instanceof Error ? err.message : String(err)}`);
|
|
27901
28305
|
}
|
|
27902
28306
|
}
|
|
27903
28307
|
for (const c of candidates) {
|
|
@@ -27914,7 +28318,7 @@ function createScheduler(deps) {
|
|
|
27914
28318
|
message: limitResumeSteer(c.fault.window),
|
|
27915
28319
|
reason: `${c.fault.window} usage limit reset (interrupted ${c.fault.at})`
|
|
27916
28320
|
}, nowMs).delivery;
|
|
27917
|
-
|
|
28321
|
+
log2(`waker: queued ${c.slug} \u2014 ${item.reason}`);
|
|
27918
28322
|
checkpoint("after-enqueue", item);
|
|
27919
28323
|
}
|
|
27920
28324
|
}
|
|
@@ -27937,7 +28341,7 @@ function createScheduler(deps) {
|
|
|
27937
28341
|
message: relayMessage(report),
|
|
27938
28342
|
reason: `sub-agent report never reached the model (${report.summary ?? report.taskId})`
|
|
27939
28343
|
}, nowMs).delivery;
|
|
27940
|
-
|
|
28344
|
+
log2(`waker: queued ${row.slug} \u2014 ${item.reason}`);
|
|
27941
28345
|
checkpoint("after-enqueue", item);
|
|
27942
28346
|
}
|
|
27943
28347
|
}
|
|
@@ -27958,7 +28362,7 @@ function createScheduler(deps) {
|
|
|
27958
28362
|
message: armed.prompt,
|
|
27959
28363
|
reason: `snooze elapsed (${armed.until})`
|
|
27960
28364
|
}, nowMs).delivery;
|
|
27961
|
-
|
|
28365
|
+
log2(`waker: queued ${row.slug} \u2014 ${item.reason}`);
|
|
27962
28366
|
checkpoint("after-enqueue", item);
|
|
27963
28367
|
}
|
|
27964
28368
|
}
|
|
@@ -27993,7 +28397,7 @@ function createScheduler(deps) {
|
|
|
27993
28397
|
);
|
|
27994
28398
|
if (recovered?.state === "exhausted") {
|
|
27995
28399
|
settleSnooze(item);
|
|
27996
|
-
|
|
28400
|
+
log2(`waker: delivery EXHAUSTED for ${item.slug} after ${recovered.attempts} attempts \u2014 ${recovered.lastError ?? "unknown error"}`);
|
|
27997
28401
|
}
|
|
27998
28402
|
}
|
|
27999
28403
|
}
|
|
@@ -28033,7 +28437,7 @@ function createScheduler(deps) {
|
|
|
28033
28437
|
if (error?.terminalDelivery === true) {
|
|
28034
28438
|
outbox.supersede(item.id, failedAt, message);
|
|
28035
28439
|
settleSnooze(item);
|
|
28036
|
-
|
|
28440
|
+
log2(`waker: delivery ABANDONED for ${item.slug} (terminal, no retry): ${message}`);
|
|
28037
28441
|
continue;
|
|
28038
28442
|
}
|
|
28039
28443
|
outbox.deferFailure(
|
|
@@ -28043,14 +28447,14 @@ function createScheduler(deps) {
|
|
|
28043
28447
|
failedAt + Math.max(deliveryLeaseMs, retryDelay(item.attempts)),
|
|
28044
28448
|
message
|
|
28045
28449
|
);
|
|
28046
|
-
|
|
28450
|
+
log2(`waker: delivery FAILED for ${item.slug} (attempt ${item.attempts} of ${maxDeliveryAttempts}): ${message}`);
|
|
28047
28451
|
continue;
|
|
28048
28452
|
}
|
|
28049
28453
|
const retried = item.attempts > 1 ? ` (on attempt ${item.attempts})` : "";
|
|
28050
|
-
|
|
28454
|
+
log2(`waker: delivered ${item.slug} \u2014 ${item.reason}${retried}`);
|
|
28051
28455
|
checkpoint("after-delivery", item);
|
|
28052
28456
|
if (!outbox.acknowledge(item.id, deliveryOwner, now())) {
|
|
28053
|
-
|
|
28457
|
+
log2(`waker: delivery acknowledgement lost ownership for ${item.slug}; preserving the authoritative terminal state`);
|
|
28054
28458
|
continue;
|
|
28055
28459
|
}
|
|
28056
28460
|
settleSnooze(item);
|
|
@@ -28076,7 +28480,7 @@ function createScheduler(deps) {
|
|
|
28076
28480
|
await evalThread(row.slug, row.session_id, fence, nowMs, fenceAt);
|
|
28077
28481
|
} catch (err) {
|
|
28078
28482
|
if (err instanceof InjectedSchedulerCrash) throw err;
|
|
28079
|
-
|
|
28483
|
+
log2(`waker: tick error for ${row.slug}: ${err instanceof Error ? err.message : String(err)}`);
|
|
28080
28484
|
}
|
|
28081
28485
|
}));
|
|
28082
28486
|
const crashed = candidateResults.find((result) => result.status === "rejected");
|
|
@@ -28093,19 +28497,19 @@ function createScheduler(deps) {
|
|
|
28093
28497
|
await evalLimits(now());
|
|
28094
28498
|
} catch (err) {
|
|
28095
28499
|
if (err instanceof InjectedSchedulerCrash) throw err;
|
|
28096
|
-
|
|
28500
|
+
log2(`waker: limit-resume pass failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28097
28501
|
}
|
|
28098
28502
|
try {
|
|
28099
28503
|
evalSnoozes(now());
|
|
28100
28504
|
} catch (err) {
|
|
28101
28505
|
if (err instanceof InjectedSchedulerCrash) throw err;
|
|
28102
|
-
|
|
28506
|
+
log2(`waker: snooze-bump pass failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28103
28507
|
}
|
|
28104
28508
|
try {
|
|
28105
28509
|
repairDroppedReports(now());
|
|
28106
28510
|
} catch (err) {
|
|
28107
28511
|
if (err instanceof InjectedSchedulerCrash) throw err;
|
|
28108
|
-
|
|
28512
|
+
log2(`waker: report-repair pass failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28109
28513
|
}
|
|
28110
28514
|
reconcileOutbox(now());
|
|
28111
28515
|
await deliverDue();
|
|
@@ -28129,9 +28533,9 @@ function createScheduler(deps) {
|
|
|
28129
28533
|
start() {
|
|
28130
28534
|
if (timer) return;
|
|
28131
28535
|
if (stopped) throw new ProducerStoppedError("wake scheduler");
|
|
28132
|
-
void tick().catch((error) =>
|
|
28536
|
+
void tick().catch((error) => log2(`waker: tick failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
28133
28537
|
timer = setInterval(() => {
|
|
28134
|
-
void tick().catch((error) =>
|
|
28538
|
+
void tick().catch((error) => log2(`waker: tick failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
28135
28539
|
}, tickMs);
|
|
28136
28540
|
timer.unref?.();
|
|
28137
28541
|
},
|
|
@@ -28156,6 +28560,7 @@ var init_scheduler = __esm({
|
|
|
28156
28560
|
init_completion_relay();
|
|
28157
28561
|
init_github_review();
|
|
28158
28562
|
init_github_review();
|
|
28563
|
+
init_logging();
|
|
28159
28564
|
execFileAsync5 = promisify6(execFile6);
|
|
28160
28565
|
PR_REF_RE = /(?:https?:\/\/github\.com\/)?([A-Za-z0-9][\w.-]*)\/([A-Za-z0-9][\w.-]*?)(?:\/pull\/|\/pulls\/|#)(\d+)/;
|
|
28161
28566
|
LIMIT_FENCE_PREFIX = "limit";
|
|
@@ -28175,7 +28580,7 @@ var init_scheduler = __esm({
|
|
|
28175
28580
|
});
|
|
28176
28581
|
|
|
28177
28582
|
// packages/server/src/resume.ts
|
|
28178
|
-
import { writeFileSync as
|
|
28583
|
+
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
28179
28584
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
28180
28585
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
28181
28586
|
function permissionModeForRow(row, settings) {
|
|
@@ -28216,7 +28621,7 @@ function spawnPinnedSession(deps, tx, row, permissionMode, message, options, lau
|
|
|
28216
28621
|
env: claudeWorkerEnvironment(),
|
|
28217
28622
|
prewrite: []
|
|
28218
28623
|
};
|
|
28219
|
-
for (const f of built.prewrite)
|
|
28624
|
+
for (const f of built.prewrite) writeFileSync8(f.path, f.contents);
|
|
28220
28625
|
return tx.spawn(row.slug, built.argv, deps.project.dir, {
|
|
28221
28626
|
...built.env,
|
|
28222
28627
|
FRAY_UI_THREAD: row.slug,
|
|
@@ -29235,7 +29640,7 @@ var init_resume = __esm({
|
|
|
29235
29640
|
});
|
|
29236
29641
|
|
|
29237
29642
|
// packages/server/src/backend/claude.ts
|
|
29238
|
-
import { join as
|
|
29643
|
+
import { join as join23 } from "node:path";
|
|
29239
29644
|
function toolResultText2(content) {
|
|
29240
29645
|
if (typeof content === "string") return content;
|
|
29241
29646
|
if (!Array.isArray(content)) return "";
|
|
@@ -29324,7 +29729,7 @@ function createClaudeBackend(opts) {
|
|
|
29324
29729
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
29325
29730
|
},
|
|
29326
29731
|
transcriptPath(sessionId) {
|
|
29327
|
-
return
|
|
29732
|
+
return join23(opts.logDir, `${sessionId}.jsonl`);
|
|
29328
29733
|
},
|
|
29329
29734
|
parseLine(line) {
|
|
29330
29735
|
return parseClaudeLine(line);
|
|
@@ -30295,27 +30700,27 @@ var init_profile_controller = __esm({
|
|
|
30295
30700
|
});
|
|
30296
30701
|
|
|
30297
30702
|
// packages/server/src/backend/codex-app-server-diagnostics.ts
|
|
30298
|
-
import { appendFileSync as
|
|
30299
|
-
import { join as
|
|
30703
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync12, renameSync as renameSync6, statSync as statSync13 } from "node:fs";
|
|
30704
|
+
import { join as join24 } from "node:path";
|
|
30300
30705
|
function codexDiagnosticLogPath(stateDir, projectId) {
|
|
30301
|
-
return
|
|
30706
|
+
return join24(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
|
|
30302
30707
|
}
|
|
30303
30708
|
function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
|
|
30304
|
-
const dir =
|
|
30709
|
+
const dir = join24(stateDir, "codex-app-server");
|
|
30305
30710
|
const path = codexDiagnosticLogPath(stateDir, projectId);
|
|
30306
30711
|
let ensured = false;
|
|
30307
30712
|
return (event) => {
|
|
30308
30713
|
if (event.event === "stderr") return;
|
|
30309
30714
|
try {
|
|
30310
30715
|
if (!ensured) {
|
|
30311
|
-
|
|
30716
|
+
mkdirSync12(dir, { recursive: true });
|
|
30312
30717
|
ensured = true;
|
|
30313
30718
|
}
|
|
30314
30719
|
try {
|
|
30315
|
-
if (
|
|
30720
|
+
if (statSync13(path).size > MAX_BYTES2) renameSync6(path, `${path}.1`);
|
|
30316
30721
|
} catch {
|
|
30317
30722
|
}
|
|
30318
|
-
|
|
30723
|
+
appendFileSync3(path, `${JSON.stringify({ at: now().toISOString(), ...event })}
|
|
30319
30724
|
`);
|
|
30320
30725
|
} catch {
|
|
30321
30726
|
}
|
|
@@ -30331,15 +30736,15 @@ var init_codex_app_server_diagnostics = __esm({
|
|
|
30331
30736
|
|
|
30332
30737
|
// packages/server/src/backend/stale-socket-sweep.ts
|
|
30333
30738
|
import { execFile as execFile8 } from "node:child_process";
|
|
30334
|
-
import { readdirSync as
|
|
30739
|
+
import { readdirSync as readdirSync8, unlinkSync as unlinkSync5 } from "node:fs";
|
|
30335
30740
|
import { connect as connectSocket } from "node:net";
|
|
30336
|
-
import { join as
|
|
30741
|
+
import { join as join25 } from "node:path";
|
|
30337
30742
|
function sweepStaleSockets(options, deps = {}) {
|
|
30338
30743
|
if (process.platform === "win32") return;
|
|
30339
30744
|
const keep = new Set(options.keep ?? []);
|
|
30340
30745
|
let candidates;
|
|
30341
30746
|
try {
|
|
30342
|
-
candidates = (deps.readdir ??
|
|
30747
|
+
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join25(options.dir, name)).filter((path) => !keep.has(path));
|
|
30343
30748
|
} catch {
|
|
30344
30749
|
return;
|
|
30345
30750
|
}
|
|
@@ -30652,7 +31057,7 @@ var init_orphan_reaper = __esm({
|
|
|
30652
31057
|
});
|
|
30653
31058
|
|
|
30654
31059
|
// packages/server/src/context.ts
|
|
30655
|
-
import { join as
|
|
31060
|
+
import { join as join26 } from "node:path";
|
|
30656
31061
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
30657
31062
|
function reconcileSessions(storage) {
|
|
30658
31063
|
for (const row of storage.allSessions()) {
|
|
@@ -30805,7 +31210,7 @@ function createContextUnchecked(opts, resources) {
|
|
|
30805
31210
|
projectId: project.id,
|
|
30806
31211
|
projectDir: project.dir
|
|
30807
31212
|
}, project.tmuxSocketManaged !== false);
|
|
30808
|
-
const dbPath =
|
|
31213
|
+
const dbPath = join26(project.stateDir, "ui.db");
|
|
30809
31214
|
const storage = createStorage(dbPath);
|
|
30810
31215
|
resources.storage = storage;
|
|
30811
31216
|
const bus = new Bus();
|
|
@@ -30868,7 +31273,7 @@ function createContextUnchecked(opts, resources) {
|
|
|
30868
31273
|
contextUnsubscribers.push(() => clearInterval(quotaRefreshTimer));
|
|
30869
31274
|
}
|
|
30870
31275
|
if (!process.env.FRAY_ORPHAN_REAPER_OFF) {
|
|
30871
|
-
contextUnsubscribers.push(startOrphanReaper({ log: (m) =>
|
|
31276
|
+
contextUnsubscribers.push(startOrphanReaper({ log: (m) => log.info("reaper", m) }));
|
|
30872
31277
|
}
|
|
30873
31278
|
opts.startup?.afterPhase?.("orphan reaper");
|
|
30874
31279
|
reconcileSessions(storage);
|
|
@@ -30883,6 +31288,9 @@ function createContextUnchecked(opts, resources) {
|
|
|
30883
31288
|
// The detached app-server daemon's socket + record live under the project state dir, so a
|
|
30884
31289
|
// later fray generation can find the app-server this one left running.
|
|
30885
31290
|
stateDir: project.stateDir,
|
|
31291
|
+
// Codex's MCP servers mount PROCESS-wide on the app-server, not per thread, so the descriptor
|
|
31292
|
+
// is resolved once here — the codex twin of the per-dispatch resolveFrayMcp on the claude side.
|
|
31293
|
+
frayMcp: resolveFrayMcp(project.stateDir),
|
|
30886
31294
|
dbPath,
|
|
30887
31295
|
interactions: storage.interactions,
|
|
30888
31296
|
codexBin: opts.codexBin,
|
|
@@ -30952,7 +31360,7 @@ function createContextUnchecked(opts, resources) {
|
|
|
30952
31360
|
// per death rather than a card nobody asked for.
|
|
30953
31361
|
onDiagnostic: (slug, _sessionId, diagnostic) => {
|
|
30954
31362
|
if (diagnostic.kind !== "lifecycle" || diagnostic.phase !== "crashed") return;
|
|
30955
|
-
|
|
31363
|
+
log.warn("broker", `claude broker ${slug}: ${diagnostic.message ?? "died without a recorded cause"}`);
|
|
30956
31364
|
}
|
|
30957
31365
|
}) : void 0;
|
|
30958
31366
|
resources.claudeBroker = claudeBroker;
|
|
@@ -31174,6 +31582,7 @@ var init_context = __esm({
|
|
|
31174
31582
|
init_adoption_recovery();
|
|
31175
31583
|
init_orphan_reaper();
|
|
31176
31584
|
init_shutdown();
|
|
31585
|
+
init_logging();
|
|
31177
31586
|
CONTEXT_STARTUP_CLEANUP_TIMEOUT_MS = 4e3;
|
|
31178
31587
|
QUOTA_REFRESH_INTERVAL_MS = 2 * 6e4;
|
|
31179
31588
|
ContextStartupError = class extends Error {
|
|
@@ -33827,7 +34236,7 @@ var init_server = __esm({
|
|
|
33827
34236
|
});
|
|
33828
34237
|
|
|
33829
34238
|
// packages/server/src/repair.ts
|
|
33830
|
-
import { existsSync as existsSync7, readFileSync as readFileSync15, writeFileSync as
|
|
34239
|
+
import { existsSync as existsSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
33831
34240
|
import { basename as basename6, dirname as dirname9, resolve as resolve7 } from "node:path";
|
|
33832
34241
|
function quoteValue(v) {
|
|
33833
34242
|
if (/^[\w./#:+-]+$/.test(v)) return v;
|
|
@@ -33858,7 +34267,7 @@ function repairThreadFile(frayDir, file) {
|
|
|
33858
34267
|
"---",
|
|
33859
34268
|
""
|
|
33860
34269
|
].join("\n");
|
|
33861
|
-
|
|
34270
|
+
writeFileSync9(abs, frontmatter + content);
|
|
33862
34271
|
return { slug };
|
|
33863
34272
|
}
|
|
33864
34273
|
var RepairError, REPAIR_STATUS_TEXT;
|
|
@@ -34009,11 +34418,11 @@ var init_bash_background = __esm({
|
|
|
34009
34418
|
});
|
|
34010
34419
|
|
|
34011
34420
|
// packages/server/src/transcript.ts
|
|
34012
|
-
import { closeSync as closeSync11, existsSync as existsSync8, fstatSync as fstatSync4, mkdirSync as
|
|
34421
|
+
import { closeSync as closeSync11, existsSync as existsSync8, fstatSync as fstatSync4, mkdirSync as mkdirSync13, openSync as openSync12, readdirSync as readdirSync9, readFileSync as readFileSync17, readSync as readSync6, renameSync as renameSync7, statSync as statSync14, unlinkSync as unlinkSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
34013
34422
|
import { createHash as createHash13 } from "node:crypto";
|
|
34014
34423
|
import { StringDecoder as StringDecoder4 } from "node:string_decoder";
|
|
34015
|
-
import { join as
|
|
34016
|
-
import { homedir as
|
|
34424
|
+
import { join as join27 } from "node:path";
|
|
34425
|
+
import { homedir as homedir9, tmpdir as tmpdir4 } from "node:os";
|
|
34017
34426
|
function isInjectedNoise(text) {
|
|
34018
34427
|
const t = text.trimStart();
|
|
34019
34428
|
return NOISE_PREFIXES.some((p) => t.startsWith(p));
|
|
@@ -34028,7 +34437,7 @@ function compactionLabel(preTokens, postTokens) {
|
|
|
34028
34437
|
return shrank ? `Context compacted \u2014 ${formatTokens(preTokens)} \u2192 ${formatTokens(postTokens)} tokens` : "Context compacted";
|
|
34029
34438
|
}
|
|
34030
34439
|
function compactionMessage(sourceId, at, preTokens, postTokens) {
|
|
34031
|
-
return { sourceId, role: "assistant", kind: "event", boundary:
|
|
34440
|
+
return { sourceId, role: "assistant", kind: "event", boundary: "compaction", text: compactionLabel(preTokens, postTokens), tools: [], parts: [], at };
|
|
34032
34441
|
}
|
|
34033
34442
|
function normalizeNewlines(s) {
|
|
34034
34443
|
return stripDeliveryMarkers(s).replace(/\r\n?/g, "\n");
|
|
@@ -34071,7 +34480,9 @@ function userProjection(text, first) {
|
|
|
34071
34480
|
const peer = parseAgentMessage(text);
|
|
34072
34481
|
if (peer) return { displayText: peer.body, peerFrom: peer.from };
|
|
34073
34482
|
const displayText2 = userDisplayText(text, first);
|
|
34074
|
-
return { ...displayText2 ? { displayText: displayText2 } : {}
|
|
34483
|
+
if (!isWakeDelivery(text)) return { ...displayText2 ? { displayText: displayText2 } : {} };
|
|
34484
|
+
const steer = parseGithubWakeSteer(displayText2 ?? text);
|
|
34485
|
+
return { ...displayText2 ? { displayText: displayText2 } : {}, wake: true, ...steer ? { wakeSteer: steer } : {} };
|
|
34075
34486
|
}
|
|
34076
34487
|
function pushTextPart(m, text) {
|
|
34077
34488
|
const last = m.parts[m.parts.length - 1];
|
|
@@ -34134,6 +34545,7 @@ function createTranscriptFold(identityPrefix = "claude") {
|
|
|
34134
34545
|
const backgroundShells = /* @__PURE__ */ new Map();
|
|
34135
34546
|
const backgroundTaskIds = /* @__PURE__ */ new Map();
|
|
34136
34547
|
const childDispatchIds = /* @__PURE__ */ new Map();
|
|
34548
|
+
const dispatchLabels = /* @__PURE__ */ new Map();
|
|
34137
34549
|
let prevTs;
|
|
34138
34550
|
let thinkingMsgId = null;
|
|
34139
34551
|
const queuedPending = [];
|
|
@@ -34247,14 +34659,13 @@ function createTranscriptFold(identityPrefix = "claude") {
|
|
|
34247
34659
|
if (entry) {
|
|
34248
34660
|
resolveQueued(prompt);
|
|
34249
34661
|
if (dispatchId && entry.message.peerFrom) entry.message.peerDispatchId = dispatchId;
|
|
34250
|
-
const
|
|
34251
|
-
const described = dispatched?.call.detail?.trim();
|
|
34662
|
+
const described = dispatchId ? dispatchLabels.get(dispatchId) : void 0;
|
|
34252
34663
|
if (described && entry.message.peerFrom) entry.message.peerFrom = described;
|
|
34253
34664
|
if (thisTs) prevTs = thisTs;
|
|
34254
34665
|
lastAssistantId = null;
|
|
34255
34666
|
} else {
|
|
34256
34667
|
const parsed = prompt.trim() ? parseAgentMessage(prompt) : void 0;
|
|
34257
|
-
const describedHere =
|
|
34668
|
+
const describedHere = dispatchId ? dispatchLabels.get(dispatchId) : void 0;
|
|
34258
34669
|
const from = describedHere || str(peerOrigin.from) || str(peerOrigin.name) || parsed?.from || "";
|
|
34259
34670
|
const body = parsed?.body ?? (typeof peerOrigin.body === "string" ? peerOrigin.body : "");
|
|
34260
34671
|
if (from && body.trim()) {
|
|
@@ -34351,9 +34762,10 @@ ${block.text}` : block.text;
|
|
|
34351
34762
|
pushToolPart(m, call);
|
|
34352
34763
|
m.tools.push(call);
|
|
34353
34764
|
if (call.agentId) agentDispatches.set(call.agentId, { at: rec.timestamp, call });
|
|
34765
|
+
if (call.agentId && call.detail?.trim()) dispatchLabels.set(call.agentId, call.detail.trim());
|
|
34354
34766
|
if (call.sendTo && call.sendTo !== "main") {
|
|
34355
34767
|
const dispatchId = childDispatchIds.get(call.sendTo);
|
|
34356
|
-
const described = dispatchId ?
|
|
34768
|
+
const described = dispatchId ? dispatchLabels.get(dispatchId) : void 0;
|
|
34357
34769
|
if (dispatchId) call.sendDispatchId = dispatchId;
|
|
34358
34770
|
if (described) call.sendTargetLabel = described;
|
|
34359
34771
|
}
|
|
@@ -34503,18 +34915,18 @@ function looksLikeImage(buf, ext) {
|
|
|
34503
34915
|
}
|
|
34504
34916
|
function pruneScreenshotCache() {
|
|
34505
34917
|
try {
|
|
34506
|
-
const entries =
|
|
34918
|
+
const entries = readdirSync9(SCREENSHOT_CACHE_DIR).filter((n) => !n.startsWith("."));
|
|
34507
34919
|
if (entries.length <= SCREENSHOT_CACHE_MAX) return;
|
|
34508
34920
|
const byMtime = entries.map((n) => {
|
|
34509
34921
|
try {
|
|
34510
|
-
return { n, m:
|
|
34922
|
+
return { n, m: statSync14(join27(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
|
|
34511
34923
|
} catch {
|
|
34512
34924
|
return { n, m: 0 };
|
|
34513
34925
|
}
|
|
34514
34926
|
}).sort((a, b) => b.m - a.m);
|
|
34515
34927
|
for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
|
|
34516
34928
|
try {
|
|
34517
|
-
unlinkSync6(
|
|
34929
|
+
unlinkSync6(join27(SCREENSHOT_CACHE_DIR, n));
|
|
34518
34930
|
} catch {
|
|
34519
34931
|
}
|
|
34520
34932
|
}
|
|
@@ -34544,15 +34956,15 @@ function persistBase64Image(mediaType, data, idKey) {
|
|
|
34544
34956
|
const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
|
|
34545
34957
|
if (!ext || !data) return void 0;
|
|
34546
34958
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
34547
|
-
const path =
|
|
34959
|
+
const path = join27(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
|
|
34548
34960
|
try {
|
|
34549
34961
|
if (existsSync8(path)) return path;
|
|
34550
34962
|
if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
|
|
34551
34963
|
const buf = Buffer.from(data, "base64");
|
|
34552
34964
|
if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
|
|
34553
|
-
|
|
34554
|
-
const tmp =
|
|
34555
|
-
|
|
34965
|
+
mkdirSync13(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
34966
|
+
const tmp = join27(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
34967
|
+
writeFileSync10(tmp, buf);
|
|
34556
34968
|
renameSync7(tmp, path);
|
|
34557
34969
|
pruneScreenshotCache();
|
|
34558
34970
|
return path;
|
|
@@ -34566,15 +34978,15 @@ function persistSentFile(srcPath, idKey) {
|
|
|
34566
34978
|
const outExt = ext === "jpeg" ? "jpg" : ext;
|
|
34567
34979
|
try {
|
|
34568
34980
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
34569
|
-
const dest =
|
|
34981
|
+
const dest = join27(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
|
|
34570
34982
|
if (existsSync8(dest)) return dest;
|
|
34571
|
-
const size =
|
|
34983
|
+
const size = statSync14(srcPath).size;
|
|
34572
34984
|
if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
|
|
34573
34985
|
const buf = readFileSync17(srcPath);
|
|
34574
34986
|
if (!looksLikeImage(buf, outExt)) return void 0;
|
|
34575
|
-
|
|
34576
|
-
const tmp =
|
|
34577
|
-
|
|
34987
|
+
mkdirSync13(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
34988
|
+
const tmp = join27(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
34989
|
+
writeFileSync10(tmp, buf);
|
|
34578
34990
|
renameSync7(tmp, dest);
|
|
34579
34991
|
pruneScreenshotCache();
|
|
34580
34992
|
return dest;
|
|
@@ -34874,7 +35286,7 @@ function completionEvents(rec, dispatches, backgroundShells, backgroundTaskIds)
|
|
|
34874
35286
|
const elapsedMs2 = elapsedBetween(shell.at, rec.timestamp);
|
|
34875
35287
|
shell.call.status = status === "completed" ? "completed" : status === "killed" ? "cancelled" : "failed";
|
|
34876
35288
|
if (elapsedMs2 !== void 0) shell.call.durationMs = elapsedMs2;
|
|
34877
|
-
out.push({ role: "assistant", kind: "event", boundary:
|
|
35289
|
+
out.push({ role: "assistant", kind: "event", boundary: "wake", text: backgroundWakeLabel(shell.call, status, block), tools: [], parts: [], at });
|
|
34878
35290
|
continue;
|
|
34879
35291
|
}
|
|
34880
35292
|
dispatches.delete(id);
|
|
@@ -34922,11 +35334,11 @@ function retainedFoldEntry(path, identityPrefix, fileId, size) {
|
|
|
34922
35334
|
return { entry, hit };
|
|
34923
35335
|
}
|
|
34924
35336
|
function readTranscript(project, sessionId) {
|
|
34925
|
-
const path =
|
|
35337
|
+
const path = join27(homedir9(), ".claude", "projects", project.cwdSlug, `${sessionId}.jsonl`);
|
|
34926
35338
|
const identityPrefix = `claude:${sessionId}`;
|
|
34927
35339
|
let fd;
|
|
34928
35340
|
try {
|
|
34929
|
-
fd =
|
|
35341
|
+
fd = openSync12(path, "r");
|
|
34930
35342
|
const st = fstatSync4(fd);
|
|
34931
35343
|
const size = st.size;
|
|
34932
35344
|
const fileId = `${st.dev}:${st.ino}:${Math.trunc(st.birthtimeMs)}`;
|
|
@@ -34970,7 +35382,7 @@ function verifyIncrementalParse(path, identityPrefix, incremental) {
|
|
|
34970
35382
|
}
|
|
34971
35383
|
}
|
|
34972
35384
|
function logDirOf(project) {
|
|
34973
|
-
return
|
|
35385
|
+
return join27(homedir9(), ".claude", "projects", project.cwdSlug);
|
|
34974
35386
|
}
|
|
34975
35387
|
function projectCodexTranscript(raw2, identityPrefix = "codex") {
|
|
34976
35388
|
const out = [];
|
|
@@ -35876,7 +36288,7 @@ function sourceForThread(project, storage, slug, backendFor) {
|
|
|
35876
36288
|
if (row) {
|
|
35877
36289
|
const backend = row.backend === "codex" ? "codex" : "claude";
|
|
35878
36290
|
const nativeId = backend === "codex" ? row.agent_session_id ?? row.session_id : row.transcript_id ?? row.session_id;
|
|
35879
|
-
const path = backend === "codex" ? (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(nativeId) :
|
|
36291
|
+
const path = backend === "codex" ? (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(nativeId) : join27(logDirOf(project), `${nativeId}.jsonl`);
|
|
35880
36292
|
if (!path) return void 0;
|
|
35881
36293
|
return {
|
|
35882
36294
|
slug,
|
|
@@ -35894,7 +36306,7 @@ function sourceForThread(project, storage, slug, backendFor) {
|
|
|
35894
36306
|
nativeId: slug,
|
|
35895
36307
|
backend: "claude",
|
|
35896
36308
|
runtimeGeneration: 0,
|
|
35897
|
-
path:
|
|
36309
|
+
path: join27(logDirOf(project), `${slug}.jsonl`)
|
|
35898
36310
|
};
|
|
35899
36311
|
}
|
|
35900
36312
|
function discoveredClaudeSource(project, storage, slug, expectedNativeId) {
|
|
@@ -35915,13 +36327,13 @@ function discoveredClaudeSource(project, storage, slug, expectedNativeId) {
|
|
|
35915
36327
|
nativeId,
|
|
35916
36328
|
backend: "claude",
|
|
35917
36329
|
runtimeGeneration: row.runtime_generation ?? 0,
|
|
35918
|
-
path:
|
|
36330
|
+
path: join27(logDirOf(project), `${nativeId}.jsonl`)
|
|
35919
36331
|
};
|
|
35920
36332
|
}
|
|
35921
36333
|
function fixedSnapshot(source) {
|
|
35922
36334
|
let fd;
|
|
35923
36335
|
try {
|
|
35924
|
-
fd =
|
|
36336
|
+
fd = openSync12(source.path, "r");
|
|
35925
36337
|
const before = fstatSync4(fd);
|
|
35926
36338
|
if (!Number.isSafeInteger(before.size) || before.size < 0) throw new Error("transcript is too large to page safely");
|
|
35927
36339
|
const bytes = Buffer.allocUnsafe(before.size);
|
|
@@ -36220,7 +36632,7 @@ var init_transcript = __esm({
|
|
|
36220
36632
|
"image/gif": "gif",
|
|
36221
36633
|
"image/webp": "webp"
|
|
36222
36634
|
};
|
|
36223
|
-
SCREENSHOT_CACHE_DIR =
|
|
36635
|
+
SCREENSHOT_CACHE_DIR = join27(tmpdir4(), "fray-tool-images");
|
|
36224
36636
|
SCREENSHOT_CACHE_MAX = 200;
|
|
36225
36637
|
SCREENSHOT_MAX_BASE64 = 32e6;
|
|
36226
36638
|
screenshotTmpSeq = 0;
|
|
@@ -36270,9 +36682,9 @@ var init_open_external = __esm({
|
|
|
36270
36682
|
|
|
36271
36683
|
// packages/server/src/local-file.ts
|
|
36272
36684
|
import { spawn as spawn7 } from "node:child_process";
|
|
36273
|
-
import { realpathSync as realpathSync7, statSync as
|
|
36274
|
-
import { homedir as
|
|
36275
|
-
import { isAbsolute as isAbsolute5, join as
|
|
36685
|
+
import { realpathSync as realpathSync7, statSync as statSync15 } from "node:fs";
|
|
36686
|
+
import { homedir as homedir10 } from "node:os";
|
|
36687
|
+
import { isAbsolute as isAbsolute5, join as join28, resolve as resolve8, sep as sep2 } from "node:path";
|
|
36276
36688
|
function isUnder(real, root) {
|
|
36277
36689
|
let rootReal;
|
|
36278
36690
|
try {
|
|
@@ -36292,17 +36704,17 @@ function resolveLocalFile(rawPath, roots) {
|
|
|
36292
36704
|
}
|
|
36293
36705
|
if (!roots.some((root) => isUnder(real, root))) throw new Error("Local file is outside Fray's trusted roots");
|
|
36294
36706
|
try {
|
|
36295
|
-
if (!
|
|
36707
|
+
if (!statSync15(real).isFile()) throw new Error("Local path is not a regular file");
|
|
36296
36708
|
} catch (error) {
|
|
36297
36709
|
if (error instanceof Error && error.message === "Local path is not a regular file") throw error;
|
|
36298
36710
|
throw new Error("Local file was not found");
|
|
36299
36711
|
}
|
|
36300
36712
|
return real;
|
|
36301
36713
|
}
|
|
36302
|
-
function resolveOpenableFile(raw2, projectDir, roots, home =
|
|
36714
|
+
function resolveOpenableFile(raw2, projectDir, roots, home = homedir10()) {
|
|
36303
36715
|
const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
|
|
36304
36716
|
if (!trimmed) return null;
|
|
36305
|
-
const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ?
|
|
36717
|
+
const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join28(home, trimmed.slice(2)) : isAbsolute5(trimmed) ? trimmed : resolve8(projectDir, trimmed);
|
|
36306
36718
|
try {
|
|
36307
36719
|
return resolveLocalFile(abs, roots);
|
|
36308
36720
|
} catch {
|
|
@@ -36646,11 +37058,11 @@ var init_external_terminal = __esm({
|
|
|
36646
37058
|
});
|
|
36647
37059
|
|
|
36648
37060
|
// packages/server/src/background-shell-output.ts
|
|
36649
|
-
import { closeSync as closeSync12, fstatSync as fstatSync5, openSync as
|
|
37061
|
+
import { closeSync as closeSync12, fstatSync as fstatSync5, openSync as openSync13, readSync as readSync7 } from "node:fs";
|
|
36650
37062
|
function readBackgroundShellOutput(path, maxBytes = OUTPUT_TAIL_BYTES) {
|
|
36651
37063
|
let fd;
|
|
36652
37064
|
try {
|
|
36653
|
-
fd =
|
|
37065
|
+
fd = openSync13(path, "r");
|
|
36654
37066
|
const size = fstatSync5(fd).size;
|
|
36655
37067
|
const length = Math.min(size, maxBytes);
|
|
36656
37068
|
const offset = Math.max(0, size - length);
|
|
@@ -36680,7 +37092,7 @@ var init_background_shell_output = __esm({
|
|
|
36680
37092
|
|
|
36681
37093
|
// packages/server/src/router.ts
|
|
36682
37094
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
36683
|
-
import { join as
|
|
37095
|
+
import { join as join29 } from "node:path";
|
|
36684
37096
|
function validateGithubDispatchProfile(input) {
|
|
36685
37097
|
validateThreadProfile(input.backend, input.model, input.effort);
|
|
36686
37098
|
}
|
|
@@ -36821,7 +37233,7 @@ async function stopAndForgetRegisteredRuntime(storage, row, runtime = tmux_expor
|
|
|
36821
37233
|
return forgotten;
|
|
36822
37234
|
}
|
|
36823
37235
|
function createRouter(ctx) {
|
|
36824
|
-
const frayDir =
|
|
37236
|
+
const frayDir = join29(ctx.project.dir, ".fray");
|
|
36825
37237
|
const openRoots = openableFileRoots(ctx.project);
|
|
36826
37238
|
const claudeRename = createClaudeRenameController({ storage: ctx.storage, tailer: ctx.tailer, board: ctx.board });
|
|
36827
37239
|
function isAutoTitledSession(slug) {
|
|
@@ -37159,6 +37571,14 @@ function createRouter(ctx) {
|
|
|
37159
37571
|
if (hasPendingPermissionChange(row)) {
|
|
37160
37572
|
throw new Error("Wait for the current permission change to finish before sending a follow-up");
|
|
37161
37573
|
}
|
|
37574
|
+
if (input.freshProcess) {
|
|
37575
|
+
if (!(row?.backend === "claude" && row.claude_runtime === "broker")) {
|
|
37576
|
+
throw new Error("Only a broker-backed Claude worker can be restarted in place");
|
|
37577
|
+
}
|
|
37578
|
+
if ((ctx.tailer.get(input.slug)?.subAgents ?? []).some((agent) => agent.state === "running")) {
|
|
37579
|
+
throw new Error("This worker has sub-agents still running; restarting it would kill them. Wait for them to finish, then restart.");
|
|
37580
|
+
}
|
|
37581
|
+
}
|
|
37162
37582
|
if (row?.backend === "codex") {
|
|
37163
37583
|
const bridge = ctx.codexAppServer;
|
|
37164
37584
|
if (!bridge) throw new Error("Codex app-server is unavailable; cannot deliver this follow-up");
|
|
@@ -37220,7 +37640,12 @@ function createRouter(ctx) {
|
|
|
37220
37640
|
// needs the same treatment: while the process is still latched on its own 429, delivering
|
|
37221
37641
|
// into it does nothing at all. Restart it instead — otherwise the button is a no-op and
|
|
37222
37642
|
// reads as fray having ignored the click.
|
|
37223
|
-
|
|
37643
|
+
//
|
|
37644
|
+
// `input.freshProcess` is the operator asking for it OUTRIGHT (the "Restart worker" verb),
|
|
37645
|
+
// which the server cannot derive: only the human knows they want the worker back on a newer
|
|
37646
|
+
// build. It is OR'd in rather than replacing the derivation, so a restart clicked on a
|
|
37647
|
+
// limit-latched thread still behaves. The live-sub-agent refusal above applies to both.
|
|
37648
|
+
freshProcess: input.freshProcess === true || needsFreshProcessForLimit(
|
|
37224
37649
|
ctx.tailer.get(input.slug)?.limitFault,
|
|
37225
37650
|
Date.now(),
|
|
37226
37651
|
(ctx.tailer.get(input.slug)?.subAgents ?? []).some((agent) => agent.state === "running")
|
|
@@ -37567,7 +37992,7 @@ function createRouter(ctx) {
|
|
|
37567
37992
|
const row = ctx.storage.getSession(input.slug);
|
|
37568
37993
|
if (!row) return { markdown: "" };
|
|
37569
37994
|
try {
|
|
37570
|
-
return { markdown: readFileSync18(
|
|
37995
|
+
return { markdown: readFileSync18(join29(ctx.project.dir, scratchpadRelPath(row.session_id)), "utf8") };
|
|
37571
37996
|
} catch {
|
|
37572
37997
|
return { markdown: "" };
|
|
37573
37998
|
}
|
|
@@ -38054,7 +38479,7 @@ var init_local_origin = __esm({
|
|
|
38054
38479
|
});
|
|
38055
38480
|
|
|
38056
38481
|
// packages/server/src/local-image.ts
|
|
38057
|
-
import { readFileSync as readFileSync19, realpathSync as realpathSync8, statSync as
|
|
38482
|
+
import { readFileSync as readFileSync19, realpathSync as realpathSync8, statSync as statSync16 } from "node:fs";
|
|
38058
38483
|
import { extname, isAbsolute as isAbsolute6 } from "node:path";
|
|
38059
38484
|
function resolveLocalImage(rawPath) {
|
|
38060
38485
|
if (!rawPath || !isAbsolute6(rawPath)) return { status: 400 };
|
|
@@ -38067,7 +38492,7 @@ function resolveLocalImage(rawPath) {
|
|
|
38067
38492
|
return { status: 404 };
|
|
38068
38493
|
}
|
|
38069
38494
|
try {
|
|
38070
|
-
if (!
|
|
38495
|
+
if (!statSync16(real).isFile()) return { status: 404 };
|
|
38071
38496
|
return { status: 200, contentType, body: readFileSync19(real) };
|
|
38072
38497
|
} catch {
|
|
38073
38498
|
return { status: 404 };
|
|
@@ -38088,11 +38513,11 @@ var init_local_image = __esm({
|
|
|
38088
38513
|
});
|
|
38089
38514
|
|
|
38090
38515
|
// packages/server/src/local-visualization.ts
|
|
38091
|
-
import { readdirSync as
|
|
38092
|
-
import { join as
|
|
38516
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync20, realpathSync as realpathSync9, statSync as statSync17 } from "node:fs";
|
|
38517
|
+
import { join as join30, sep as sep3 } from "node:path";
|
|
38093
38518
|
function children(path, pattern) {
|
|
38094
38519
|
try {
|
|
38095
|
-
return
|
|
38520
|
+
return readdirSync10(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
|
|
38096
38521
|
} catch {
|
|
38097
38522
|
return [];
|
|
38098
38523
|
}
|
|
@@ -38101,7 +38526,7 @@ function isUnder2(path, root) {
|
|
|
38101
38526
|
return path === root || path.startsWith(root.endsWith(sep3) ? root : root + sep3);
|
|
38102
38527
|
}
|
|
38103
38528
|
function resolveFragment(projectDir, sessionId, file) {
|
|
38104
|
-
const base =
|
|
38529
|
+
const base = join30(projectDir, ".codex", "visualizations");
|
|
38105
38530
|
let projectReal;
|
|
38106
38531
|
let baseReal;
|
|
38107
38532
|
try {
|
|
@@ -38112,15 +38537,15 @@ function resolveFragment(projectDir, sessionId, file) {
|
|
|
38112
38537
|
}
|
|
38113
38538
|
if (!isUnder2(baseReal, projectReal)) return null;
|
|
38114
38539
|
for (const year of children(base, YEAR_PART)) {
|
|
38115
|
-
const yearDir =
|
|
38540
|
+
const yearDir = join30(base, year);
|
|
38116
38541
|
for (const month of children(yearDir, DATE_PART)) {
|
|
38117
|
-
const monthDir =
|
|
38542
|
+
const monthDir = join30(yearDir, month);
|
|
38118
38543
|
for (const day of children(monthDir, DATE_PART)) {
|
|
38119
|
-
const sessionRoot =
|
|
38120
|
-
const candidate =
|
|
38544
|
+
const sessionRoot = join30(baseReal, year, month, day, sessionId);
|
|
38545
|
+
const candidate = join30(monthDir, day, sessionId, file);
|
|
38121
38546
|
try {
|
|
38122
38547
|
const real = realpathSync9(candidate);
|
|
38123
|
-
if (isUnder2(real, sessionRoot) &&
|
|
38548
|
+
if (isUnder2(real, sessionRoot) && statSync17(real).isFile()) return real;
|
|
38124
38549
|
} catch {
|
|
38125
38550
|
}
|
|
38126
38551
|
}
|
|
@@ -38201,7 +38626,7 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
|
|
|
38201
38626
|
if (!path) return { status: 404 };
|
|
38202
38627
|
let fragment;
|
|
38203
38628
|
try {
|
|
38204
|
-
const size =
|
|
38629
|
+
const size = statSync17(path).size;
|
|
38205
38630
|
if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
|
|
38206
38631
|
fragment = readFileSync20(path, "utf8");
|
|
38207
38632
|
} catch {
|
|
@@ -38245,8 +38670,8 @@ var init_local_visualization = __esm({
|
|
|
38245
38670
|
});
|
|
38246
38671
|
|
|
38247
38672
|
// packages/server/src/app.ts
|
|
38248
|
-
import { mkdirSync as
|
|
38249
|
-
import { join as
|
|
38673
|
+
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync11 } from "node:fs";
|
|
38674
|
+
import { join as join31 } from "node:path";
|
|
38250
38675
|
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
38251
38676
|
function createApp(ctx, options = {}) {
|
|
38252
38677
|
const app = new Hono2();
|
|
@@ -38323,11 +38748,11 @@ function createApp(ctx, options = {}) {
|
|
|
38323
38748
|
if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
|
|
38324
38749
|
const ext = `.${attachmentExtension(name)}`;
|
|
38325
38750
|
const buf = Buffer.from(body.data, "base64");
|
|
38326
|
-
const dir =
|
|
38327
|
-
|
|
38751
|
+
const dir = join31(ctx.project.stateDir, "attachments");
|
|
38752
|
+
mkdirSync14(dir, { recursive: true });
|
|
38328
38753
|
const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
|
|
38329
|
-
const path =
|
|
38330
|
-
|
|
38754
|
+
const path = join31(dir, `${Date.now()}-${randomUUID18().slice(0, 8)}-${base}${ext}`);
|
|
38755
|
+
writeFileSync11(path, buf);
|
|
38331
38756
|
return c.json({ path });
|
|
38332
38757
|
});
|
|
38333
38758
|
app.get(
|
|
@@ -39599,10 +40024,10 @@ var init_app_socket = __esm({
|
|
|
39599
40024
|
});
|
|
39600
40025
|
|
|
39601
40026
|
// packages/server/src/boot-progress.ts
|
|
39602
|
-
import { mkdirSync as
|
|
39603
|
-
import { basename as basename8, dirname as dirname10, join as
|
|
40027
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync21, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "node:fs";
|
|
40028
|
+
import { basename as basename8, dirname as dirname10, join as join32 } from "node:path";
|
|
39604
40029
|
function bootProgressPath(stateDir) {
|
|
39605
|
-
return
|
|
40030
|
+
return join32(stateDir, BOOT_PROGRESS_NAME);
|
|
39606
40031
|
}
|
|
39607
40032
|
function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
39608
40033
|
if (!stateDir) {
|
|
@@ -39622,9 +40047,9 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
39622
40047
|
lastAt = now;
|
|
39623
40048
|
step++;
|
|
39624
40049
|
try {
|
|
39625
|
-
|
|
39626
|
-
const temp =
|
|
39627
|
-
|
|
40050
|
+
mkdirSync15(dirname10(path), { recursive: true, mode: 448 });
|
|
40051
|
+
const temp = join32(dirname10(path), `.${basename8(path)}.${process.pid}.tmp`);
|
|
40052
|
+
writeFileSync12(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
|
|
39628
40053
|
`, "utf8");
|
|
39629
40054
|
renameSync8(temp, path);
|
|
39630
40055
|
} catch {
|
|
@@ -39633,7 +40058,7 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
39633
40058
|
return Object.assign(write, {
|
|
39634
40059
|
done: () => {
|
|
39635
40060
|
try {
|
|
39636
|
-
|
|
40061
|
+
rmSync7(path, { force: true });
|
|
39637
40062
|
} catch {
|
|
39638
40063
|
}
|
|
39639
40064
|
}
|
|
@@ -39659,7 +40084,7 @@ __export(index_exports, {
|
|
|
39659
40084
|
});
|
|
39660
40085
|
import { createServer } from "node:http";
|
|
39661
40086
|
import { readFileSync as readFileSync22, existsSync as existsSync9 } from "node:fs";
|
|
39662
|
-
import { join as
|
|
40087
|
+
import { join as join33, resolve as resolve9, extname as extname2, normalize } from "node:path";
|
|
39663
40088
|
async function pipeToApp(app, req, res, port2, controller) {
|
|
39664
40089
|
const url = `http://127.0.0.1:${port2}${req.url ?? "/"}`;
|
|
39665
40090
|
res.on("close", () => {
|
|
@@ -39728,9 +40153,9 @@ function createShutdownSignalHandler(options) {
|
|
|
39728
40153
|
}
|
|
39729
40154
|
function serveStatic(distDir, req, res) {
|
|
39730
40155
|
const rel = normalize((req.url ?? "/").split("?")[0]).replace(/^(\.\.[/\\])+/, "");
|
|
39731
|
-
let file =
|
|
39732
|
-
if (!file.startsWith(distDir)) file =
|
|
39733
|
-
if (!existsSync9(file)) file =
|
|
40156
|
+
let file = join33(distDir, rel === "/" ? "index.html" : rel);
|
|
40157
|
+
if (!file.startsWith(distDir)) file = join33(distDir, "index.html");
|
|
40158
|
+
if (!existsSync9(file)) file = join33(distDir, "index.html");
|
|
39734
40159
|
try {
|
|
39735
40160
|
const body = readFileSync22(file);
|
|
39736
40161
|
res.writeHead(200, { "content-type": MIME[extname2(file)] ?? "application/octet-stream" });
|
|
@@ -39780,8 +40205,9 @@ async function startServer(opts = {}) {
|
|
|
39780
40205
|
diagnostics.push(event);
|
|
39781
40206
|
if (opts.shutdownDiagnostic) opts.shutdownDiagnostic(event);
|
|
39782
40207
|
else {
|
|
39783
|
-
|
|
39784
|
-
|
|
40208
|
+
log.warn(
|
|
40209
|
+
"shutdown",
|
|
40210
|
+
`${event.phase}: ${event.message}${event.error instanceof Error ? ` \u2014 ${event.error.message}` : ""}`
|
|
39785
40211
|
);
|
|
39786
40212
|
}
|
|
39787
40213
|
};
|
|
@@ -40028,9 +40454,9 @@ async function startServer(opts = {}) {
|
|
|
40028
40454
|
} else {
|
|
40029
40455
|
await phase("wake scheduler", () => void 0);
|
|
40030
40456
|
}
|
|
40031
|
-
statusPath =
|
|
40457
|
+
statusPath = join33(ctx.project.stateDir, "server.lock");
|
|
40032
40458
|
const webRoot = resolve9(import.meta.dirname, "..", "..", "web");
|
|
40033
|
-
const distDir = opts.webDistDir ? resolve9(opts.webDistDir) :
|
|
40459
|
+
const distDir = opts.webDistDir ? resolve9(opts.webDistDir) : join33(webRoot, "dist");
|
|
40034
40460
|
startupPhase = "Vite";
|
|
40035
40461
|
if (opts.dev) {
|
|
40036
40462
|
try {
|
|
@@ -40042,8 +40468,9 @@ async function startServer(opts = {}) {
|
|
|
40042
40468
|
});
|
|
40043
40469
|
} catch (error) {
|
|
40044
40470
|
if (opts.requireDevWeb) throw error;
|
|
40045
|
-
|
|
40046
|
-
|
|
40471
|
+
log.warn(
|
|
40472
|
+
"server",
|
|
40473
|
+
`vite dev middleware unavailable \u2014 serving API only: ${error instanceof Error ? error.message : error}`
|
|
40047
40474
|
);
|
|
40048
40475
|
}
|
|
40049
40476
|
}
|
|
@@ -40073,7 +40500,7 @@ async function startServer(opts = {}) {
|
|
|
40073
40500
|
if (vite) {
|
|
40074
40501
|
vite.middlewares(req, res, () => {
|
|
40075
40502
|
try {
|
|
40076
|
-
const html = readFileSync22(
|
|
40503
|
+
const html = readFileSync22(join33(webRoot, "index.html"), "utf8");
|
|
40077
40504
|
void vite.transformIndexHtml(url, html).then((out) => {
|
|
40078
40505
|
res.writeHead(200, { "content-type": "text/html" });
|
|
40079
40506
|
res.end(out);
|
|
@@ -40128,8 +40555,9 @@ async function startServer(opts = {}) {
|
|
|
40128
40555
|
port: port2,
|
|
40129
40556
|
bootId: ctx.bootId
|
|
40130
40557
|
}));
|
|
40131
|
-
|
|
40132
|
-
|
|
40558
|
+
log.info(
|
|
40559
|
+
"server",
|
|
40560
|
+
`control plane listening on 127.0.0.1:${port2} (${opts.dev ? "dev" : "prod"}) \u2014 project ${ctx.project.name}`
|
|
40133
40561
|
);
|
|
40134
40562
|
startupPhase = "signal handlers";
|
|
40135
40563
|
if (!opts.requestOwnerStop) {
|
|
@@ -40197,6 +40625,7 @@ var init_index = __esm({
|
|
|
40197
40625
|
init_project_launch();
|
|
40198
40626
|
init_tmux();
|
|
40199
40627
|
init_boot_progress();
|
|
40628
|
+
init_logging();
|
|
40200
40629
|
SERVER_SHUTDOWN_TIMEOUT_MS = 4e3;
|
|
40201
40630
|
SERVER_FORCE_EXIT_MS = 5e3;
|
|
40202
40631
|
defaultStartServerRuntime = {
|
|
@@ -40251,12 +40680,20 @@ var init_index = __esm({
|
|
|
40251
40680
|
init_project();
|
|
40252
40681
|
init_project_launch();
|
|
40253
40682
|
init_shutdown();
|
|
40683
|
+
init_logging();
|
|
40254
40684
|
import { existsSync as existsSync10 } from "node:fs";
|
|
40255
|
-
import { join as
|
|
40685
|
+
import { join as join34 } from "node:path";
|
|
40686
|
+
process.on("uncaughtException", (error) => {
|
|
40687
|
+
log.error("dev-child", `uncaught exception: ${error instanceof Error ? error.stack ?? error.message : error}`);
|
|
40688
|
+
process.exit(1);
|
|
40689
|
+
});
|
|
40690
|
+
process.on("unhandledRejection", (reason) => {
|
|
40691
|
+
log.error("dev-child", `unhandled rejection: ${reason instanceof Error ? reason.stack ?? reason.message : reason}`);
|
|
40692
|
+
});
|
|
40256
40693
|
var rawPort = process.env.FRAY_DEV_PORT;
|
|
40257
40694
|
var port = Number(rawPort);
|
|
40258
40695
|
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
40259
|
-
|
|
40696
|
+
log.error("dev-child", `invalid FRAY_DEV_PORT: ${rawPort ?? "<unset>"}`);
|
|
40260
40697
|
process.exit(1);
|
|
40261
40698
|
}
|
|
40262
40699
|
function notifySupervisor(message) {
|
|
@@ -40285,7 +40722,7 @@ try {
|
|
|
40285
40722
|
];
|
|
40286
40723
|
if (!existsSync10(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
|
|
40287
40724
|
for (const [name, directory, requiredFile] of required) {
|
|
40288
|
-
if (!directory || !existsSync10(
|
|
40725
|
+
if (!directory || !existsSync10(join34(directory, requiredFile)))
|
|
40289
40726
|
throw new Error(`stable artifact launch is missing verified ${name}`);
|
|
40290
40727
|
}
|
|
40291
40728
|
}
|
|
@@ -40316,16 +40753,16 @@ try {
|
|
|
40316
40753
|
process.exit(0);
|
|
40317
40754
|
} catch (err) {
|
|
40318
40755
|
if (err instanceof ShutdownTimeoutError) {
|
|
40319
|
-
|
|
40756
|
+
log.warn("dev-child", `shutdown exceeded ${err.timeoutMs}ms; retaining ownership while the drain completes`);
|
|
40320
40757
|
try {
|
|
40321
40758
|
await server.shutdownFence.whenSafe();
|
|
40322
40759
|
process.exit(0);
|
|
40323
40760
|
return;
|
|
40324
40761
|
} catch (drainError) {
|
|
40325
|
-
|
|
40762
|
+
log.error("dev-child", `late shutdown drain failed: ${drainError instanceof Error ? drainError.stack ?? drainError.message : drainError}`);
|
|
40326
40763
|
}
|
|
40327
40764
|
}
|
|
40328
|
-
|
|
40765
|
+
log.error("dev-child", `shutdown failed: ${err instanceof Error ? err.stack ?? err.message : err}`);
|
|
40329
40766
|
process.exit(1);
|
|
40330
40767
|
}
|
|
40331
40768
|
};
|
|
@@ -40333,6 +40770,6 @@ try {
|
|
|
40333
40770
|
process.on("SIGTERM", () => void shutdown());
|
|
40334
40771
|
process.once("disconnect", () => void shutdown());
|
|
40335
40772
|
} catch (err) {
|
|
40336
|
-
|
|
40773
|
+
log.error("dev-child", `failed to start: ${err instanceof Error ? err.stack ?? err.message : err}`);
|
|
40337
40774
|
process.exit(1);
|
|
40338
40775
|
}
|