@joeyshi12/casper 0.9.8 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/mcp.js +1 -1
- package/dist/server.js +547 -636
- package/dist/web/assets/{abnfDiagram-N423BO3Z-BNWwSSFp.js → abnfDiagram-N423BO3Z-kolM6iA-.js} +1 -1
- package/dist/web/assets/{arc-CcwkM28l.js → arc-B6sXpTsU.js} +1 -1
- package/dist/web/assets/{architectureDiagram-T3A2C74G-BTs-BMSL.js → architectureDiagram-T3A2C74G-OxTKamol.js} +1 -1
- package/dist/web/assets/{blockDiagram-VBNYF7ZC-Cg3AKEAE.js → blockDiagram-VBNYF7ZC-CrzGu26P.js} +1 -1
- package/dist/web/assets/{c4Diagram-5PPSVZJV-BeLOHTLt.js → c4Diagram-5PPSVZJV-DbhlW2MG.js} +1 -1
- package/dist/web/assets/channel-BCnJe19k.js +1 -0
- package/dist/web/assets/{chunk-2GRJ4B5K-BLdzjU64.js → chunk-2GRJ4B5K-B9kcSVk5.js} +1 -1
- package/dist/web/assets/{chunk-2Q5K7J3B-OBq9M6-Y.js → chunk-2Q5K7J3B-D1RHbH0J.js} +1 -1
- package/dist/web/assets/{chunk-5RXB4S5H-Bo5TlL5E.js → chunk-5RXB4S5H-DsS4Kaly.js} +1 -1
- package/dist/web/assets/{chunk-5VM5RSS4-Bu73pV_h.js → chunk-5VM5RSS4-DyOH_9_m.js} +1 -1
- package/dist/web/assets/{chunk-6Q2QTUOP-zkF1AB2H.js → chunk-6Q2QTUOP-r6YYakZy.js} +1 -1
- package/dist/web/assets/{chunk-GF5L2VYU-1gox46V8.js → chunk-GF5L2VYU-C0TZsRe_.js} +1 -1
- package/dist/web/assets/{chunk-JWPE2WC7-Co6JHAZe.js → chunk-JWPE2WC7-BSrnLx8X.js} +1 -1
- package/dist/web/assets/{chunk-KBJHAD2P-COImJ5cB.js → chunk-KBJHAD2P-B3CrUOTy.js} +1 -1
- package/dist/web/assets/{chunk-RYQCIY6F-DwZzSkn2.js → chunk-RYQCIY6F-DXDDOuka.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-D7J4Ua5K.js → chunk-XXDRQBXY-J9uQk278.js} +1 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-CkP6tQZt.js +1 -0
- package/dist/web/assets/classDiagram-v2-OCEON4UE-CkP6tQZt.js +1 -0
- package/dist/web/assets/{cose-bilkent-JH36ORCC-io_sMArY.js → cose-bilkent-JH36ORCC-BuDAduIK.js} +1 -1
- package/dist/web/assets/{cynefin-VYW2F7L2-2kI4W7NK.js → cynefin-VYW2F7L2-CG7bn45a.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-MW4NZA55-DDOMF8nO.js → cynefinDiagram-MW4NZA55-uwFsfwj5.js} +1 -1
- package/dist/web/assets/{dagre-VZM6K2ZE-D8ePE05C.js → dagre-VZM6K2ZE-DZTMXAWW.js} +1 -1
- package/dist/web/assets/{diagram-7IWD3JNH-D7-_PTKo.js → diagram-7IWD3JNH-Brh-1lY7.js} +1 -1
- package/dist/web/assets/{diagram-B4RE2ZJO-CotIn3Ty.js → diagram-B4RE2ZJO-DRJ9jz48.js} +1 -1
- package/dist/web/assets/{diagram-LBJQPF4R-DofOzTTJ.js → diagram-LBJQPF4R-Ci1wP1XA.js} +1 -1
- package/dist/web/assets/{diagram-Q27KOJAE-CVkyN7TJ.js → diagram-Q27KOJAE-q3HfSRIb.js} +1 -1
- package/dist/web/assets/{diagram-UB23O5K3-BMeuDkaO.js → diagram-UB23O5K3-DPMadCyG.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-BXEA7PRR-DprK65id.js → ebnfDiagram-BXEA7PRR-CakDDHAQ.js} +1 -1
- package/dist/web/assets/{erDiagram-JOGREHBK-D-hwXtn8.js → erDiagram-JOGREHBK-DPAJjaAJ.js} +1 -1
- package/dist/web/assets/{flowDiagram-UKHOOZJN-BanZuk45.js → flowDiagram-UKHOOZJN-CK1flOS3.js} +1 -1
- package/dist/web/assets/{ganttDiagram-PKOTCBZU-DKdLDEUL.js → ganttDiagram-PKOTCBZU-ev8Z0O8-.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-CadXyKLa.js → gitGraphDiagram-DS77QQ5N-iJcyO-dh.js} +1 -1
- package/dist/web/assets/index--yuGO85o.js +253 -0
- package/dist/web/assets/index-CTbulwN2.css +1 -0
- package/dist/web/assets/index-UeZS9SPv.js +3 -0
- package/dist/web/assets/{infoDiagram-6WML65LV-DguQBaFj.js → infoDiagram-6WML65LV-DxOiXy9i.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-sCmvl_Ov.js → ishikawaDiagram-WSZJBQD7-BCnL1bCP.js} +1 -1
- package/dist/web/assets/{journeyDiagram-NVQOT4AX-Dp3H0qDU.js → journeyDiagram-NVQOT4AX-D4Nx4bnN.js} +1 -1
- package/dist/web/assets/{kanban-definition-27J2QSJJ-C6mTugp9.js → kanban-definition-27J2QSJJ-vipiDpz7.js} +1 -1
- package/dist/web/assets/katex-BYZTj3zW.css +1 -0
- package/dist/web/assets/katex-C5jXJg4s.js +257 -0
- package/dist/web/assets/{linear-CyDuoWyx.js → linear-DZFaVkiW.js} +1 -1
- package/dist/web/assets/{mermaid.core-BpVrpe6c.js → mermaid.core-BZw29KJL.js} +5 -5
- package/dist/web/assets/{mindmap-definition-FAOFIHXS-BPgOZVAx.js → mindmap-definition-FAOFIHXS-O_jFkrYs.js} +1 -1
- package/dist/web/assets/{pegDiagram-VL7TDLO6-eXPwHM7z.js → pegDiagram-VL7TDLO6-CkN8OpRg.js} +1 -1
- package/dist/web/assets/{pieDiagram-7S7Q4E2Y-CFIMahU1.js → pieDiagram-7S7Q4E2Y-CaYwpFJC.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-dnDjuXib.js → quadrantDiagram-CIZ2JOQS-BF5j8H9k.js} +1 -1
- package/dist/web/assets/{railroadDiagram-AXF67PYL-_3DCSEsR.js → railroadDiagram-AXF67PYL-C8ZNLg_a.js} +1 -1
- package/dist/web/assets/{requirementDiagram-LRYGKXZP-DimVO25E.js → requirementDiagram-LRYGKXZP-DrtfbR4t.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-W5VNT64P-DtCSNUnd.js → sankeyDiagram-W5VNT64P-DzderLXC.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-SI44F4Z6-DyZDw9rq.js → sequenceDiagram-SI44F4Z6-CZNSPgAG.js} +1 -1
- package/dist/web/assets/{sizeCapture-X5ZJPWSS-DsJVViPa.js → sizeCapture-X5ZJPWSS-5MEaTz4H.js} +1 -1
- package/dist/web/assets/{stateDiagram-OKZ733FA-3W1WK-Lx.js → stateDiagram-OKZ733FA-BcE7PIHf.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-CFi3MuJ7.js +1 -0
- package/dist/web/assets/{swimlanes-SLNWSIFB-BdAToTPU.js → swimlanes-SLNWSIFB-1B7NQiEC.js} +2 -2
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-7TcTu5IQ.js +8 -0
- package/dist/web/assets/{timeline-definition-Z64GVDOM-W9PH8xTz.js → timeline-definition-Z64GVDOM-CYYoWuDj.js} +1 -1
- package/dist/web/assets/{vennDiagram-T6HMQDX7-DztY6X4E.js → vennDiagram-T6HMQDX7-Bn_XtMKm.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-T6FBY63Y-BVJp-LQ1.js → wardleyDiagram-T6FBY63Y-Cb_ku0pE.js} +1 -1
- package/dist/web/assets/{xychartDiagram-ELKLHX3M-CUgAA_UM.js → xychartDiagram-ELKLHX3M-C7OphE1U.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/dist/web/assets/channel-DdxwT919.js +0 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-CqM6S8tE.js +0 -1
- package/dist/web/assets/classDiagram-v2-OCEON4UE-CqM6S8tE.js +0 -1
- package/dist/web/assets/index-C0NwqMe_.css +0 -1
- package/dist/web/assets/index-Cv7GqnlJ.js +0 -510
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-BIWqQTVE.js +0 -1
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-iIR_M-eG.js +0 -8
package/dist/server.js
CHANGED
|
@@ -292,6 +292,7 @@ var init_config = __esm({
|
|
|
292
292
|
"token",
|
|
293
293
|
"sessionTtlSeconds",
|
|
294
294
|
"kiroBin",
|
|
295
|
+
"kiroSessionsDir",
|
|
295
296
|
"defaultCwd",
|
|
296
297
|
"maxLiveSessions",
|
|
297
298
|
"defaultAgent",
|
|
@@ -325,8 +326,18 @@ var init_config = __esm({
|
|
|
325
326
|
* or SSH keys.
|
|
326
327
|
*/
|
|
327
328
|
fileRoot: path5.resolve(setting("CASPER_FILE_ROOT", "fileRoot", "/")),
|
|
328
|
-
/**
|
|
329
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Directory where kiro-cli persists its own session files. Overridable for the
|
|
331
|
+
* same reason as every other path here: a non-default kiro home, a container,
|
|
332
|
+
* or a test that must not write into the real one.
|
|
333
|
+
*/
|
|
334
|
+
kiroSessionsDir: path5.resolve(
|
|
335
|
+
setting(
|
|
336
|
+
"CASPER_KIRO_SESSIONS_DIR",
|
|
337
|
+
"kiroSessionsDir",
|
|
338
|
+
path5.join(home, ".kiro", "sessions", "cli")
|
|
339
|
+
)
|
|
340
|
+
),
|
|
330
341
|
/** Where casper.db lives. Env-only: it says where data is, and the config
|
|
331
342
|
* file isn't there. */
|
|
332
343
|
casperDataDir: dataDirPath(),
|
|
@@ -1608,8 +1619,10 @@ var init_SessionManager = __esm({
|
|
|
1608
1619
|
sessions = /* @__PURE__ */ new Map();
|
|
1609
1620
|
log;
|
|
1610
1621
|
store = new SessionStore();
|
|
1611
|
-
|
|
1622
|
+
spawnProcess;
|
|
1623
|
+
constructor(log, opts = {}) {
|
|
1612
1624
|
this.log = log;
|
|
1625
|
+
this.spawnProcess = opts.spawn ?? ((o, l) => new KiroProcess(o, l));
|
|
1613
1626
|
}
|
|
1614
1627
|
/**
|
|
1615
1628
|
* What a session is called, from every read path. The folder is skipped for a workspace of
|
|
@@ -1721,7 +1734,7 @@ var init_SessionManager = __esm({
|
|
|
1721
1734
|
if (s.spawning) return s.spawning;
|
|
1722
1735
|
s.spawning = (async () => {
|
|
1723
1736
|
await this.ensureCapacity();
|
|
1724
|
-
const proc =
|
|
1737
|
+
const proc = this.spawnProcess(
|
|
1725
1738
|
{ cwd: s.cwd, agent: s.agentId, model: s.modelId },
|
|
1726
1739
|
this.log
|
|
1727
1740
|
);
|
|
@@ -1858,61 +1871,58 @@ ${tail}` : err.message;
|
|
|
1858
1871
|
await proc.execCommand(s.sessionId, command, args);
|
|
1859
1872
|
s.lastActivity = Date.now();
|
|
1860
1873
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1874
|
+
summaryOf(live, persisted, transcript) {
|
|
1875
|
+
const sessionId = live?.sessionId ?? persisted.sessionId;
|
|
1876
|
+
const snap = live?.turnState.get();
|
|
1877
|
+
const cwd = live?.cwd ?? this.store.getCwd(sessionId) ?? persisted.cwd;
|
|
1878
|
+
return {
|
|
1879
|
+
sessionId,
|
|
1880
|
+
title: this.titleOf(sessionId, {
|
|
1881
|
+
// kiro's file is what kiro called it; the live copy is only a cache of it.
|
|
1882
|
+
kiroTitle: persisted?.title || live?.title,
|
|
1883
|
+
firstPrompt: transcript && firstPromptText(transcript),
|
|
1884
|
+
cwd
|
|
1885
|
+
}),
|
|
1886
|
+
cwd,
|
|
1887
|
+
createdAt: persisted?.createdAt ?? live.createdAt,
|
|
1888
|
+
// kiro's file and our own activity move separately.
|
|
1889
|
+
updatedAt: live ? newerOf(persisted?.updatedAt, live.updatedAt) : persisted.updatedAt,
|
|
1890
|
+
liveness: live?.proc ? "live" : "dormant",
|
|
1891
|
+
agentId: live?.agentId ?? persisted?.agentId,
|
|
1892
|
+
modelId: live?.modelId ?? persisted?.modelId,
|
|
1893
|
+
running: live?.running ?? false,
|
|
1894
|
+
// A dormant session's totals live in kiro's file; a live one's snapshot
|
|
1895
|
+
// starts at zero until the first turn reports, so fall back to the file.
|
|
1896
|
+
creditsSpent: snap?.creditsSpent || persisted?.creditsSpent,
|
|
1897
|
+
contextUsagePercentage: snap?.contextUsagePercentage || persisted?.contextUsagePercentage
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1864
1900
|
async listSessions() {
|
|
1865
1901
|
const persisted = await listPersistedSessions(this.log);
|
|
1902
|
+
const files = new Map(persisted.map((p) => [p.sessionId, p]));
|
|
1866
1903
|
const byId = /* @__PURE__ */ new Map();
|
|
1867
1904
|
for (const p of persisted) {
|
|
1868
|
-
byId.set(p.sessionId,
|
|
1869
|
-
...p,
|
|
1870
|
-
title: this.titleOf(p.sessionId, {
|
|
1871
|
-
kiroTitle: p.title,
|
|
1872
|
-
cwd: this.store.getCwd(p.sessionId) ?? p.cwd
|
|
1873
|
-
}),
|
|
1874
|
-
cwd: this.store.getCwd(p.sessionId) ?? p.cwd
|
|
1875
|
-
});
|
|
1905
|
+
byId.set(p.sessionId, this.summaryOf(void 0, p));
|
|
1876
1906
|
}
|
|
1877
1907
|
for (const s of this.sessions.values()) {
|
|
1878
|
-
|
|
1879
|
-
const base = byId.get(s.sessionId);
|
|
1880
|
-
if (isGhost(s, base !== void 0)) {
|
|
1908
|
+
if (isGhost(s, files.has(s.sessionId))) {
|
|
1881
1909
|
this.evict(s.sessionId);
|
|
1882
1910
|
continue;
|
|
1883
1911
|
}
|
|
1884
|
-
byId.set(s.sessionId,
|
|
1885
|
-
sessionId: s.sessionId,
|
|
1886
|
-
title: this.titleOf(s.sessionId, { kiroTitle: base?.title, cwd: s.cwd }),
|
|
1887
|
-
cwd: s.cwd,
|
|
1888
|
-
createdAt: base?.createdAt ?? s.createdAt,
|
|
1889
|
-
updatedAt: newerOf(base?.updatedAt, s.updatedAt),
|
|
1890
|
-
liveness: s.proc ? "live" : "dormant",
|
|
1891
|
-
agentId: s.agentId ?? base?.agentId,
|
|
1892
|
-
modelId: s.modelId ?? base?.modelId,
|
|
1893
|
-
running: s.running,
|
|
1894
|
-
creditsSpent: snap.creditsSpent || base?.creditsSpent,
|
|
1895
|
-
contextUsagePercentage: snap.contextUsagePercentage || base?.contextUsagePercentage
|
|
1896
|
-
});
|
|
1912
|
+
byId.set(s.sessionId, this.summaryOf(s, files.get(s.sessionId)));
|
|
1897
1913
|
}
|
|
1898
1914
|
return [...byId.values()].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1899
1915
|
}
|
|
1900
1916
|
async getDetail(sessionId) {
|
|
1901
|
-
const transcript = await
|
|
1917
|
+
const [transcript, persisted] = await Promise.all([
|
|
1918
|
+
hydrateTranscript(sessionId),
|
|
1919
|
+
readPersistedSession(sessionId)
|
|
1920
|
+
]);
|
|
1902
1921
|
const s = this.sessions.get(sessionId);
|
|
1903
|
-
if (s) return this.buildDetail(s, transcript);
|
|
1904
|
-
const persisted = await readPersistedSession(sessionId);
|
|
1922
|
+
if (s) return this.buildDetail(s, transcript, persisted ?? void 0);
|
|
1905
1923
|
if (!persisted) throw new Error(`Unknown session: ${sessionId}`);
|
|
1906
1924
|
return {
|
|
1907
|
-
summary:
|
|
1908
|
-
...persisted,
|
|
1909
|
-
title: this.titleOf(sessionId, {
|
|
1910
|
-
kiroTitle: persisted.title,
|
|
1911
|
-
firstPrompt: firstPromptText(transcript),
|
|
1912
|
-
cwd: this.store.getCwd(sessionId) ?? persisted.cwd
|
|
1913
|
-
}),
|
|
1914
|
-
cwd: this.store.getCwd(sessionId) ?? persisted.cwd
|
|
1915
|
-
},
|
|
1925
|
+
summary: this.summaryOf(void 0, persisted, transcript),
|
|
1916
1926
|
modes: [],
|
|
1917
1927
|
currentModeId: persisted.agentId,
|
|
1918
1928
|
transcript: transcript.slice(-TRANSCRIPT_PAGE_SIZE),
|
|
@@ -1936,26 +1946,10 @@ ${tail}` : err.message;
|
|
|
1936
1946
|
const end = Math.max(start, Math.min(start + limit, transcript.length));
|
|
1937
1947
|
return transcript.slice(start, end);
|
|
1938
1948
|
}
|
|
1939
|
-
buildDetail(s, transcript) {
|
|
1949
|
+
buildDetail(s, transcript, persisted) {
|
|
1940
1950
|
const snap = s.turnState.get();
|
|
1941
1951
|
return {
|
|
1942
|
-
summary:
|
|
1943
|
-
sessionId: s.sessionId,
|
|
1944
|
-
title: this.titleOf(s.sessionId, {
|
|
1945
|
-
kiroTitle: s.title,
|
|
1946
|
-
firstPrompt: firstPromptText(transcript),
|
|
1947
|
-
cwd: s.cwd
|
|
1948
|
-
}),
|
|
1949
|
-
cwd: s.cwd,
|
|
1950
|
-
createdAt: s.createdAt,
|
|
1951
|
-
updatedAt: s.updatedAt,
|
|
1952
|
-
liveness: s.proc ? "live" : "dormant",
|
|
1953
|
-
agentId: s.agentId,
|
|
1954
|
-
modelId: s.modelId,
|
|
1955
|
-
running: s.running,
|
|
1956
|
-
creditsSpent: snap.creditsSpent,
|
|
1957
|
-
contextUsagePercentage: snap.contextUsagePercentage
|
|
1958
|
-
},
|
|
1952
|
+
summary: this.summaryOf(s, persisted, transcript),
|
|
1959
1953
|
modes: s.availableModes,
|
|
1960
1954
|
currentModeId: s.currentModeId,
|
|
1961
1955
|
// Only the tail is sent on load; replayHead/title use the full transcript.
|
|
@@ -2299,13 +2293,242 @@ var init_agents2 = __esm({
|
|
|
2299
2293
|
}
|
|
2300
2294
|
});
|
|
2301
2295
|
|
|
2302
|
-
// server/src/
|
|
2296
|
+
// server/src/util/confinedFile.ts
|
|
2303
2297
|
import fs11 from "node:fs/promises";
|
|
2304
|
-
import { createReadStream } from "node:fs";
|
|
2305
2298
|
import path12 from "node:path";
|
|
2306
|
-
function
|
|
2307
|
-
|
|
2299
|
+
async function resolveConfined(input, spec) {
|
|
2300
|
+
let target = null;
|
|
2301
|
+
for (const root of spec.lexical) {
|
|
2302
|
+
target = confineToRoot(root, input);
|
|
2303
|
+
if (target) break;
|
|
2304
|
+
}
|
|
2305
|
+
if (!target) return { ok: false, ...spec.escaped };
|
|
2306
|
+
let real = null;
|
|
2307
|
+
for (const root of spec.real) {
|
|
2308
|
+
real = await realConfineToRoot(root, target);
|
|
2309
|
+
if (real) break;
|
|
2310
|
+
}
|
|
2311
|
+
if (!real) return { ok: false, ...await spec.notFound() };
|
|
2312
|
+
let stat;
|
|
2313
|
+
try {
|
|
2314
|
+
stat = await fs11.stat(real);
|
|
2315
|
+
} catch {
|
|
2316
|
+
return { ok: false, ...await spec.notFound() };
|
|
2317
|
+
}
|
|
2318
|
+
if (spec.require === "file" && !stat.isFile()) {
|
|
2319
|
+
return { ok: false, status: 400, error: "Path is not a file" };
|
|
2320
|
+
}
|
|
2321
|
+
if (spec.require === "directory" && !stat.isDirectory()) {
|
|
2322
|
+
return { ok: false, ...await spec.notFound() };
|
|
2323
|
+
}
|
|
2324
|
+
return { ok: true, real, stat };
|
|
2325
|
+
}
|
|
2326
|
+
function replyWith(reply, failure) {
|
|
2327
|
+
reply.code(failure.status);
|
|
2328
|
+
return { error: failure.error };
|
|
2329
|
+
}
|
|
2330
|
+
async function resolveSessionPath(sessions, sessionId, requestedPath, require2) {
|
|
2331
|
+
let cwd;
|
|
2332
|
+
try {
|
|
2333
|
+
cwd = await sessions.getSessionCwd(sessionId);
|
|
2334
|
+
} catch {
|
|
2335
|
+
return { ok: false, status: 404, error: "Session not found" };
|
|
2336
|
+
}
|
|
2337
|
+
const relative = (requestedPath ?? "").replace(/^\/+/, "");
|
|
2338
|
+
if (require2 === "file" && !relative) {
|
|
2339
|
+
return { ok: false, status: 400, error: "path parameter is required" };
|
|
2340
|
+
}
|
|
2341
|
+
const resolved = await resolveConfined(relative, {
|
|
2342
|
+
lexical: [cwd],
|
|
2343
|
+
real: [config.fileRoot],
|
|
2344
|
+
require: require2,
|
|
2345
|
+
escaped: { status: 400, error: "Invalid path" },
|
|
2346
|
+
notFound: require2 === "file" ? () => ({ status: 404, error: "File not found" }) : () => workspaceNotFound(cwd)
|
|
2347
|
+
});
|
|
2348
|
+
return resolved.ok ? { ...resolved, cwd, relative } : resolved;
|
|
2349
|
+
}
|
|
2350
|
+
async function workspaceNotFound(cwd) {
|
|
2351
|
+
let missing;
|
|
2352
|
+
try {
|
|
2353
|
+
missing = !(await fs11.stat(cwd)).isDirectory();
|
|
2354
|
+
} catch {
|
|
2355
|
+
missing = true;
|
|
2356
|
+
}
|
|
2357
|
+
return missing ? { status: 404, error: `Workspace folder no longer exists: ${cwd}` } : { status: 404, error: "Directory not found" };
|
|
2358
|
+
}
|
|
2359
|
+
function absoluteRoots() {
|
|
2360
|
+
return [config.fileRoot, config.casperDataDir];
|
|
2361
|
+
}
|
|
2362
|
+
async function resolveAbsolutePath(input, require2) {
|
|
2363
|
+
const roots = absoluteRoots();
|
|
2364
|
+
return resolveConfined(input, {
|
|
2365
|
+
lexical: roots,
|
|
2366
|
+
real: roots,
|
|
2367
|
+
require: require2,
|
|
2368
|
+
escaped: { status: 403, error: "Path outside allowed root" },
|
|
2369
|
+
notFound: () => ({ status: 404, error: "File not found" })
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
async function classifyDirent(parentReal, entry, realRoots) {
|
|
2373
|
+
const absolute = path12.join(parentReal, entry.name);
|
|
2374
|
+
if (entry.isDirectory()) return { kind: "directory", real: absolute };
|
|
2375
|
+
if (entry.isFile()) return { kind: "file", real: absolute };
|
|
2376
|
+
if (!entry.isSymbolicLink()) return null;
|
|
2377
|
+
for (const root of realRoots) {
|
|
2378
|
+
const real = await realConfineToRoot(root, absolute);
|
|
2379
|
+
if (!real) continue;
|
|
2380
|
+
try {
|
|
2381
|
+
const stat = await fs11.stat(real);
|
|
2382
|
+
if (stat.isDirectory()) return { kind: "directory", real };
|
|
2383
|
+
if (stat.isFile()) return { kind: "file", real };
|
|
2384
|
+
} catch {
|
|
2385
|
+
}
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
return null;
|
|
2389
|
+
}
|
|
2390
|
+
var init_confinedFile = __esm({
|
|
2391
|
+
"server/src/util/confinedFile.ts"() {
|
|
2392
|
+
"use strict";
|
|
2393
|
+
init_config();
|
|
2394
|
+
init_paths2();
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
// server/src/util/filekind.ts
|
|
2399
|
+
import path13 from "node:path";
|
|
2400
|
+
function mimeForExt(ext) {
|
|
2401
|
+
return MIME_MAP[ext.toLowerCase()] ?? "application/octet-stream";
|
|
2402
|
+
}
|
|
2403
|
+
function looksBinary(buf) {
|
|
2404
|
+
if (buf.length === 0) return false;
|
|
2405
|
+
if (buf.length >= 2) {
|
|
2406
|
+
const b0 = buf[0];
|
|
2407
|
+
const b1 = buf[1];
|
|
2408
|
+
if (b0 === 255 && b1 === 254 || b0 === 254 && b1 === 255) return true;
|
|
2409
|
+
}
|
|
2410
|
+
let control = 0;
|
|
2411
|
+
for (let i = 0; i < buf.length; i++) {
|
|
2412
|
+
const b = buf[i];
|
|
2413
|
+
if (b === 0) return true;
|
|
2414
|
+
if (b < 32 && b !== 9 && b !== 10 && b !== 12 && b !== 13) control++;
|
|
2415
|
+
}
|
|
2416
|
+
return control / buf.length > 0.3;
|
|
2417
|
+
}
|
|
2418
|
+
function classifyKind(name) {
|
|
2419
|
+
const ext = path13.extname(name).toLowerCase();
|
|
2420
|
+
if (ext in IMAGE_EXTS2) return "image";
|
|
2421
|
+
if (TEXT_EXTS.has(ext)) return "text";
|
|
2422
|
+
return "binary";
|
|
2308
2423
|
}
|
|
2424
|
+
var IMAGE_EXTS2, TEXT_EXTS, MIME_MAP;
|
|
2425
|
+
var init_filekind = __esm({
|
|
2426
|
+
"server/src/util/filekind.ts"() {
|
|
2427
|
+
"use strict";
|
|
2428
|
+
IMAGE_EXTS2 = {
|
|
2429
|
+
".png": "image/png",
|
|
2430
|
+
".jpg": "image/jpeg",
|
|
2431
|
+
".jpeg": "image/jpeg",
|
|
2432
|
+
".gif": "image/gif",
|
|
2433
|
+
".webp": "image/webp",
|
|
2434
|
+
".svg": "image/svg+xml",
|
|
2435
|
+
".bmp": "image/bmp",
|
|
2436
|
+
".ico": "image/x-icon",
|
|
2437
|
+
".avif": "image/avif"
|
|
2438
|
+
};
|
|
2439
|
+
TEXT_EXTS = /* @__PURE__ */ new Set([
|
|
2440
|
+
".txt",
|
|
2441
|
+
".md",
|
|
2442
|
+
".markdown",
|
|
2443
|
+
".rst",
|
|
2444
|
+
".log",
|
|
2445
|
+
".csv",
|
|
2446
|
+
".tsv",
|
|
2447
|
+
".json",
|
|
2448
|
+
".jsonl",
|
|
2449
|
+
".yaml",
|
|
2450
|
+
".yml",
|
|
2451
|
+
".toml",
|
|
2452
|
+
".ini",
|
|
2453
|
+
".env",
|
|
2454
|
+
".cfg",
|
|
2455
|
+
".conf",
|
|
2456
|
+
".xml",
|
|
2457
|
+
".html",
|
|
2458
|
+
".htm",
|
|
2459
|
+
".css",
|
|
2460
|
+
".scss",
|
|
2461
|
+
".less",
|
|
2462
|
+
".js",
|
|
2463
|
+
".mjs",
|
|
2464
|
+
".cjs",
|
|
2465
|
+
".jsx",
|
|
2466
|
+
".ts",
|
|
2467
|
+
".tsx",
|
|
2468
|
+
".vue",
|
|
2469
|
+
".svelte",
|
|
2470
|
+
".py",
|
|
2471
|
+
".rb",
|
|
2472
|
+
".go",
|
|
2473
|
+
".rs",
|
|
2474
|
+
".java",
|
|
2475
|
+
".kt",
|
|
2476
|
+
".c",
|
|
2477
|
+
".h",
|
|
2478
|
+
".cpp",
|
|
2479
|
+
".hpp",
|
|
2480
|
+
".cc",
|
|
2481
|
+
".cs",
|
|
2482
|
+
".php",
|
|
2483
|
+
".swift",
|
|
2484
|
+
".scala",
|
|
2485
|
+
".sh",
|
|
2486
|
+
".bash",
|
|
2487
|
+
".zsh",
|
|
2488
|
+
".fish",
|
|
2489
|
+
".sql",
|
|
2490
|
+
".graphql",
|
|
2491
|
+
".proto",
|
|
2492
|
+
".dockerfile",
|
|
2493
|
+
".makefile",
|
|
2494
|
+
".gradle",
|
|
2495
|
+
".diff",
|
|
2496
|
+
".patch",
|
|
2497
|
+
".tex"
|
|
2498
|
+
]);
|
|
2499
|
+
MIME_MAP = {
|
|
2500
|
+
...IMAGE_EXTS2,
|
|
2501
|
+
".txt": "text/plain",
|
|
2502
|
+
".md": "text/markdown",
|
|
2503
|
+
".csv": "text/csv",
|
|
2504
|
+
".json": "application/json",
|
|
2505
|
+
".xml": "application/xml",
|
|
2506
|
+
".yaml": "text/yaml",
|
|
2507
|
+
".yml": "text/yaml",
|
|
2508
|
+
".html": "text/html",
|
|
2509
|
+
".css": "text/css",
|
|
2510
|
+
".js": "application/javascript",
|
|
2511
|
+
".mjs": "application/javascript",
|
|
2512
|
+
".ts": "text/typescript",
|
|
2513
|
+
".tsx": "text/typescript",
|
|
2514
|
+
".toml": "text/plain",
|
|
2515
|
+
".pdf": "application/pdf",
|
|
2516
|
+
".zip": "application/zip",
|
|
2517
|
+
".tar": "application/x-tar",
|
|
2518
|
+
".gz": "application/gzip",
|
|
2519
|
+
".wasm": "application/wasm",
|
|
2520
|
+
".exe": "application/vnd.microsoft.portable-executable",
|
|
2521
|
+
".dll": "application/vnd.microsoft.portable-executable",
|
|
2522
|
+
".so": "application/x-sharedlib",
|
|
2523
|
+
".bin": "application/octet-stream"
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2528
|
+
// server/src/routes/fs.ts
|
|
2529
|
+
import fs12 from "node:fs/promises";
|
|
2530
|
+
import { createReadStream } from "node:fs";
|
|
2531
|
+
import path14 from "node:path";
|
|
2309
2532
|
function registerFsRoutes(app) {
|
|
2310
2533
|
app.get(
|
|
2311
2534
|
"/api/fs/dirs",
|
|
@@ -2313,37 +2536,27 @@ function registerFsRoutes(app) {
|
|
|
2313
2536
|
const input = (req.query.path ?? "").trim();
|
|
2314
2537
|
const base = config.defaultCwd;
|
|
2315
2538
|
const endsWithSep = input.endsWith("/");
|
|
2316
|
-
const resolved = input ?
|
|
2317
|
-
const dir = endsWithSep || !input ? resolved :
|
|
2318
|
-
const prefix = endsWithSep || !input ? "" :
|
|
2319
|
-
const targetKind = await
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
}
|
|
2324
|
-
const realDir = await realConfineToRoot(config.fileRoot, dir);
|
|
2325
|
-
if (!realDir) {
|
|
2539
|
+
const resolved = input ? path14.resolve(base, input) : base;
|
|
2540
|
+
const dir = endsWithSep || !input ? resolved : path14.dirname(resolved);
|
|
2541
|
+
const prefix = endsWithSep || !input ? "" : path14.basename(resolved);
|
|
2542
|
+
const targetKind = await fs12.stat(resolved).then((s) => s.isDirectory() ? "directory" : "file").catch(() => "missing");
|
|
2543
|
+
const listing = await resolveAbsolutePath(dir, "directory");
|
|
2544
|
+
if (!listing.ok) {
|
|
2545
|
+
if (listing.status === 403) return replyWith(reply, listing);
|
|
2326
2546
|
return { dir, entries: [], target: resolved, targetKind };
|
|
2327
2547
|
}
|
|
2548
|
+
const realDir = listing.real;
|
|
2328
2549
|
let entries = [];
|
|
2329
2550
|
try {
|
|
2330
|
-
const dirents = await
|
|
2551
|
+
const dirents = await fs12.readdir(realDir, { withFileTypes: true });
|
|
2331
2552
|
const candidates = dirents.filter((d) => !d.name.startsWith("."));
|
|
2332
2553
|
const checks2 = await Promise.all(
|
|
2333
2554
|
candidates.map(async (d) => {
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
const full = path12.join(realDir, d.name);
|
|
2337
|
-
const real = await realConfineToRoot(config.fileRoot, full);
|
|
2338
|
-
if (!real) return null;
|
|
2339
|
-
try {
|
|
2340
|
-
return (await fs11.stat(real)).isDirectory() ? d.name : null;
|
|
2341
|
-
} catch {
|
|
2342
|
-
return null;
|
|
2343
|
-
}
|
|
2555
|
+
const target = await classifyDirent(realDir, d, absoluteRoots());
|
|
2556
|
+
return target?.kind === "directory" ? d.name : null;
|
|
2344
2557
|
})
|
|
2345
2558
|
);
|
|
2346
|
-
entries = checks2.filter((name) => name !== null).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase())).sort((a, b) => a.localeCompare(b)).slice(0, 20).map((name) =>
|
|
2559
|
+
entries = checks2.filter((name) => name !== null).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase())).sort((a, b) => a.localeCompare(b)).slice(0, 20).map((name) => path14.join(dir, name));
|
|
2347
2560
|
} catch {
|
|
2348
2561
|
entries = [];
|
|
2349
2562
|
}
|
|
@@ -2358,72 +2571,43 @@ function registerFsRoutes(app) {
|
|
|
2358
2571
|
reply.code(400);
|
|
2359
2572
|
return { error: "path parameter is required" };
|
|
2360
2573
|
}
|
|
2361
|
-
if (!
|
|
2574
|
+
if (!path14.isAbsolute(filePath)) {
|
|
2362
2575
|
reply.code(400);
|
|
2363
2576
|
return { error: "path must be absolute" };
|
|
2364
2577
|
}
|
|
2365
|
-
const
|
|
2366
|
-
if (
|
|
2367
|
-
reply.code(403);
|
|
2368
|
-
return { error: "Path outside allowed root" };
|
|
2369
|
-
}
|
|
2370
|
-
const ext = path12.extname(resolved).toLowerCase();
|
|
2371
|
-
const mime = IMAGE_MIMES[ext];
|
|
2372
|
-
if (!mime) {
|
|
2578
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
2579
|
+
if (classifyKind(filePath) !== "image") {
|
|
2373
2580
|
reply.code(400);
|
|
2374
2581
|
return { error: `Not a supported image type: ${ext}` };
|
|
2375
2582
|
}
|
|
2376
|
-
const
|
|
2377
|
-
if (!
|
|
2378
|
-
|
|
2379
|
-
return { error: "File not found" };
|
|
2380
|
-
}
|
|
2381
|
-
let stat;
|
|
2382
|
-
try {
|
|
2383
|
-
stat = await fs11.stat(real);
|
|
2384
|
-
} catch {
|
|
2385
|
-
reply.code(404);
|
|
2386
|
-
return { error: "File not found" };
|
|
2387
|
-
}
|
|
2388
|
-
if (!stat.isFile()) {
|
|
2389
|
-
reply.code(400);
|
|
2390
|
-
return { error: "Path is not a file" };
|
|
2391
|
-
}
|
|
2583
|
+
const resolvedImage = await resolveAbsolutePath(filePath, "file");
|
|
2584
|
+
if (!resolvedImage.ok) return replyWith(reply, resolvedImage);
|
|
2585
|
+
const { real, stat } = resolvedImage;
|
|
2392
2586
|
if (stat.size > MAX_IMAGE_BYTES) {
|
|
2393
2587
|
reply.code(413);
|
|
2394
2588
|
return { error: "Image too large" };
|
|
2395
2589
|
}
|
|
2396
2590
|
const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
|
|
2397
|
-
reply.header("Cache-Control", "private,
|
|
2591
|
+
reply.header("Cache-Control", "private, no-cache");
|
|
2398
2592
|
reply.header("ETag", etag);
|
|
2399
2593
|
reply.header("Last-Modified", stat.mtime.toUTCString());
|
|
2400
2594
|
if (req.headers["if-none-match"] === etag) {
|
|
2401
2595
|
reply.code(304);
|
|
2402
2596
|
return reply.send();
|
|
2403
2597
|
}
|
|
2404
|
-
reply.header("Content-Type",
|
|
2598
|
+
reply.header("Content-Type", mimeForExt(ext));
|
|
2405
2599
|
reply.header("Content-Length", stat.size);
|
|
2406
2600
|
return reply.send(createReadStream(real));
|
|
2407
2601
|
}
|
|
2408
2602
|
);
|
|
2409
2603
|
}
|
|
2410
|
-
var
|
|
2604
|
+
var MAX_IMAGE_BYTES;
|
|
2411
2605
|
var init_fs = __esm({
|
|
2412
2606
|
"server/src/routes/fs.ts"() {
|
|
2413
2607
|
"use strict";
|
|
2414
2608
|
init_config();
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
".png": "image/png",
|
|
2418
|
-
".jpg": "image/jpeg",
|
|
2419
|
-
".jpeg": "image/jpeg",
|
|
2420
|
-
".gif": "image/gif",
|
|
2421
|
-
".webp": "image/webp",
|
|
2422
|
-
".svg": "image/svg+xml",
|
|
2423
|
-
".bmp": "image/bmp",
|
|
2424
|
-
".ico": "image/x-icon",
|
|
2425
|
-
".avif": "image/avif"
|
|
2426
|
-
};
|
|
2609
|
+
init_confinedFile();
|
|
2610
|
+
init_filekind();
|
|
2427
2611
|
MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
2428
2612
|
}
|
|
2429
2613
|
});
|
|
@@ -2537,236 +2721,72 @@ var init_sessions = __esm({
|
|
|
2537
2721
|
}
|
|
2538
2722
|
});
|
|
2539
2723
|
|
|
2540
|
-
// server/src/
|
|
2541
|
-
import
|
|
2542
|
-
|
|
2543
|
-
|
|
2724
|
+
// server/src/routes/workspace.ts
|
|
2725
|
+
import fs13 from "node:fs/promises";
|
|
2726
|
+
import { createReadStream as createReadStream2 } from "node:fs";
|
|
2727
|
+
import path15 from "node:path";
|
|
2728
|
+
function tooLargeForPreview(size, maxBytes) {
|
|
2729
|
+
return {
|
|
2730
|
+
error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
|
|
2731
|
+
};
|
|
2544
2732
|
}
|
|
2545
|
-
function
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
const
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2733
|
+
function hexdump(buf) {
|
|
2734
|
+
const lines = [];
|
|
2735
|
+
for (let off = 0; off < buf.length; off += 16) {
|
|
2736
|
+
const slice = buf.subarray(off, off + 16);
|
|
2737
|
+
const hex = [];
|
|
2738
|
+
let ascii = "";
|
|
2739
|
+
for (let i = 0; i < 16; i++) {
|
|
2740
|
+
if (i < slice.length) {
|
|
2741
|
+
const b = slice[i];
|
|
2742
|
+
hex.push(b.toString(16).padStart(2, "0"));
|
|
2743
|
+
ascii += b >= 32 && b <= 126 ? String.fromCharCode(b) : ".";
|
|
2744
|
+
} else {
|
|
2745
|
+
hex.push(" ");
|
|
2746
|
+
}
|
|
2747
|
+
if (i === 7) hex.push("");
|
|
2748
|
+
}
|
|
2749
|
+
lines.push(`${off.toString(16).padStart(8, "0")} ${hex.join(" ")} |${ascii}|`);
|
|
2557
2750
|
}
|
|
2558
|
-
return
|
|
2559
|
-
}
|
|
2560
|
-
function classifyKind(name) {
|
|
2561
|
-
const ext = path13.extname(name).toLowerCase();
|
|
2562
|
-
if (ext in IMAGE_EXTS2) return "image";
|
|
2563
|
-
if (TEXT_EXTS.has(ext)) return "text";
|
|
2564
|
-
return "binary";
|
|
2565
|
-
}
|
|
2566
|
-
var IMAGE_EXTS2, TEXT_EXTS, MIME_MAP;
|
|
2567
|
-
var init_filekind = __esm({
|
|
2568
|
-
"server/src/util/filekind.ts"() {
|
|
2569
|
-
"use strict";
|
|
2570
|
-
IMAGE_EXTS2 = {
|
|
2571
|
-
".png": "image/png",
|
|
2572
|
-
".jpg": "image/jpeg",
|
|
2573
|
-
".jpeg": "image/jpeg",
|
|
2574
|
-
".gif": "image/gif",
|
|
2575
|
-
".webp": "image/webp",
|
|
2576
|
-
".svg": "image/svg+xml",
|
|
2577
|
-
".bmp": "image/bmp",
|
|
2578
|
-
".ico": "image/x-icon",
|
|
2579
|
-
".avif": "image/avif"
|
|
2580
|
-
};
|
|
2581
|
-
TEXT_EXTS = /* @__PURE__ */ new Set([
|
|
2582
|
-
".txt",
|
|
2583
|
-
".md",
|
|
2584
|
-
".markdown",
|
|
2585
|
-
".rst",
|
|
2586
|
-
".log",
|
|
2587
|
-
".csv",
|
|
2588
|
-
".tsv",
|
|
2589
|
-
".json",
|
|
2590
|
-
".jsonl",
|
|
2591
|
-
".yaml",
|
|
2592
|
-
".yml",
|
|
2593
|
-
".toml",
|
|
2594
|
-
".ini",
|
|
2595
|
-
".env",
|
|
2596
|
-
".cfg",
|
|
2597
|
-
".conf",
|
|
2598
|
-
".xml",
|
|
2599
|
-
".html",
|
|
2600
|
-
".htm",
|
|
2601
|
-
".css",
|
|
2602
|
-
".scss",
|
|
2603
|
-
".less",
|
|
2604
|
-
".js",
|
|
2605
|
-
".mjs",
|
|
2606
|
-
".cjs",
|
|
2607
|
-
".jsx",
|
|
2608
|
-
".ts",
|
|
2609
|
-
".tsx",
|
|
2610
|
-
".vue",
|
|
2611
|
-
".svelte",
|
|
2612
|
-
".py",
|
|
2613
|
-
".rb",
|
|
2614
|
-
".go",
|
|
2615
|
-
".rs",
|
|
2616
|
-
".java",
|
|
2617
|
-
".kt",
|
|
2618
|
-
".c",
|
|
2619
|
-
".h",
|
|
2620
|
-
".cpp",
|
|
2621
|
-
".hpp",
|
|
2622
|
-
".cc",
|
|
2623
|
-
".cs",
|
|
2624
|
-
".php",
|
|
2625
|
-
".swift",
|
|
2626
|
-
".scala",
|
|
2627
|
-
".sh",
|
|
2628
|
-
".bash",
|
|
2629
|
-
".zsh",
|
|
2630
|
-
".fish",
|
|
2631
|
-
".sql",
|
|
2632
|
-
".graphql",
|
|
2633
|
-
".proto",
|
|
2634
|
-
".dockerfile",
|
|
2635
|
-
".makefile",
|
|
2636
|
-
".gradle",
|
|
2637
|
-
".diff",
|
|
2638
|
-
".patch",
|
|
2639
|
-
".tex"
|
|
2640
|
-
]);
|
|
2641
|
-
MIME_MAP = {
|
|
2642
|
-
...IMAGE_EXTS2,
|
|
2643
|
-
".txt": "text/plain",
|
|
2644
|
-
".md": "text/markdown",
|
|
2645
|
-
".csv": "text/csv",
|
|
2646
|
-
".json": "application/json",
|
|
2647
|
-
".xml": "application/xml",
|
|
2648
|
-
".yaml": "text/yaml",
|
|
2649
|
-
".yml": "text/yaml",
|
|
2650
|
-
".html": "text/html",
|
|
2651
|
-
".css": "text/css",
|
|
2652
|
-
".js": "application/javascript",
|
|
2653
|
-
".mjs": "application/javascript",
|
|
2654
|
-
".ts": "text/typescript",
|
|
2655
|
-
".tsx": "text/typescript",
|
|
2656
|
-
".toml": "text/plain",
|
|
2657
|
-
".pdf": "application/pdf",
|
|
2658
|
-
".zip": "application/zip",
|
|
2659
|
-
".tar": "application/x-tar",
|
|
2660
|
-
".gz": "application/gzip",
|
|
2661
|
-
".wasm": "application/wasm",
|
|
2662
|
-
".exe": "application/vnd.microsoft.portable-executable",
|
|
2663
|
-
".dll": "application/vnd.microsoft.portable-executable",
|
|
2664
|
-
".so": "application/x-sharedlib",
|
|
2665
|
-
".bin": "application/octet-stream"
|
|
2666
|
-
};
|
|
2667
|
-
}
|
|
2668
|
-
});
|
|
2669
|
-
|
|
2670
|
-
// server/src/routes/workspace.ts
|
|
2671
|
-
import fs12 from "node:fs/promises";
|
|
2672
|
-
import { createReadStream as createReadStream2 } from "node:fs";
|
|
2673
|
-
import path14 from "node:path";
|
|
2674
|
-
function tooLargeForPreview(size, maxBytes) {
|
|
2675
|
-
return {
|
|
2676
|
-
error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
|
|
2677
|
-
};
|
|
2678
|
-
}
|
|
2679
|
-
function hexdump(buf) {
|
|
2680
|
-
const lines = [];
|
|
2681
|
-
for (let off = 0; off < buf.length; off += 16) {
|
|
2682
|
-
const slice = buf.subarray(off, off + 16);
|
|
2683
|
-
const hex = [];
|
|
2684
|
-
let ascii = "";
|
|
2685
|
-
for (let i = 0; i < 16; i++) {
|
|
2686
|
-
if (i < slice.length) {
|
|
2687
|
-
const b = slice[i];
|
|
2688
|
-
hex.push(b.toString(16).padStart(2, "0"));
|
|
2689
|
-
ascii += b >= 32 && b <= 126 ? String.fromCharCode(b) : ".";
|
|
2690
|
-
} else {
|
|
2691
|
-
hex.push(" ");
|
|
2692
|
-
}
|
|
2693
|
-
if (i === 7) hex.push("");
|
|
2694
|
-
}
|
|
2695
|
-
lines.push(`${off.toString(16).padStart(8, "0")} ${hex.join(" ")} |${ascii}|`);
|
|
2696
|
-
}
|
|
2697
|
-
return lines.join("\n");
|
|
2751
|
+
return lines.join("\n");
|
|
2698
2752
|
}
|
|
2699
2753
|
function registerWorkspaceRoutes(app, manager) {
|
|
2700
2754
|
app.get(
|
|
2701
2755
|
"/api/sessions/:id/tree",
|
|
2702
2756
|
async (req, reply) => {
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
const relative
|
|
2711
|
-
const target = confineToRoot(cwd, relative);
|
|
2712
|
-
if (!target) {
|
|
2713
|
-
reply.code(400);
|
|
2714
|
-
return { error: "Invalid path" };
|
|
2715
|
-
}
|
|
2716
|
-
const workspaceMissing = async () => {
|
|
2717
|
-
try {
|
|
2718
|
-
return !(await fs12.stat(cwd)).isDirectory();
|
|
2719
|
-
} catch {
|
|
2720
|
-
return true;
|
|
2721
|
-
}
|
|
2722
|
-
};
|
|
2723
|
-
const notFound = async () => {
|
|
2724
|
-
reply.code(404);
|
|
2725
|
-
return await workspaceMissing() ? { error: `Workspace folder no longer exists: ${cwd}` } : { error: "Directory not found" };
|
|
2726
|
-
};
|
|
2727
|
-
const realTarget = await realConfineToRoot(config.fileRoot, target);
|
|
2728
|
-
if (!realTarget) {
|
|
2729
|
-
return notFound();
|
|
2730
|
-
}
|
|
2757
|
+
const resolved = await resolveSessionPath(
|
|
2758
|
+
manager,
|
|
2759
|
+
req.params.id,
|
|
2760
|
+
req.query.path,
|
|
2761
|
+
"directory"
|
|
2762
|
+
);
|
|
2763
|
+
if (!resolved.ok) return replyWith(reply, resolved);
|
|
2764
|
+
const { cwd, relative, real: realTarget } = resolved;
|
|
2731
2765
|
let dirents;
|
|
2732
2766
|
try {
|
|
2733
|
-
dirents = await
|
|
2767
|
+
dirents = await fs13.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
|
|
2734
2768
|
} catch {
|
|
2735
|
-
return
|
|
2769
|
+
return replyWith(reply, await workspaceNotFound(cwd));
|
|
2736
2770
|
}
|
|
2737
2771
|
const entries = [];
|
|
2738
2772
|
for (const d of dirents) {
|
|
2739
|
-
const
|
|
2740
|
-
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
const real = await realConfineToRoot(config.fileRoot, entryAbsolute);
|
|
2746
|
-
if (!real) continue;
|
|
2747
|
-
try {
|
|
2748
|
-
const st = await fs12.stat(real);
|
|
2749
|
-
kind = st.isDirectory() ? "directory" : st.isFile() ? "file" : null;
|
|
2750
|
-
statTarget = real;
|
|
2751
|
-
} catch {
|
|
2752
|
-
continue;
|
|
2753
|
-
}
|
|
2773
|
+
const target = await classifyDirent(realTarget, d, [config.fileRoot]);
|
|
2774
|
+
if (!target) continue;
|
|
2775
|
+
const entryRelative = relative ? `${relative}/${d.name}` : d.name;
|
|
2776
|
+
if (target.kind === "directory") {
|
|
2777
|
+
entries.push({ name: d.name, path: entryRelative, type: "directory" });
|
|
2778
|
+
continue;
|
|
2754
2779
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
entries.push({
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
size: stat.size,
|
|
2766
|
-
modifiedAt: stat.mtime.toISOString()
|
|
2767
|
-
});
|
|
2768
|
-
} catch {
|
|
2769
|
-
}
|
|
2780
|
+
try {
|
|
2781
|
+
const stat = await fs13.stat(target.real);
|
|
2782
|
+
entries.push({
|
|
2783
|
+
name: d.name,
|
|
2784
|
+
path: entryRelative,
|
|
2785
|
+
type: "file",
|
|
2786
|
+
size: stat.size,
|
|
2787
|
+
modifiedAt: stat.mtime.toISOString()
|
|
2788
|
+
});
|
|
2789
|
+
} catch {
|
|
2770
2790
|
}
|
|
2771
2791
|
}
|
|
2772
2792
|
entries.sort((a, b) => {
|
|
@@ -2780,45 +2800,15 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2780
2800
|
app.get(
|
|
2781
2801
|
"/api/sessions/:id/download",
|
|
2782
2802
|
async (req, reply) => {
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
} catch {
|
|
2787
|
-
reply.code(404);
|
|
2788
|
-
return { error: "Session not found" };
|
|
2789
|
-
}
|
|
2790
|
-
const relative = (req.query.path ?? "").replace(/^\/+/, "");
|
|
2791
|
-
if (!relative) {
|
|
2792
|
-
reply.code(400);
|
|
2793
|
-
return { error: "path parameter is required" };
|
|
2794
|
-
}
|
|
2795
|
-
const target = confineToRoot(cwd, relative);
|
|
2796
|
-
if (!target) {
|
|
2797
|
-
reply.code(400);
|
|
2798
|
-
return { error: "Invalid path" };
|
|
2799
|
-
}
|
|
2800
|
-
const realTarget = await realConfineToRoot(config.fileRoot, target);
|
|
2801
|
-
if (!realTarget) {
|
|
2802
|
-
reply.code(404);
|
|
2803
|
-
return { error: "File not found" };
|
|
2804
|
-
}
|
|
2805
|
-
let stat;
|
|
2806
|
-
try {
|
|
2807
|
-
stat = await fs12.stat(realTarget);
|
|
2808
|
-
} catch {
|
|
2809
|
-
reply.code(404);
|
|
2810
|
-
return { error: "File not found" };
|
|
2811
|
-
}
|
|
2812
|
-
if (!stat.isFile()) {
|
|
2813
|
-
reply.code(400);
|
|
2814
|
-
return { error: "Path is not a file" };
|
|
2815
|
-
}
|
|
2803
|
+
const resolved = await resolveSessionPath(manager, req.params.id, req.query.path, "file");
|
|
2804
|
+
if (!resolved.ok) return replyWith(reply, resolved);
|
|
2805
|
+
const { real: realTarget, stat } = resolved;
|
|
2816
2806
|
if (stat.size > MAX_DOWNLOAD_BYTES) {
|
|
2817
2807
|
reply.code(413);
|
|
2818
2808
|
return { error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 100 MB)` };
|
|
2819
2809
|
}
|
|
2820
|
-
const ext =
|
|
2821
|
-
const filename =
|
|
2810
|
+
const ext = path15.extname(realTarget);
|
|
2811
|
+
const filename = path15.basename(realTarget);
|
|
2822
2812
|
const asciiName = filename.replace(/[^\w.\-]/g, "_");
|
|
2823
2813
|
reply.header("Content-Type", mimeForExt(ext));
|
|
2824
2814
|
reply.header(
|
|
@@ -2832,40 +2822,10 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2832
2822
|
app.get(
|
|
2833
2823
|
"/api/sessions/:id/preview",
|
|
2834
2824
|
async (req, reply) => {
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
reply.code(404);
|
|
2840
|
-
return { error: "Session not found" };
|
|
2841
|
-
}
|
|
2842
|
-
const relative = (req.query.path ?? "").replace(/^\/+/, "");
|
|
2843
|
-
if (!relative) {
|
|
2844
|
-
reply.code(400);
|
|
2845
|
-
return { error: "path parameter is required" };
|
|
2846
|
-
}
|
|
2847
|
-
const target = confineToRoot(cwd, relative);
|
|
2848
|
-
if (!target) {
|
|
2849
|
-
reply.code(400);
|
|
2850
|
-
return { error: "Invalid path" };
|
|
2851
|
-
}
|
|
2852
|
-
const realTarget = await realConfineToRoot(config.fileRoot, target);
|
|
2853
|
-
if (!realTarget) {
|
|
2854
|
-
reply.code(404);
|
|
2855
|
-
return { error: "File not found" };
|
|
2856
|
-
}
|
|
2857
|
-
let stat;
|
|
2858
|
-
try {
|
|
2859
|
-
stat = await fs12.stat(realTarget);
|
|
2860
|
-
} catch {
|
|
2861
|
-
reply.code(404);
|
|
2862
|
-
return { error: "File not found" };
|
|
2863
|
-
}
|
|
2864
|
-
if (!stat.isFile()) {
|
|
2865
|
-
reply.code(400);
|
|
2866
|
-
return { error: "Path is not a file" };
|
|
2867
|
-
}
|
|
2868
|
-
const ext = path14.extname(realTarget).toLowerCase();
|
|
2825
|
+
const resolved = await resolveSessionPath(manager, req.params.id, req.query.path, "file");
|
|
2826
|
+
if (!resolved.ok) return replyWith(reply, resolved);
|
|
2827
|
+
const { real: realTarget, stat } = resolved;
|
|
2828
|
+
const ext = path15.extname(realTarget).toLowerCase();
|
|
2869
2829
|
const mime = mimeForExt(ext);
|
|
2870
2830
|
const isImage = mime.startsWith("image/");
|
|
2871
2831
|
const isPdf = mime === "application/pdf";
|
|
@@ -2894,7 +2854,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2894
2854
|
}
|
|
2895
2855
|
if (isImage || isPdf) {
|
|
2896
2856
|
const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
|
|
2897
|
-
reply.header("Cache-Control", "private,
|
|
2857
|
+
reply.header("Cache-Control", "private, no-cache");
|
|
2898
2858
|
reply.header("ETag", etag);
|
|
2899
2859
|
reply.header("Last-Modified", stat.mtime.toUTCString());
|
|
2900
2860
|
const ifNoneMatch = req.headers["if-none-match"];
|
|
@@ -2910,7 +2870,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2910
2870
|
if (kind === "binary") {
|
|
2911
2871
|
let fh;
|
|
2912
2872
|
try {
|
|
2913
|
-
fh = await
|
|
2873
|
+
fh = await fs13.open(realTarget, "r");
|
|
2914
2874
|
const buf = Buffer.alloc(Math.min(HEXDUMP_BYTES, stat.size));
|
|
2915
2875
|
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
2916
2876
|
const head = buf.subarray(0, bytesRead);
|
|
@@ -2949,7 +2909,7 @@ var init_workspace = __esm({
|
|
|
2949
2909
|
"server/src/routes/workspace.ts"() {
|
|
2950
2910
|
"use strict";
|
|
2951
2911
|
init_config();
|
|
2952
|
-
|
|
2912
|
+
init_confinedFile();
|
|
2953
2913
|
init_filekind();
|
|
2954
2914
|
MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
|
|
2955
2915
|
HEXDUMP_BYTES = 4096;
|
|
@@ -2960,30 +2920,30 @@ var init_workspace = __esm({
|
|
|
2960
2920
|
|
|
2961
2921
|
// server/src/routes/uploads.ts
|
|
2962
2922
|
import { createReadStream as createReadStream3, createWriteStream } from "node:fs";
|
|
2963
|
-
import
|
|
2964
|
-
import
|
|
2923
|
+
import fs14 from "node:fs/promises";
|
|
2924
|
+
import path16 from "node:path";
|
|
2965
2925
|
import crypto5 from "node:crypto";
|
|
2966
2926
|
import { execFile as execFile3 } from "node:child_process";
|
|
2967
2927
|
import { pipeline } from "node:stream/promises";
|
|
2968
2928
|
function uploadDirFor(sessionId) {
|
|
2969
|
-
return
|
|
2929
|
+
return path16.join(config.casperDataDir, "sessions", sessionId, "uploads");
|
|
2970
2930
|
}
|
|
2971
2931
|
function sanitizeName(raw) {
|
|
2972
|
-
const base =
|
|
2932
|
+
const base = path16.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
|
|
2973
2933
|
if (!base || base === "." || base === "..") return `upload-${Date.now()}`;
|
|
2974
2934
|
return base.replace(/^\.+/, "") || `upload-${Date.now()}`;
|
|
2975
2935
|
}
|
|
2976
2936
|
async function uniquePath(dir, name) {
|
|
2977
|
-
const ext =
|
|
2937
|
+
const ext = path16.extname(name);
|
|
2978
2938
|
const stem = name.slice(0, name.length - ext.length);
|
|
2979
2939
|
let candidate = name;
|
|
2980
2940
|
let n = 1;
|
|
2981
2941
|
while (true) {
|
|
2982
2942
|
try {
|
|
2983
|
-
await
|
|
2943
|
+
await fs14.access(path16.join(dir, candidate));
|
|
2984
2944
|
candidate = `${stem} (${n++})${ext}`;
|
|
2985
2945
|
} catch {
|
|
2986
|
-
return
|
|
2946
|
+
return path16.join(dir, candidate);
|
|
2987
2947
|
}
|
|
2988
2948
|
}
|
|
2989
2949
|
}
|
|
@@ -3003,7 +2963,7 @@ function fileType(absPath) {
|
|
|
3003
2963
|
async function sampleStrings(absPath, limit = 40) {
|
|
3004
2964
|
let fh;
|
|
3005
2965
|
try {
|
|
3006
|
-
fh = await
|
|
2966
|
+
fh = await fs14.open(absPath, "r");
|
|
3007
2967
|
const buf = Buffer.alloc(STRINGS_SCAN_BYTES);
|
|
3008
2968
|
const { bytesRead } = await fh.read(buf, 0, STRINGS_SCAN_BYTES, 0);
|
|
3009
2969
|
const out = [];
|
|
@@ -3041,7 +3001,7 @@ function registerUploadRoutes(app, manager) {
|
|
|
3041
3001
|
return { error: "Invalid session id" };
|
|
3042
3002
|
}
|
|
3043
3003
|
const uploadDir = uploadDirFor(req.params.id);
|
|
3044
|
-
await
|
|
3004
|
+
await fs14.mkdir(uploadDir, { recursive: true, mode: 448 });
|
|
3045
3005
|
const results = [];
|
|
3046
3006
|
const parts = req.files();
|
|
3047
3007
|
for await (const part of parts) {
|
|
@@ -3055,17 +3015,17 @@ function registerUploadRoutes(app, manager) {
|
|
|
3055
3015
|
return { error: `Failed to store ${name}` };
|
|
3056
3016
|
}
|
|
3057
3017
|
if (part.file.truncated) {
|
|
3058
|
-
await
|
|
3018
|
+
await fs14.rm(dest, { force: true });
|
|
3059
3019
|
reply.code(413);
|
|
3060
3020
|
return {
|
|
3061
3021
|
error: `${name} exceeds the max upload size (${Math.round(config.maxUploadBytes / 1024 / 1024)} MB)`
|
|
3062
3022
|
};
|
|
3063
3023
|
}
|
|
3064
|
-
const stat = await
|
|
3065
|
-
const ext =
|
|
3024
|
+
const stat = await fs14.stat(dest);
|
|
3025
|
+
const ext = path16.extname(dest);
|
|
3066
3026
|
const kind = classifyKind(dest);
|
|
3067
3027
|
const uploaded = {
|
|
3068
|
-
name:
|
|
3028
|
+
name: path16.basename(dest),
|
|
3069
3029
|
// Absolute, because the file sits outside the session's workspace: a
|
|
3070
3030
|
// relative path would resolve against the wrong root for both the agent
|
|
3071
3031
|
// and the image endpoint.
|
|
@@ -3122,97 +3082,8 @@ var init_health = __esm({
|
|
|
3122
3082
|
}
|
|
3123
3083
|
});
|
|
3124
3084
|
|
|
3125
|
-
// server/src/ws/dispatch.ts
|
|
3126
|
-
function send(socket, msg) {
|
|
3127
|
-
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify(msg));
|
|
3128
|
-
}
|
|
3129
|
-
async function handleClientMessage(socket, manager, sessionId, msg, watchers) {
|
|
3130
|
-
switch (msg.type) {
|
|
3131
|
-
case "ping":
|
|
3132
|
-
send(socket, { type: "pong" });
|
|
3133
|
-
return;
|
|
3134
|
-
case "hello":
|
|
3135
|
-
return;
|
|
3136
|
-
case "watch_paths":
|
|
3137
|
-
await watchers.sync(msg.paths);
|
|
3138
|
-
return;
|
|
3139
|
-
case "prompt":
|
|
3140
|
-
try {
|
|
3141
|
-
await manager.runPrompt(sessionId, msg.content);
|
|
3142
|
-
send(socket, { type: "ack", action: "prompt", ok: true });
|
|
3143
|
-
} catch (err) {
|
|
3144
|
-
send(socket, {
|
|
3145
|
-
type: "ack",
|
|
3146
|
-
action: "prompt",
|
|
3147
|
-
ok: false,
|
|
3148
|
-
error: err.message
|
|
3149
|
-
});
|
|
3150
|
-
}
|
|
3151
|
-
return;
|
|
3152
|
-
case "cancel":
|
|
3153
|
-
try {
|
|
3154
|
-
manager.cancel(sessionId);
|
|
3155
|
-
send(socket, { type: "ack", action: "cancel", ok: true });
|
|
3156
|
-
} catch (err) {
|
|
3157
|
-
send(socket, {
|
|
3158
|
-
type: "ack",
|
|
3159
|
-
action: "cancel",
|
|
3160
|
-
ok: false,
|
|
3161
|
-
error: err.message
|
|
3162
|
-
});
|
|
3163
|
-
}
|
|
3164
|
-
return;
|
|
3165
|
-
case "set_mode":
|
|
3166
|
-
try {
|
|
3167
|
-
await manager.setMode(sessionId, msg.modeId);
|
|
3168
|
-
send(socket, { type: "ack", action: "set_mode", ok: true });
|
|
3169
|
-
} catch (err) {
|
|
3170
|
-
send(socket, {
|
|
3171
|
-
type: "ack",
|
|
3172
|
-
action: "set_mode",
|
|
3173
|
-
ok: false,
|
|
3174
|
-
error: err.message
|
|
3175
|
-
});
|
|
3176
|
-
}
|
|
3177
|
-
return;
|
|
3178
|
-
case "set_model":
|
|
3179
|
-
try {
|
|
3180
|
-
await manager.setModel(sessionId, msg.modelId);
|
|
3181
|
-
send(socket, { type: "ack", action: "set_model", ok: true });
|
|
3182
|
-
} catch (err) {
|
|
3183
|
-
send(socket, {
|
|
3184
|
-
type: "ack",
|
|
3185
|
-
action: "set_model",
|
|
3186
|
-
ok: false,
|
|
3187
|
-
error: err.message
|
|
3188
|
-
});
|
|
3189
|
-
}
|
|
3190
|
-
return;
|
|
3191
|
-
case "exec_command":
|
|
3192
|
-
try {
|
|
3193
|
-
await manager.execCommand(sessionId, msg.command, msg.args);
|
|
3194
|
-
send(socket, { type: "ack", action: "exec_command", ok: true });
|
|
3195
|
-
} catch (err) {
|
|
3196
|
-
send(socket, {
|
|
3197
|
-
type: "ack",
|
|
3198
|
-
action: "exec_command",
|
|
3199
|
-
ok: false,
|
|
3200
|
-
error: err.message
|
|
3201
|
-
});
|
|
3202
|
-
}
|
|
3203
|
-
return;
|
|
3204
|
-
default:
|
|
3205
|
-
send(socket, { type: "error", message: `Unknown message type` });
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
var init_dispatch = __esm({
|
|
3209
|
-
"server/src/ws/dispatch.ts"() {
|
|
3210
|
-
"use strict";
|
|
3211
|
-
}
|
|
3212
|
-
});
|
|
3213
|
-
|
|
3214
3085
|
// server/src/ws/dirWatchers.ts
|
|
3215
|
-
import
|
|
3086
|
+
import fs15 from "node:fs";
|
|
3216
3087
|
function diffWatchSet(current, next) {
|
|
3217
3088
|
const have = new Set(current);
|
|
3218
3089
|
const want = new Set(next.slice(0, MAX_WATCHES));
|
|
@@ -3236,7 +3107,7 @@ function createDirWatchers(opts) {
|
|
|
3236
3107
|
const target = await opts.resolve(relative);
|
|
3237
3108
|
if (!target || closed) return;
|
|
3238
3109
|
try {
|
|
3239
|
-
const watcher =
|
|
3110
|
+
const watcher = fs15.watch(target, { persistent: false }, () => {
|
|
3240
3111
|
const existing = timers.get(relative);
|
|
3241
3112
|
if (existing) clearTimeout(existing);
|
|
3242
3113
|
timers.set(
|
|
@@ -3278,6 +3149,122 @@ var init_dirWatchers = __esm({
|
|
|
3278
3149
|
});
|
|
3279
3150
|
|
|
3280
3151
|
// server/src/ws/gateway.ts
|
|
3152
|
+
function send(socket, msg) {
|
|
3153
|
+
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify(msg));
|
|
3154
|
+
}
|
|
3155
|
+
function handleConnection(socket, manager, sessionId, startCursor) {
|
|
3156
|
+
let cursor = startCursor;
|
|
3157
|
+
let unsubscribe = null;
|
|
3158
|
+
let alive = true;
|
|
3159
|
+
let ready = false;
|
|
3160
|
+
const watchers = createDirWatchers({
|
|
3161
|
+
resolve: async (relative) => {
|
|
3162
|
+
try {
|
|
3163
|
+
return confineToRoot(await manager.getSessionCwd(sessionId), relative);
|
|
3164
|
+
} catch {
|
|
3165
|
+
return null;
|
|
3166
|
+
}
|
|
3167
|
+
},
|
|
3168
|
+
onChange: (path19) => send(socket, { type: "fs_changed", path: path19 })
|
|
3169
|
+
});
|
|
3170
|
+
const forward = (event) => {
|
|
3171
|
+
if (event.seq <= cursor) return;
|
|
3172
|
+
cursor = event.seq;
|
|
3173
|
+
send(socket, { type: "event", event });
|
|
3174
|
+
};
|
|
3175
|
+
const attach = async () => {
|
|
3176
|
+
try {
|
|
3177
|
+
await manager.ensureOpen(sessionId);
|
|
3178
|
+
} catch (err) {
|
|
3179
|
+
send(socket, { type: "error", message: err.message });
|
|
3180
|
+
socket.close(1011, "open failed");
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
const store = manager.getStore(sessionId);
|
|
3184
|
+
if (!store) {
|
|
3185
|
+
send(socket, { type: "error", message: "Session store unavailable" });
|
|
3186
|
+
socket.close(1011, "no store");
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
const { events, gap } = store.getSince(cursor);
|
|
3190
|
+
if (gap) {
|
|
3191
|
+
send(socket, {
|
|
3192
|
+
type: "resync",
|
|
3193
|
+
reason: "cursor older than buffer; refetch full transcript"
|
|
3194
|
+
});
|
|
3195
|
+
cursor = store.head();
|
|
3196
|
+
} else {
|
|
3197
|
+
for (const e of events) forward(e);
|
|
3198
|
+
}
|
|
3199
|
+
send(socket, { type: "replay_complete", head: store.head() });
|
|
3200
|
+
unsubscribe = manager.onEvent(sessionId, forward);
|
|
3201
|
+
ready = true;
|
|
3202
|
+
};
|
|
3203
|
+
void attach();
|
|
3204
|
+
const ack = async (action, run) => {
|
|
3205
|
+
try {
|
|
3206
|
+
await run();
|
|
3207
|
+
send(socket, { type: "ack", action, ok: true });
|
|
3208
|
+
} catch (err) {
|
|
3209
|
+
send(socket, { type: "ack", action, ok: false, error: err.message });
|
|
3210
|
+
}
|
|
3211
|
+
};
|
|
3212
|
+
const handle2 = (msg) => {
|
|
3213
|
+
switch (msg.type) {
|
|
3214
|
+
case "ping":
|
|
3215
|
+
return send(socket, { type: "pong" });
|
|
3216
|
+
case "watch_paths":
|
|
3217
|
+
return watchers.sync(msg.paths);
|
|
3218
|
+
case "prompt":
|
|
3219
|
+
return ack("prompt", () => manager.runPrompt(sessionId, msg.content));
|
|
3220
|
+
case "cancel":
|
|
3221
|
+
return ack("cancel", () => manager.cancel(sessionId));
|
|
3222
|
+
case "set_mode":
|
|
3223
|
+
return ack("set_mode", () => manager.setMode(sessionId, msg.modeId));
|
|
3224
|
+
case "set_model":
|
|
3225
|
+
return ack("set_model", () => manager.setModel(sessionId, msg.modelId));
|
|
3226
|
+
case "exec_command":
|
|
3227
|
+
return ack(
|
|
3228
|
+
"exec_command",
|
|
3229
|
+
() => manager.execCommand(sessionId, msg.command, msg.args)
|
|
3230
|
+
);
|
|
3231
|
+
default:
|
|
3232
|
+
return send(socket, { type: "error", message: "Unknown message type" });
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
const heartbeat = setInterval(() => {
|
|
3236
|
+
if (!alive) {
|
|
3237
|
+
socket.terminate();
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
alive = false;
|
|
3241
|
+
try {
|
|
3242
|
+
socket.ping();
|
|
3243
|
+
} catch {
|
|
3244
|
+
}
|
|
3245
|
+
}, HEARTBEAT_MS);
|
|
3246
|
+
heartbeat.unref?.();
|
|
3247
|
+
socket.on("pong", () => {
|
|
3248
|
+
alive = true;
|
|
3249
|
+
});
|
|
3250
|
+
socket.on("message", (raw) => {
|
|
3251
|
+
alive = true;
|
|
3252
|
+
if (!ready) return;
|
|
3253
|
+
let msg;
|
|
3254
|
+
try {
|
|
3255
|
+
msg = JSON.parse(raw.toString());
|
|
3256
|
+
} catch {
|
|
3257
|
+
send(socket, { type: "error", message: "Invalid JSON" });
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
void handle2(msg);
|
|
3261
|
+
});
|
|
3262
|
+
socket.on("close", () => {
|
|
3263
|
+
clearInterval(heartbeat);
|
|
3264
|
+
unsubscribe?.();
|
|
3265
|
+
watchers.close();
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3281
3268
|
function registerWsGateway(app, manager) {
|
|
3282
3269
|
app.get("/ws", { websocket: true }, (socket, req) => {
|
|
3283
3270
|
const query = req.query;
|
|
@@ -3286,92 +3273,17 @@ function registerWsGateway(app, manager) {
|
|
|
3286
3273
|
socket.close(1008, "Unauthorized");
|
|
3287
3274
|
return;
|
|
3288
3275
|
}
|
|
3289
|
-
|
|
3290
|
-
if (!sessionId) {
|
|
3276
|
+
if (!query.sessionId) {
|
|
3291
3277
|
send(socket, { type: "error", message: "Missing sessionId" });
|
|
3292
3278
|
socket.close(1008, "Missing sessionId");
|
|
3293
3279
|
return;
|
|
3294
3280
|
}
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
} catch {
|
|
3302
|
-
return null;
|
|
3303
|
-
}
|
|
3304
|
-
},
|
|
3305
|
-
onChange: (path18) => send(socket, { type: "fs_changed", path: path18 })
|
|
3306
|
-
});
|
|
3307
|
-
let alive = true;
|
|
3308
|
-
let ready = false;
|
|
3309
|
-
const forward = (event) => {
|
|
3310
|
-
if (event.seq <= cursor) return;
|
|
3311
|
-
cursor = event.seq;
|
|
3312
|
-
send(socket, { type: "event", event });
|
|
3313
|
-
};
|
|
3314
|
-
const attach = async () => {
|
|
3315
|
-
try {
|
|
3316
|
-
await manager.ensureOpen(sessionId);
|
|
3317
|
-
} catch (err) {
|
|
3318
|
-
send(socket, { type: "error", message: err.message });
|
|
3319
|
-
socket.close(1011, "open failed");
|
|
3320
|
-
return;
|
|
3321
|
-
}
|
|
3322
|
-
const store = manager.getStore(sessionId);
|
|
3323
|
-
if (!store) {
|
|
3324
|
-
send(socket, { type: "error", message: "Session store unavailable" });
|
|
3325
|
-
socket.close(1011, "no store");
|
|
3326
|
-
return;
|
|
3327
|
-
}
|
|
3328
|
-
const { events, gap } = store.getSince(cursor);
|
|
3329
|
-
if (gap) {
|
|
3330
|
-
send(socket, {
|
|
3331
|
-
type: "resync",
|
|
3332
|
-
reason: "cursor older than buffer; refetch full transcript"
|
|
3333
|
-
});
|
|
3334
|
-
cursor = store.head();
|
|
3335
|
-
} else {
|
|
3336
|
-
for (const e of events) forward(e);
|
|
3337
|
-
}
|
|
3338
|
-
send(socket, { type: "replay_complete", head: store.head() });
|
|
3339
|
-
unsubscribe = manager.onEvent(sessionId, forward);
|
|
3340
|
-
ready = true;
|
|
3341
|
-
};
|
|
3342
|
-
void attach();
|
|
3343
|
-
const heartbeat = setInterval(() => {
|
|
3344
|
-
if (!alive) {
|
|
3345
|
-
socket.terminate();
|
|
3346
|
-
return;
|
|
3347
|
-
}
|
|
3348
|
-
alive = false;
|
|
3349
|
-
try {
|
|
3350
|
-
socket.ping();
|
|
3351
|
-
} catch {
|
|
3352
|
-
}
|
|
3353
|
-
}, HEARTBEAT_MS);
|
|
3354
|
-
heartbeat.unref?.();
|
|
3355
|
-
socket.on("pong", () => {
|
|
3356
|
-
alive = true;
|
|
3357
|
-
});
|
|
3358
|
-
socket.on("message", (raw) => {
|
|
3359
|
-
alive = true;
|
|
3360
|
-
if (!ready) return;
|
|
3361
|
-
let msg;
|
|
3362
|
-
try {
|
|
3363
|
-
msg = JSON.parse(raw.toString());
|
|
3364
|
-
} catch {
|
|
3365
|
-
send(socket, { type: "error", message: "Invalid JSON" });
|
|
3366
|
-
return;
|
|
3367
|
-
}
|
|
3368
|
-
void handleClientMessage(socket, manager, sessionId, msg, watchers);
|
|
3369
|
-
});
|
|
3370
|
-
socket.on("close", () => {
|
|
3371
|
-
clearInterval(heartbeat);
|
|
3372
|
-
unsubscribe?.();
|
|
3373
|
-
watchers.close();
|
|
3374
|
-
});
|
|
3281
|
+
handleConnection(
|
|
3282
|
+
socket,
|
|
3283
|
+
manager,
|
|
3284
|
+
query.sessionId,
|
|
3285
|
+
Number.parseInt(query.cursor ?? "0", 10) || 0
|
|
3286
|
+
);
|
|
3375
3287
|
});
|
|
3376
3288
|
}
|
|
3377
3289
|
var HEARTBEAT_MS;
|
|
@@ -3379,7 +3291,6 @@ var init_gateway = __esm({
|
|
|
3379
3291
|
"server/src/ws/gateway.ts"() {
|
|
3380
3292
|
"use strict";
|
|
3381
3293
|
init_auth();
|
|
3382
|
-
init_dispatch();
|
|
3383
3294
|
init_dirWatchers();
|
|
3384
3295
|
init_paths2();
|
|
3385
3296
|
HEARTBEAT_MS = 2e4;
|
|
@@ -3387,7 +3298,7 @@ var init_gateway = __esm({
|
|
|
3387
3298
|
});
|
|
3388
3299
|
|
|
3389
3300
|
// server/src/app.ts
|
|
3390
|
-
import
|
|
3301
|
+
import fs16 from "node:fs";
|
|
3391
3302
|
import Fastify from "fastify";
|
|
3392
3303
|
import cors from "@fastify/cors";
|
|
3393
3304
|
import websocket from "@fastify/websocket";
|
|
@@ -3425,20 +3336,20 @@ async function buildApp() {
|
|
|
3425
3336
|
registerWorkspaceRoutes(app, manager);
|
|
3426
3337
|
registerUploadRoutes(app, manager);
|
|
3427
3338
|
registerWsGateway(app, manager);
|
|
3428
|
-
if (
|
|
3339
|
+
if (fs16.existsSync(config.webDist)) {
|
|
3429
3340
|
await app.register(fastifyStatic, {
|
|
3430
3341
|
root: config.webDist
|
|
3431
3342
|
});
|
|
3432
3343
|
app.addHook("onSend", async (req, reply) => {
|
|
3433
|
-
const
|
|
3434
|
-
if (
|
|
3344
|
+
const path19 = req.url.split("?")[0] ?? "";
|
|
3345
|
+
if (path19 === "/manifest.json" || path19.startsWith("/icons/")) {
|
|
3435
3346
|
reply.header("cache-control", "public, max-age=300");
|
|
3436
3347
|
}
|
|
3437
3348
|
});
|
|
3438
3349
|
app.setNotFoundHandler((req, reply) => {
|
|
3439
|
-
const
|
|
3440
|
-
const looksLikeAsset =
|
|
3441
|
-
if (
|
|
3350
|
+
const path19 = req.url.split("?")[0] ?? "";
|
|
3351
|
+
const looksLikeAsset = path19.startsWith("/assets/") || /\.[a-zA-Z0-9]+$/.test(path19);
|
|
3352
|
+
if (path19.startsWith("/api/") || path19.startsWith("/ws") || looksLikeAsset) {
|
|
3442
3353
|
reply.code(404).send({ error: "Not found" });
|
|
3443
3354
|
return;
|
|
3444
3355
|
}
|
|
@@ -3512,15 +3423,15 @@ __export(doctor_exports, {
|
|
|
3512
3423
|
mcpWiring: () => mcpWiring
|
|
3513
3424
|
});
|
|
3514
3425
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3515
|
-
import
|
|
3516
|
-
import
|
|
3426
|
+
import fs17 from "node:fs";
|
|
3427
|
+
import path17 from "node:path";
|
|
3517
3428
|
function which(bin) {
|
|
3518
|
-
if (bin.includes("/")) return
|
|
3519
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
3429
|
+
if (bin.includes("/")) return fs17.existsSync(bin) ? bin : void 0;
|
|
3430
|
+
for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
|
|
3520
3431
|
if (dir === "") continue;
|
|
3521
|
-
const candidate =
|
|
3432
|
+
const candidate = path17.join(dir, bin);
|
|
3522
3433
|
try {
|
|
3523
|
-
|
|
3434
|
+
fs17.accessSync(candidate, fs17.constants.X_OK);
|
|
3524
3435
|
return candidate;
|
|
3525
3436
|
} catch {
|
|
3526
3437
|
}
|
|
@@ -3544,7 +3455,7 @@ function mcpWiring(agentJson, resolve) {
|
|
|
3544
3455
|
return { ok: false, detail: `${command || "(no command)"} not found - widgets are unavailable` };
|
|
3545
3456
|
}
|
|
3546
3457
|
const script = args.find((a) => a.endsWith(".js"));
|
|
3547
|
-
if (script && !
|
|
3458
|
+
if (script && !fs17.existsSync(script)) {
|
|
3548
3459
|
return { ok: false, detail: `${script} is gone - delete the agent file and restart to rewrite it` };
|
|
3549
3460
|
}
|
|
3550
3461
|
return { ok: true, detail: script ?? `${command} ${args.join(" ")}` };
|
|
@@ -3576,7 +3487,7 @@ function checks() {
|
|
|
3576
3487
|
const settings = readSettings(config.configFile);
|
|
3577
3488
|
const hasToken = typeof settings.token === "string" && settings.token !== "";
|
|
3578
3489
|
out.push(
|
|
3579
|
-
|
|
3490
|
+
fs17.existsSync(config.configFile) ? {
|
|
3580
3491
|
ok: hasToken,
|
|
3581
3492
|
name: "settings",
|
|
3582
3493
|
detail: hasToken ? `${config.configFile} (token set)` : `${config.configFile} exists but has no token - auth is DISABLED`
|
|
@@ -3586,8 +3497,8 @@ function checks() {
|
|
|
3586
3497
|
detail: `${config.configFile} not found; a token is generated on first run`
|
|
3587
3498
|
}
|
|
3588
3499
|
);
|
|
3589
|
-
if (
|
|
3590
|
-
const mode =
|
|
3500
|
+
if (fs17.existsSync(config.configFile)) {
|
|
3501
|
+
const mode = fs17.statSync(config.configFile).mode & 511;
|
|
3591
3502
|
out.push({
|
|
3592
3503
|
ok: mode === 384,
|
|
3593
3504
|
name: "settings mode",
|
|
@@ -3595,10 +3506,10 @@ function checks() {
|
|
|
3595
3506
|
});
|
|
3596
3507
|
}
|
|
3597
3508
|
try {
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
const db2 =
|
|
3601
|
-
const size =
|
|
3509
|
+
fs17.mkdirSync(config.casperDataDir, { recursive: true });
|
|
3510
|
+
fs17.accessSync(config.casperDataDir, fs17.constants.W_OK);
|
|
3511
|
+
const db2 = path17.join(config.casperDataDir, "casper.db");
|
|
3512
|
+
const size = fs17.existsSync(db2) ? `, casper.db ${(fs17.statSync(db2).size / 1024).toFixed(0)} KB` : "";
|
|
3602
3513
|
out.push({ ok: true, name: "data dir", detail: `${config.casperDataDir} writable${size}` });
|
|
3603
3514
|
} catch (err) {
|
|
3604
3515
|
out.push({
|
|
@@ -3609,22 +3520,22 @@ function checks() {
|
|
|
3609
3520
|
});
|
|
3610
3521
|
}
|
|
3611
3522
|
out.push(
|
|
3612
|
-
|
|
3523
|
+
fs17.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
|
|
3613
3524
|
ok: false,
|
|
3614
3525
|
fatal: true,
|
|
3615
3526
|
name: "web app",
|
|
3616
3527
|
detail: `${config.webDist} not found - the package is incomplete or CASPER_WEB_DIST is wrong`
|
|
3617
3528
|
}
|
|
3618
3529
|
);
|
|
3619
|
-
const agent =
|
|
3620
|
-
const hasAgent =
|
|
3530
|
+
const agent = path17.join(config.kiroSessionsDir, "..", "..", "agents", "casper.json");
|
|
3531
|
+
const hasAgent = fs17.existsSync(agent);
|
|
3621
3532
|
out.push({
|
|
3622
3533
|
ok: hasAgent,
|
|
3623
3534
|
name: "casper agent",
|
|
3624
3535
|
detail: hasAgent ? agent : `${agent} missing - it is installed on first run`
|
|
3625
3536
|
});
|
|
3626
3537
|
if (hasAgent) {
|
|
3627
|
-
const wiring = mcpWiring(
|
|
3538
|
+
const wiring = mcpWiring(fs17.readFileSync(agent, "utf8"), which);
|
|
3628
3539
|
out.push({ ok: wiring.ok, name: "casper mcp", detail: wiring.detail });
|
|
3629
3540
|
}
|
|
3630
3541
|
return out;
|
|
@@ -4088,14 +3999,14 @@ var init_server2 = __esm({
|
|
|
4088
3999
|
"server/src/mcp/server.ts"() {
|
|
4089
4000
|
"use strict";
|
|
4090
4001
|
init_protocol();
|
|
4091
|
-
VERSION = true ? "0.
|
|
4002
|
+
VERSION = true ? "0.10.1" : "dev";
|
|
4092
4003
|
}
|
|
4093
4004
|
});
|
|
4094
4005
|
|
|
4095
4006
|
// server/src/cli/program.ts
|
|
4096
|
-
import
|
|
4007
|
+
import fs18 from "node:fs";
|
|
4097
4008
|
import os4 from "node:os";
|
|
4098
|
-
import
|
|
4009
|
+
import path18 from "node:path";
|
|
4099
4010
|
|
|
4100
4011
|
// server/node_modules/commander/lib/error.js
|
|
4101
4012
|
var CommanderError = class extends Error {
|
|
@@ -7208,9 +7119,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
7208
7119
|
* @param {string} [path]
|
|
7209
7120
|
* @return {(string|null|Command)}
|
|
7210
7121
|
*/
|
|
7211
|
-
executableDir(
|
|
7212
|
-
if (
|
|
7213
|
-
this._executableDir =
|
|
7122
|
+
executableDir(path19) {
|
|
7123
|
+
if (path19 === void 0) return this._executableDir;
|
|
7124
|
+
this._executableDir = path19;
|
|
7214
7125
|
return this;
|
|
7215
7126
|
}
|
|
7216
7127
|
/**
|
|
@@ -7468,7 +7379,7 @@ var program = new Command();
|
|
|
7468
7379
|
// server/src/cli/program.ts
|
|
7469
7380
|
init_paths();
|
|
7470
7381
|
init_settings();
|
|
7471
|
-
var VERSION2 = true ? "0.
|
|
7382
|
+
var VERSION2 = true ? "0.10.1" : "dev";
|
|
7472
7383
|
async function bootstrap() {
|
|
7473
7384
|
const file = configFilePath();
|
|
7474
7385
|
const settings = readSettings(file);
|
|
@@ -7514,8 +7425,8 @@ async function resetToken(value) {
|
|
|
7514
7425
|
updateSettings(file, { token });
|
|
7515
7426
|
process.stdout.write(`wrote a new token to ${file}
|
|
7516
7427
|
`);
|
|
7517
|
-
const dbFile =
|
|
7518
|
-
if (
|
|
7428
|
+
const dbFile = path18.join(dataDirPath(), "casper.db");
|
|
7429
|
+
if (fs18.existsSync(dbFile)) {
|
|
7519
7430
|
const { LoginStore: LoginStore2 } = await Promise.resolve().then(() => (init_logins(), logins_exports));
|
|
7520
7431
|
new LoginStore2().revokeAll();
|
|
7521
7432
|
process.stdout.write("revoked all device sessions\n");
|