@joeyshi12/casper 0.9.7 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/mcp.js +1 -1
- package/dist/server.js +565 -638
- package/dist/web/assets/{abnfDiagram-N423BO3Z-BNWwSSFp.js → abnfDiagram-N423BO3Z-DuqqTYEM.js} +1 -1
- package/dist/web/assets/{arc-CcwkM28l.js → arc-BbitKrIO.js} +1 -1
- package/dist/web/assets/{architectureDiagram-T3A2C74G-BTs-BMSL.js → architectureDiagram-T3A2C74G-Dc0p1FLK.js} +1 -1
- package/dist/web/assets/{blockDiagram-VBNYF7ZC-Cg3AKEAE.js → blockDiagram-VBNYF7ZC-A2tZMhwX.js} +1 -1
- package/dist/web/assets/{c4Diagram-5PPSVZJV-BeLOHTLt.js → c4Diagram-5PPSVZJV-BGYnWUNq.js} +1 -1
- package/dist/web/assets/channel-oev4WWHD.js +1 -0
- package/dist/web/assets/{chunk-2GRJ4B5K-BLdzjU64.js → chunk-2GRJ4B5K-DGcSgggV.js} +1 -1
- package/dist/web/assets/{chunk-2Q5K7J3B-OBq9M6-Y.js → chunk-2Q5K7J3B-Cqd5ykHS.js} +1 -1
- package/dist/web/assets/{chunk-5RXB4S5H-Bo5TlL5E.js → chunk-5RXB4S5H-4s-V0baK.js} +1 -1
- package/dist/web/assets/{chunk-5VM5RSS4-Bu73pV_h.js → chunk-5VM5RSS4-DN_MfTRV.js} +1 -1
- package/dist/web/assets/{chunk-6Q2QTUOP-zkF1AB2H.js → chunk-6Q2QTUOP-6-ncud5-.js} +1 -1
- package/dist/web/assets/{chunk-GF5L2VYU-1gox46V8.js → chunk-GF5L2VYU-CkCAkajU.js} +1 -1
- package/dist/web/assets/{chunk-JWPE2WC7-Co6JHAZe.js → chunk-JWPE2WC7-B2HT0tHT.js} +1 -1
- package/dist/web/assets/{chunk-KBJHAD2P-COImJ5cB.js → chunk-KBJHAD2P-DtYmpITE.js} +1 -1
- package/dist/web/assets/{chunk-RYQCIY6F-DwZzSkn2.js → chunk-RYQCIY6F-CqU4ZESr.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-D7J4Ua5K.js → chunk-XXDRQBXY-5zlfmd8I.js} +1 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-D4FIkEsQ.js +1 -0
- package/dist/web/assets/classDiagram-v2-OCEON4UE-D4FIkEsQ.js +1 -0
- package/dist/web/assets/{cose-bilkent-JH36ORCC-io_sMArY.js → cose-bilkent-JH36ORCC-BtXxufQe.js} +1 -1
- package/dist/web/assets/{cynefin-VYW2F7L2-2kI4W7NK.js → cynefin-VYW2F7L2-DFLsp7Rj.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-MW4NZA55-DDOMF8nO.js → cynefinDiagram-MW4NZA55-BiXfZ5zy.js} +1 -1
- package/dist/web/assets/{dagre-VZM6K2ZE-D8ePE05C.js → dagre-VZM6K2ZE-MBbBcp4r.js} +1 -1
- package/dist/web/assets/{diagram-7IWD3JNH-D7-_PTKo.js → diagram-7IWD3JNH-BBJQPRUY.js} +1 -1
- package/dist/web/assets/{diagram-B4RE2ZJO-CotIn3Ty.js → diagram-B4RE2ZJO-D39huys8.js} +1 -1
- package/dist/web/assets/{diagram-LBJQPF4R-DofOzTTJ.js → diagram-LBJQPF4R-BEI6m3xw.js} +1 -1
- package/dist/web/assets/{diagram-Q27KOJAE-CVkyN7TJ.js → diagram-Q27KOJAE-0mMWjfIY.js} +1 -1
- package/dist/web/assets/{diagram-UB23O5K3-BMeuDkaO.js → diagram-UB23O5K3-Cq_AT0df.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-BXEA7PRR-DprK65id.js → ebnfDiagram-BXEA7PRR-CspS7m6Z.js} +1 -1
- package/dist/web/assets/{erDiagram-JOGREHBK-D-hwXtn8.js → erDiagram-JOGREHBK-B6NxTPkb.js} +1 -1
- package/dist/web/assets/{flowDiagram-UKHOOZJN-BanZuk45.js → flowDiagram-UKHOOZJN-D3Pt3-cN.js} +1 -1
- package/dist/web/assets/{ganttDiagram-PKOTCBZU-DKdLDEUL.js → ganttDiagram-PKOTCBZU-CV4orYkm.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-CadXyKLa.js → gitGraphDiagram-DS77QQ5N-CANHeJZg.js} +1 -1
- package/dist/web/assets/index-CK4YbLbU.js +253 -0
- package/dist/web/assets/index-Cz-T9NEB.css +1 -0
- package/dist/web/assets/index-Dewf4nkF.js +3 -0
- package/dist/web/assets/{infoDiagram-6WML65LV-DguQBaFj.js → infoDiagram-6WML65LV-ySSVfb1E.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-sCmvl_Ov.js → ishikawaDiagram-WSZJBQD7-C8PCYjFX.js} +1 -1
- package/dist/web/assets/{journeyDiagram-NVQOT4AX-Dp3H0qDU.js → journeyDiagram-NVQOT4AX-BS_jBDBe.js} +1 -1
- package/dist/web/assets/{kanban-definition-27J2QSJJ-C6mTugp9.js → kanban-definition-27J2QSJJ-H6jhmFjt.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-YU9d9rPY.js} +1 -1
- package/dist/web/assets/{mermaid.core-BpVrpe6c.js → mermaid.core-DsId9yj5.js} +5 -5
- package/dist/web/assets/{mindmap-definition-FAOFIHXS-BPgOZVAx.js → mindmap-definition-FAOFIHXS-DVFyeioX.js} +1 -1
- package/dist/web/assets/{pegDiagram-VL7TDLO6-eXPwHM7z.js → pegDiagram-VL7TDLO6-BD4va82R.js} +1 -1
- package/dist/web/assets/{pieDiagram-7S7Q4E2Y-CFIMahU1.js → pieDiagram-7S7Q4E2Y-CZAefhVe.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-dnDjuXib.js → quadrantDiagram-CIZ2JOQS-D3RB1kgH.js} +1 -1
- package/dist/web/assets/{railroadDiagram-AXF67PYL-_3DCSEsR.js → railroadDiagram-AXF67PYL-Cvnf0WjF.js} +1 -1
- package/dist/web/assets/{requirementDiagram-LRYGKXZP-DimVO25E.js → requirementDiagram-LRYGKXZP-C4B6vv6V.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-W5VNT64P-DtCSNUnd.js → sankeyDiagram-W5VNT64P-C5xg3C0D.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-SI44F4Z6-DyZDw9rq.js → sequenceDiagram-SI44F4Z6-DjWE-t7c.js} +1 -1
- package/dist/web/assets/{sizeCapture-X5ZJPWSS-DsJVViPa.js → sizeCapture-X5ZJPWSS-Bf7ugfWa.js} +1 -1
- package/dist/web/assets/{stateDiagram-OKZ733FA-3W1WK-Lx.js → stateDiagram-OKZ733FA-DxEitXwP.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-BV8yVaCo.js +1 -0
- package/dist/web/assets/{swimlanes-SLNWSIFB-BdAToTPU.js → swimlanes-SLNWSIFB-DMW3T9iR.js} +2 -2
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-DZu5mMsy.js +8 -0
- package/dist/web/assets/{timeline-definition-Z64GVDOM-W9PH8xTz.js → timeline-definition-Z64GVDOM-whI5A_En.js} +1 -1
- package/dist/web/assets/{vennDiagram-T6HMQDX7-DztY6X4E.js → vennDiagram-T6HMQDX7-DGdGbcJw.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-T6FBY63Y-BVJp-LQ1.js → wardleyDiagram-T6FBY63Y-CTNpUHSX.js} +1 -1
- package/dist/web/assets/{xychartDiagram-ELKLHX3M-CUgAA_UM.js → xychartDiagram-ELKLHX3M-C2j32C85.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.
|
|
@@ -2174,6 +2168,20 @@ var init_auth = __esm({
|
|
|
2174
2168
|
// server/src/session/models.ts
|
|
2175
2169
|
import { execFile } from "node:child_process";
|
|
2176
2170
|
import { promisify } from "node:util";
|
|
2171
|
+
async function configuredDefault() {
|
|
2172
|
+
try {
|
|
2173
|
+
const { stdout } = await execFileAsync(config.kiroBin, [
|
|
2174
|
+
"settings",
|
|
2175
|
+
"chat.defaultModel",
|
|
2176
|
+
"-f",
|
|
2177
|
+
"json"
|
|
2178
|
+
]);
|
|
2179
|
+
const value = JSON.parse(stdout);
|
|
2180
|
+
return typeof value === "string" && value ? value : void 0;
|
|
2181
|
+
} catch {
|
|
2182
|
+
return void 0;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2177
2185
|
async function listModels(log) {
|
|
2178
2186
|
if (cache) return cache;
|
|
2179
2187
|
const { stdout } = await execFileAsync(
|
|
@@ -2182,6 +2190,8 @@ async function listModels(log) {
|
|
|
2182
2190
|
{ maxBuffer: 4 * 1024 * 1024 }
|
|
2183
2191
|
);
|
|
2184
2192
|
const parsed = JSON.parse(stdout);
|
|
2193
|
+
const configured = await configuredDefault();
|
|
2194
|
+
const preferred = parsed.models.some((m) => m.model_id === configured) ? configured : parsed.default_model;
|
|
2185
2195
|
cache = parsed.models.map((m, i) => ({
|
|
2186
2196
|
modelId: m.model_id,
|
|
2187
2197
|
modelName: m.model_name,
|
|
@@ -2189,9 +2199,9 @@ async function listModels(log) {
|
|
|
2189
2199
|
contextWindowTokens: m.context_window_tokens,
|
|
2190
2200
|
rateMultiplier: m.rate_multiplier,
|
|
2191
2201
|
rateUnit: m.rate_unit,
|
|
2192
|
-
isDefault: m.default_model ?? i === 0
|
|
2202
|
+
isDefault: preferred ? m.model_id === preferred : m.default_model ?? i === 0
|
|
2193
2203
|
}));
|
|
2194
|
-
log.info({ count: cache.length }, "loaded model list");
|
|
2204
|
+
log.info({ count: cache.length, default: preferred }, "loaded model list");
|
|
2195
2205
|
return cache;
|
|
2196
2206
|
}
|
|
2197
2207
|
async function kiroVersion() {
|
|
@@ -2283,13 +2293,242 @@ var init_agents2 = __esm({
|
|
|
2283
2293
|
}
|
|
2284
2294
|
});
|
|
2285
2295
|
|
|
2286
|
-
// server/src/
|
|
2296
|
+
// server/src/util/confinedFile.ts
|
|
2287
2297
|
import fs11 from "node:fs/promises";
|
|
2288
|
-
import { createReadStream } from "node:fs";
|
|
2289
2298
|
import path12 from "node:path";
|
|
2290
|
-
function
|
|
2291
|
-
|
|
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";
|
|
2292
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";
|
|
2293
2532
|
function registerFsRoutes(app) {
|
|
2294
2533
|
app.get(
|
|
2295
2534
|
"/api/fs/dirs",
|
|
@@ -2297,37 +2536,27 @@ function registerFsRoutes(app) {
|
|
|
2297
2536
|
const input = (req.query.path ?? "").trim();
|
|
2298
2537
|
const base = config.defaultCwd;
|
|
2299
2538
|
const endsWithSep = input.endsWith("/");
|
|
2300
|
-
const resolved = input ?
|
|
2301
|
-
const dir = endsWithSep || !input ? resolved :
|
|
2302
|
-
const prefix = endsWithSep || !input ? "" :
|
|
2303
|
-
const targetKind = await
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
}
|
|
2308
|
-
const realDir = await realConfineToRoot(config.fileRoot, dir);
|
|
2309
|
-
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);
|
|
2310
2546
|
return { dir, entries: [], target: resolved, targetKind };
|
|
2311
2547
|
}
|
|
2548
|
+
const realDir = listing.real;
|
|
2312
2549
|
let entries = [];
|
|
2313
2550
|
try {
|
|
2314
|
-
const dirents = await
|
|
2551
|
+
const dirents = await fs12.readdir(realDir, { withFileTypes: true });
|
|
2315
2552
|
const candidates = dirents.filter((d) => !d.name.startsWith("."));
|
|
2316
2553
|
const checks2 = await Promise.all(
|
|
2317
2554
|
candidates.map(async (d) => {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
const full = path12.join(realDir, d.name);
|
|
2321
|
-
const real = await realConfineToRoot(config.fileRoot, full);
|
|
2322
|
-
if (!real) return null;
|
|
2323
|
-
try {
|
|
2324
|
-
return (await fs11.stat(real)).isDirectory() ? d.name : null;
|
|
2325
|
-
} catch {
|
|
2326
|
-
return null;
|
|
2327
|
-
}
|
|
2555
|
+
const target = await classifyDirent(realDir, d, absoluteRoots());
|
|
2556
|
+
return target?.kind === "directory" ? d.name : null;
|
|
2328
2557
|
})
|
|
2329
2558
|
);
|
|
2330
|
-
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));
|
|
2331
2560
|
} catch {
|
|
2332
2561
|
entries = [];
|
|
2333
2562
|
}
|
|
@@ -2342,72 +2571,43 @@ function registerFsRoutes(app) {
|
|
|
2342
2571
|
reply.code(400);
|
|
2343
2572
|
return { error: "path parameter is required" };
|
|
2344
2573
|
}
|
|
2345
|
-
if (!
|
|
2574
|
+
if (!path14.isAbsolute(filePath)) {
|
|
2346
2575
|
reply.code(400);
|
|
2347
2576
|
return { error: "path must be absolute" };
|
|
2348
2577
|
}
|
|
2349
|
-
const
|
|
2350
|
-
if (
|
|
2351
|
-
reply.code(403);
|
|
2352
|
-
return { error: "Path outside allowed root" };
|
|
2353
|
-
}
|
|
2354
|
-
const ext = path12.extname(resolved).toLowerCase();
|
|
2355
|
-
const mime = IMAGE_MIMES[ext];
|
|
2356
|
-
if (!mime) {
|
|
2578
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
2579
|
+
if (classifyKind(filePath) !== "image") {
|
|
2357
2580
|
reply.code(400);
|
|
2358
2581
|
return { error: `Not a supported image type: ${ext}` };
|
|
2359
2582
|
}
|
|
2360
|
-
const
|
|
2361
|
-
if (!
|
|
2362
|
-
|
|
2363
|
-
return { error: "File not found" };
|
|
2364
|
-
}
|
|
2365
|
-
let stat;
|
|
2366
|
-
try {
|
|
2367
|
-
stat = await fs11.stat(real);
|
|
2368
|
-
} catch {
|
|
2369
|
-
reply.code(404);
|
|
2370
|
-
return { error: "File not found" };
|
|
2371
|
-
}
|
|
2372
|
-
if (!stat.isFile()) {
|
|
2373
|
-
reply.code(400);
|
|
2374
|
-
return { error: "Path is not a file" };
|
|
2375
|
-
}
|
|
2583
|
+
const resolvedImage = await resolveAbsolutePath(filePath, "file");
|
|
2584
|
+
if (!resolvedImage.ok) return replyWith(reply, resolvedImage);
|
|
2585
|
+
const { real, stat } = resolvedImage;
|
|
2376
2586
|
if (stat.size > MAX_IMAGE_BYTES) {
|
|
2377
2587
|
reply.code(413);
|
|
2378
2588
|
return { error: "Image too large" };
|
|
2379
2589
|
}
|
|
2380
2590
|
const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
|
|
2381
|
-
reply.header("Cache-Control", "private,
|
|
2591
|
+
reply.header("Cache-Control", "private, no-cache");
|
|
2382
2592
|
reply.header("ETag", etag);
|
|
2383
2593
|
reply.header("Last-Modified", stat.mtime.toUTCString());
|
|
2384
2594
|
if (req.headers["if-none-match"] === etag) {
|
|
2385
2595
|
reply.code(304);
|
|
2386
2596
|
return reply.send();
|
|
2387
2597
|
}
|
|
2388
|
-
reply.header("Content-Type",
|
|
2598
|
+
reply.header("Content-Type", mimeForExt(ext));
|
|
2389
2599
|
reply.header("Content-Length", stat.size);
|
|
2390
2600
|
return reply.send(createReadStream(real));
|
|
2391
2601
|
}
|
|
2392
2602
|
);
|
|
2393
2603
|
}
|
|
2394
|
-
var
|
|
2604
|
+
var MAX_IMAGE_BYTES;
|
|
2395
2605
|
var init_fs = __esm({
|
|
2396
2606
|
"server/src/routes/fs.ts"() {
|
|
2397
2607
|
"use strict";
|
|
2398
2608
|
init_config();
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
".png": "image/png",
|
|
2402
|
-
".jpg": "image/jpeg",
|
|
2403
|
-
".jpeg": "image/jpeg",
|
|
2404
|
-
".gif": "image/gif",
|
|
2405
|
-
".webp": "image/webp",
|
|
2406
|
-
".svg": "image/svg+xml",
|
|
2407
|
-
".bmp": "image/bmp",
|
|
2408
|
-
".ico": "image/x-icon",
|
|
2409
|
-
".avif": "image/avif"
|
|
2410
|
-
};
|
|
2609
|
+
init_confinedFile();
|
|
2610
|
+
init_filekind();
|
|
2411
2611
|
MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
2412
2612
|
}
|
|
2413
2613
|
});
|
|
@@ -2521,236 +2721,72 @@ var init_sessions = __esm({
|
|
|
2521
2721
|
}
|
|
2522
2722
|
});
|
|
2523
2723
|
|
|
2524
|
-
// server/src/
|
|
2525
|
-
import
|
|
2526
|
-
|
|
2527
|
-
|
|
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
|
+
};
|
|
2528
2732
|
}
|
|
2529
|
-
function
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
const
|
|
2533
|
-
const
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
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}|`);
|
|
2541
2750
|
}
|
|
2542
|
-
return
|
|
2543
|
-
}
|
|
2544
|
-
function classifyKind(name) {
|
|
2545
|
-
const ext = path13.extname(name).toLowerCase();
|
|
2546
|
-
if (ext in IMAGE_EXTS2) return "image";
|
|
2547
|
-
if (TEXT_EXTS.has(ext)) return "text";
|
|
2548
|
-
return "binary";
|
|
2549
|
-
}
|
|
2550
|
-
var IMAGE_EXTS2, TEXT_EXTS, MIME_MAP;
|
|
2551
|
-
var init_filekind = __esm({
|
|
2552
|
-
"server/src/util/filekind.ts"() {
|
|
2553
|
-
"use strict";
|
|
2554
|
-
IMAGE_EXTS2 = {
|
|
2555
|
-
".png": "image/png",
|
|
2556
|
-
".jpg": "image/jpeg",
|
|
2557
|
-
".jpeg": "image/jpeg",
|
|
2558
|
-
".gif": "image/gif",
|
|
2559
|
-
".webp": "image/webp",
|
|
2560
|
-
".svg": "image/svg+xml",
|
|
2561
|
-
".bmp": "image/bmp",
|
|
2562
|
-
".ico": "image/x-icon",
|
|
2563
|
-
".avif": "image/avif"
|
|
2564
|
-
};
|
|
2565
|
-
TEXT_EXTS = /* @__PURE__ */ new Set([
|
|
2566
|
-
".txt",
|
|
2567
|
-
".md",
|
|
2568
|
-
".markdown",
|
|
2569
|
-
".rst",
|
|
2570
|
-
".log",
|
|
2571
|
-
".csv",
|
|
2572
|
-
".tsv",
|
|
2573
|
-
".json",
|
|
2574
|
-
".jsonl",
|
|
2575
|
-
".yaml",
|
|
2576
|
-
".yml",
|
|
2577
|
-
".toml",
|
|
2578
|
-
".ini",
|
|
2579
|
-
".env",
|
|
2580
|
-
".cfg",
|
|
2581
|
-
".conf",
|
|
2582
|
-
".xml",
|
|
2583
|
-
".html",
|
|
2584
|
-
".htm",
|
|
2585
|
-
".css",
|
|
2586
|
-
".scss",
|
|
2587
|
-
".less",
|
|
2588
|
-
".js",
|
|
2589
|
-
".mjs",
|
|
2590
|
-
".cjs",
|
|
2591
|
-
".jsx",
|
|
2592
|
-
".ts",
|
|
2593
|
-
".tsx",
|
|
2594
|
-
".vue",
|
|
2595
|
-
".svelte",
|
|
2596
|
-
".py",
|
|
2597
|
-
".rb",
|
|
2598
|
-
".go",
|
|
2599
|
-
".rs",
|
|
2600
|
-
".java",
|
|
2601
|
-
".kt",
|
|
2602
|
-
".c",
|
|
2603
|
-
".h",
|
|
2604
|
-
".cpp",
|
|
2605
|
-
".hpp",
|
|
2606
|
-
".cc",
|
|
2607
|
-
".cs",
|
|
2608
|
-
".php",
|
|
2609
|
-
".swift",
|
|
2610
|
-
".scala",
|
|
2611
|
-
".sh",
|
|
2612
|
-
".bash",
|
|
2613
|
-
".zsh",
|
|
2614
|
-
".fish",
|
|
2615
|
-
".sql",
|
|
2616
|
-
".graphql",
|
|
2617
|
-
".proto",
|
|
2618
|
-
".dockerfile",
|
|
2619
|
-
".makefile",
|
|
2620
|
-
".gradle",
|
|
2621
|
-
".diff",
|
|
2622
|
-
".patch",
|
|
2623
|
-
".tex"
|
|
2624
|
-
]);
|
|
2625
|
-
MIME_MAP = {
|
|
2626
|
-
...IMAGE_EXTS2,
|
|
2627
|
-
".txt": "text/plain",
|
|
2628
|
-
".md": "text/markdown",
|
|
2629
|
-
".csv": "text/csv",
|
|
2630
|
-
".json": "application/json",
|
|
2631
|
-
".xml": "application/xml",
|
|
2632
|
-
".yaml": "text/yaml",
|
|
2633
|
-
".yml": "text/yaml",
|
|
2634
|
-
".html": "text/html",
|
|
2635
|
-
".css": "text/css",
|
|
2636
|
-
".js": "application/javascript",
|
|
2637
|
-
".mjs": "application/javascript",
|
|
2638
|
-
".ts": "text/typescript",
|
|
2639
|
-
".tsx": "text/typescript",
|
|
2640
|
-
".toml": "text/plain",
|
|
2641
|
-
".pdf": "application/pdf",
|
|
2642
|
-
".zip": "application/zip",
|
|
2643
|
-
".tar": "application/x-tar",
|
|
2644
|
-
".gz": "application/gzip",
|
|
2645
|
-
".wasm": "application/wasm",
|
|
2646
|
-
".exe": "application/vnd.microsoft.portable-executable",
|
|
2647
|
-
".dll": "application/vnd.microsoft.portable-executable",
|
|
2648
|
-
".so": "application/x-sharedlib",
|
|
2649
|
-
".bin": "application/octet-stream"
|
|
2650
|
-
};
|
|
2651
|
-
}
|
|
2652
|
-
});
|
|
2653
|
-
|
|
2654
|
-
// server/src/routes/workspace.ts
|
|
2655
|
-
import fs12 from "node:fs/promises";
|
|
2656
|
-
import { createReadStream as createReadStream2 } from "node:fs";
|
|
2657
|
-
import path14 from "node:path";
|
|
2658
|
-
function tooLargeForPreview(size, maxBytes) {
|
|
2659
|
-
return {
|
|
2660
|
-
error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
|
|
2661
|
-
};
|
|
2662
|
-
}
|
|
2663
|
-
function hexdump(buf) {
|
|
2664
|
-
const lines = [];
|
|
2665
|
-
for (let off = 0; off < buf.length; off += 16) {
|
|
2666
|
-
const slice = buf.subarray(off, off + 16);
|
|
2667
|
-
const hex = [];
|
|
2668
|
-
let ascii = "";
|
|
2669
|
-
for (let i = 0; i < 16; i++) {
|
|
2670
|
-
if (i < slice.length) {
|
|
2671
|
-
const b = slice[i];
|
|
2672
|
-
hex.push(b.toString(16).padStart(2, "0"));
|
|
2673
|
-
ascii += b >= 32 && b <= 126 ? String.fromCharCode(b) : ".";
|
|
2674
|
-
} else {
|
|
2675
|
-
hex.push(" ");
|
|
2676
|
-
}
|
|
2677
|
-
if (i === 7) hex.push("");
|
|
2678
|
-
}
|
|
2679
|
-
lines.push(`${off.toString(16).padStart(8, "0")} ${hex.join(" ")} |${ascii}|`);
|
|
2680
|
-
}
|
|
2681
|
-
return lines.join("\n");
|
|
2751
|
+
return lines.join("\n");
|
|
2682
2752
|
}
|
|
2683
2753
|
function registerWorkspaceRoutes(app, manager) {
|
|
2684
2754
|
app.get(
|
|
2685
2755
|
"/api/sessions/:id/tree",
|
|
2686
2756
|
async (req, reply) => {
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
const relative
|
|
2695
|
-
const target = confineToRoot(cwd, relative);
|
|
2696
|
-
if (!target) {
|
|
2697
|
-
reply.code(400);
|
|
2698
|
-
return { error: "Invalid path" };
|
|
2699
|
-
}
|
|
2700
|
-
const workspaceMissing = async () => {
|
|
2701
|
-
try {
|
|
2702
|
-
return !(await fs12.stat(cwd)).isDirectory();
|
|
2703
|
-
} catch {
|
|
2704
|
-
return true;
|
|
2705
|
-
}
|
|
2706
|
-
};
|
|
2707
|
-
const notFound = async () => {
|
|
2708
|
-
reply.code(404);
|
|
2709
|
-
return await workspaceMissing() ? { error: `Workspace folder no longer exists: ${cwd}` } : { error: "Directory not found" };
|
|
2710
|
-
};
|
|
2711
|
-
const realTarget = await realConfineToRoot(config.fileRoot, target);
|
|
2712
|
-
if (!realTarget) {
|
|
2713
|
-
return notFound();
|
|
2714
|
-
}
|
|
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;
|
|
2715
2765
|
let dirents;
|
|
2716
2766
|
try {
|
|
2717
|
-
dirents = await
|
|
2767
|
+
dirents = await fs13.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
|
|
2718
2768
|
} catch {
|
|
2719
|
-
return
|
|
2769
|
+
return replyWith(reply, await workspaceNotFound(cwd));
|
|
2720
2770
|
}
|
|
2721
2771
|
const entries = [];
|
|
2722
2772
|
for (const d of dirents) {
|
|
2723
|
-
const
|
|
2724
|
-
|
|
2725
|
-
const
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
const real = await realConfineToRoot(config.fileRoot, entryAbsolute);
|
|
2730
|
-
if (!real) continue;
|
|
2731
|
-
try {
|
|
2732
|
-
const st = await fs12.stat(real);
|
|
2733
|
-
kind = st.isDirectory() ? "directory" : st.isFile() ? "file" : null;
|
|
2734
|
-
statTarget = real;
|
|
2735
|
-
} catch {
|
|
2736
|
-
continue;
|
|
2737
|
-
}
|
|
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;
|
|
2738
2779
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
entries.push({
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
size: stat.size,
|
|
2750
|
-
modifiedAt: stat.mtime.toISOString()
|
|
2751
|
-
});
|
|
2752
|
-
} catch {
|
|
2753
|
-
}
|
|
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 {
|
|
2754
2790
|
}
|
|
2755
2791
|
}
|
|
2756
2792
|
entries.sort((a, b) => {
|
|
@@ -2764,45 +2800,15 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2764
2800
|
app.get(
|
|
2765
2801
|
"/api/sessions/:id/download",
|
|
2766
2802
|
async (req, reply) => {
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
} catch {
|
|
2771
|
-
reply.code(404);
|
|
2772
|
-
return { error: "Session not found" };
|
|
2773
|
-
}
|
|
2774
|
-
const relative = (req.query.path ?? "").replace(/^\/+/, "");
|
|
2775
|
-
if (!relative) {
|
|
2776
|
-
reply.code(400);
|
|
2777
|
-
return { error: "path parameter is required" };
|
|
2778
|
-
}
|
|
2779
|
-
const target = confineToRoot(cwd, relative);
|
|
2780
|
-
if (!target) {
|
|
2781
|
-
reply.code(400);
|
|
2782
|
-
return { error: "Invalid path" };
|
|
2783
|
-
}
|
|
2784
|
-
const realTarget = await realConfineToRoot(config.fileRoot, target);
|
|
2785
|
-
if (!realTarget) {
|
|
2786
|
-
reply.code(404);
|
|
2787
|
-
return { error: "File not found" };
|
|
2788
|
-
}
|
|
2789
|
-
let stat;
|
|
2790
|
-
try {
|
|
2791
|
-
stat = await fs12.stat(realTarget);
|
|
2792
|
-
} catch {
|
|
2793
|
-
reply.code(404);
|
|
2794
|
-
return { error: "File not found" };
|
|
2795
|
-
}
|
|
2796
|
-
if (!stat.isFile()) {
|
|
2797
|
-
reply.code(400);
|
|
2798
|
-
return { error: "Path is not a file" };
|
|
2799
|
-
}
|
|
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;
|
|
2800
2806
|
if (stat.size > MAX_DOWNLOAD_BYTES) {
|
|
2801
2807
|
reply.code(413);
|
|
2802
2808
|
return { error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 100 MB)` };
|
|
2803
2809
|
}
|
|
2804
|
-
const ext =
|
|
2805
|
-
const filename =
|
|
2810
|
+
const ext = path15.extname(realTarget);
|
|
2811
|
+
const filename = path15.basename(realTarget);
|
|
2806
2812
|
const asciiName = filename.replace(/[^\w.\-]/g, "_");
|
|
2807
2813
|
reply.header("Content-Type", mimeForExt(ext));
|
|
2808
2814
|
reply.header(
|
|
@@ -2816,40 +2822,10 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2816
2822
|
app.get(
|
|
2817
2823
|
"/api/sessions/:id/preview",
|
|
2818
2824
|
async (req, reply) => {
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
reply.code(404);
|
|
2824
|
-
return { error: "Session not found" };
|
|
2825
|
-
}
|
|
2826
|
-
const relative = (req.query.path ?? "").replace(/^\/+/, "");
|
|
2827
|
-
if (!relative) {
|
|
2828
|
-
reply.code(400);
|
|
2829
|
-
return { error: "path parameter is required" };
|
|
2830
|
-
}
|
|
2831
|
-
const target = confineToRoot(cwd, relative);
|
|
2832
|
-
if (!target) {
|
|
2833
|
-
reply.code(400);
|
|
2834
|
-
return { error: "Invalid path" };
|
|
2835
|
-
}
|
|
2836
|
-
const realTarget = await realConfineToRoot(config.fileRoot, target);
|
|
2837
|
-
if (!realTarget) {
|
|
2838
|
-
reply.code(404);
|
|
2839
|
-
return { error: "File not found" };
|
|
2840
|
-
}
|
|
2841
|
-
let stat;
|
|
2842
|
-
try {
|
|
2843
|
-
stat = await fs12.stat(realTarget);
|
|
2844
|
-
} catch {
|
|
2845
|
-
reply.code(404);
|
|
2846
|
-
return { error: "File not found" };
|
|
2847
|
-
}
|
|
2848
|
-
if (!stat.isFile()) {
|
|
2849
|
-
reply.code(400);
|
|
2850
|
-
return { error: "Path is not a file" };
|
|
2851
|
-
}
|
|
2852
|
-
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();
|
|
2853
2829
|
const mime = mimeForExt(ext);
|
|
2854
2830
|
const isImage = mime.startsWith("image/");
|
|
2855
2831
|
const isPdf = mime === "application/pdf";
|
|
@@ -2878,7 +2854,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2878
2854
|
}
|
|
2879
2855
|
if (isImage || isPdf) {
|
|
2880
2856
|
const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
|
|
2881
|
-
reply.header("Cache-Control", "private,
|
|
2857
|
+
reply.header("Cache-Control", "private, no-cache");
|
|
2882
2858
|
reply.header("ETag", etag);
|
|
2883
2859
|
reply.header("Last-Modified", stat.mtime.toUTCString());
|
|
2884
2860
|
const ifNoneMatch = req.headers["if-none-match"];
|
|
@@ -2894,7 +2870,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2894
2870
|
if (kind === "binary") {
|
|
2895
2871
|
let fh;
|
|
2896
2872
|
try {
|
|
2897
|
-
fh = await
|
|
2873
|
+
fh = await fs13.open(realTarget, "r");
|
|
2898
2874
|
const buf = Buffer.alloc(Math.min(HEXDUMP_BYTES, stat.size));
|
|
2899
2875
|
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
2900
2876
|
const head = buf.subarray(0, bytesRead);
|
|
@@ -2933,7 +2909,7 @@ var init_workspace = __esm({
|
|
|
2933
2909
|
"server/src/routes/workspace.ts"() {
|
|
2934
2910
|
"use strict";
|
|
2935
2911
|
init_config();
|
|
2936
|
-
|
|
2912
|
+
init_confinedFile();
|
|
2937
2913
|
init_filekind();
|
|
2938
2914
|
MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
|
|
2939
2915
|
HEXDUMP_BYTES = 4096;
|
|
@@ -2944,30 +2920,30 @@ var init_workspace = __esm({
|
|
|
2944
2920
|
|
|
2945
2921
|
// server/src/routes/uploads.ts
|
|
2946
2922
|
import { createReadStream as createReadStream3, createWriteStream } from "node:fs";
|
|
2947
|
-
import
|
|
2948
|
-
import
|
|
2923
|
+
import fs14 from "node:fs/promises";
|
|
2924
|
+
import path16 from "node:path";
|
|
2949
2925
|
import crypto5 from "node:crypto";
|
|
2950
2926
|
import { execFile as execFile3 } from "node:child_process";
|
|
2951
2927
|
import { pipeline } from "node:stream/promises";
|
|
2952
2928
|
function uploadDirFor(sessionId) {
|
|
2953
|
-
return
|
|
2929
|
+
return path16.join(config.casperDataDir, "sessions", sessionId, "uploads");
|
|
2954
2930
|
}
|
|
2955
2931
|
function sanitizeName(raw) {
|
|
2956
|
-
const base =
|
|
2932
|
+
const base = path16.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
|
|
2957
2933
|
if (!base || base === "." || base === "..") return `upload-${Date.now()}`;
|
|
2958
2934
|
return base.replace(/^\.+/, "") || `upload-${Date.now()}`;
|
|
2959
2935
|
}
|
|
2960
2936
|
async function uniquePath(dir, name) {
|
|
2961
|
-
const ext =
|
|
2937
|
+
const ext = path16.extname(name);
|
|
2962
2938
|
const stem = name.slice(0, name.length - ext.length);
|
|
2963
2939
|
let candidate = name;
|
|
2964
2940
|
let n = 1;
|
|
2965
2941
|
while (true) {
|
|
2966
2942
|
try {
|
|
2967
|
-
await
|
|
2943
|
+
await fs14.access(path16.join(dir, candidate));
|
|
2968
2944
|
candidate = `${stem} (${n++})${ext}`;
|
|
2969
2945
|
} catch {
|
|
2970
|
-
return
|
|
2946
|
+
return path16.join(dir, candidate);
|
|
2971
2947
|
}
|
|
2972
2948
|
}
|
|
2973
2949
|
}
|
|
@@ -2987,7 +2963,7 @@ function fileType(absPath) {
|
|
|
2987
2963
|
async function sampleStrings(absPath, limit = 40) {
|
|
2988
2964
|
let fh;
|
|
2989
2965
|
try {
|
|
2990
|
-
fh = await
|
|
2966
|
+
fh = await fs14.open(absPath, "r");
|
|
2991
2967
|
const buf = Buffer.alloc(STRINGS_SCAN_BYTES);
|
|
2992
2968
|
const { bytesRead } = await fh.read(buf, 0, STRINGS_SCAN_BYTES, 0);
|
|
2993
2969
|
const out = [];
|
|
@@ -3025,7 +3001,7 @@ function registerUploadRoutes(app, manager) {
|
|
|
3025
3001
|
return { error: "Invalid session id" };
|
|
3026
3002
|
}
|
|
3027
3003
|
const uploadDir = uploadDirFor(req.params.id);
|
|
3028
|
-
await
|
|
3004
|
+
await fs14.mkdir(uploadDir, { recursive: true, mode: 448 });
|
|
3029
3005
|
const results = [];
|
|
3030
3006
|
const parts = req.files();
|
|
3031
3007
|
for await (const part of parts) {
|
|
@@ -3039,17 +3015,17 @@ function registerUploadRoutes(app, manager) {
|
|
|
3039
3015
|
return { error: `Failed to store ${name}` };
|
|
3040
3016
|
}
|
|
3041
3017
|
if (part.file.truncated) {
|
|
3042
|
-
await
|
|
3018
|
+
await fs14.rm(dest, { force: true });
|
|
3043
3019
|
reply.code(413);
|
|
3044
3020
|
return {
|
|
3045
3021
|
error: `${name} exceeds the max upload size (${Math.round(config.maxUploadBytes / 1024 / 1024)} MB)`
|
|
3046
3022
|
};
|
|
3047
3023
|
}
|
|
3048
|
-
const stat = await
|
|
3049
|
-
const ext =
|
|
3024
|
+
const stat = await fs14.stat(dest);
|
|
3025
|
+
const ext = path16.extname(dest);
|
|
3050
3026
|
const kind = classifyKind(dest);
|
|
3051
3027
|
const uploaded = {
|
|
3052
|
-
name:
|
|
3028
|
+
name: path16.basename(dest),
|
|
3053
3029
|
// Absolute, because the file sits outside the session's workspace: a
|
|
3054
3030
|
// relative path would resolve against the wrong root for both the agent
|
|
3055
3031
|
// and the image endpoint.
|
|
@@ -3106,97 +3082,8 @@ var init_health = __esm({
|
|
|
3106
3082
|
}
|
|
3107
3083
|
});
|
|
3108
3084
|
|
|
3109
|
-
// server/src/ws/dispatch.ts
|
|
3110
|
-
function send(socket, msg) {
|
|
3111
|
-
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify(msg));
|
|
3112
|
-
}
|
|
3113
|
-
async function handleClientMessage(socket, manager, sessionId, msg, watchers) {
|
|
3114
|
-
switch (msg.type) {
|
|
3115
|
-
case "ping":
|
|
3116
|
-
send(socket, { type: "pong" });
|
|
3117
|
-
return;
|
|
3118
|
-
case "hello":
|
|
3119
|
-
return;
|
|
3120
|
-
case "watch_paths":
|
|
3121
|
-
await watchers.sync(msg.paths);
|
|
3122
|
-
return;
|
|
3123
|
-
case "prompt":
|
|
3124
|
-
try {
|
|
3125
|
-
await manager.runPrompt(sessionId, msg.content);
|
|
3126
|
-
send(socket, { type: "ack", action: "prompt", ok: true });
|
|
3127
|
-
} catch (err) {
|
|
3128
|
-
send(socket, {
|
|
3129
|
-
type: "ack",
|
|
3130
|
-
action: "prompt",
|
|
3131
|
-
ok: false,
|
|
3132
|
-
error: err.message
|
|
3133
|
-
});
|
|
3134
|
-
}
|
|
3135
|
-
return;
|
|
3136
|
-
case "cancel":
|
|
3137
|
-
try {
|
|
3138
|
-
manager.cancel(sessionId);
|
|
3139
|
-
send(socket, { type: "ack", action: "cancel", ok: true });
|
|
3140
|
-
} catch (err) {
|
|
3141
|
-
send(socket, {
|
|
3142
|
-
type: "ack",
|
|
3143
|
-
action: "cancel",
|
|
3144
|
-
ok: false,
|
|
3145
|
-
error: err.message
|
|
3146
|
-
});
|
|
3147
|
-
}
|
|
3148
|
-
return;
|
|
3149
|
-
case "set_mode":
|
|
3150
|
-
try {
|
|
3151
|
-
await manager.setMode(sessionId, msg.modeId);
|
|
3152
|
-
send(socket, { type: "ack", action: "set_mode", ok: true });
|
|
3153
|
-
} catch (err) {
|
|
3154
|
-
send(socket, {
|
|
3155
|
-
type: "ack",
|
|
3156
|
-
action: "set_mode",
|
|
3157
|
-
ok: false,
|
|
3158
|
-
error: err.message
|
|
3159
|
-
});
|
|
3160
|
-
}
|
|
3161
|
-
return;
|
|
3162
|
-
case "set_model":
|
|
3163
|
-
try {
|
|
3164
|
-
await manager.setModel(sessionId, msg.modelId);
|
|
3165
|
-
send(socket, { type: "ack", action: "set_model", ok: true });
|
|
3166
|
-
} catch (err) {
|
|
3167
|
-
send(socket, {
|
|
3168
|
-
type: "ack",
|
|
3169
|
-
action: "set_model",
|
|
3170
|
-
ok: false,
|
|
3171
|
-
error: err.message
|
|
3172
|
-
});
|
|
3173
|
-
}
|
|
3174
|
-
return;
|
|
3175
|
-
case "exec_command":
|
|
3176
|
-
try {
|
|
3177
|
-
await manager.execCommand(sessionId, msg.command, msg.args);
|
|
3178
|
-
send(socket, { type: "ack", action: "exec_command", ok: true });
|
|
3179
|
-
} catch (err) {
|
|
3180
|
-
send(socket, {
|
|
3181
|
-
type: "ack",
|
|
3182
|
-
action: "exec_command",
|
|
3183
|
-
ok: false,
|
|
3184
|
-
error: err.message
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
return;
|
|
3188
|
-
default:
|
|
3189
|
-
send(socket, { type: "error", message: `Unknown message type` });
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
var init_dispatch = __esm({
|
|
3193
|
-
"server/src/ws/dispatch.ts"() {
|
|
3194
|
-
"use strict";
|
|
3195
|
-
}
|
|
3196
|
-
});
|
|
3197
|
-
|
|
3198
3085
|
// server/src/ws/dirWatchers.ts
|
|
3199
|
-
import
|
|
3086
|
+
import fs15 from "node:fs";
|
|
3200
3087
|
function diffWatchSet(current, next) {
|
|
3201
3088
|
const have = new Set(current);
|
|
3202
3089
|
const want = new Set(next.slice(0, MAX_WATCHES));
|
|
@@ -3220,7 +3107,7 @@ function createDirWatchers(opts) {
|
|
|
3220
3107
|
const target = await opts.resolve(relative);
|
|
3221
3108
|
if (!target || closed) return;
|
|
3222
3109
|
try {
|
|
3223
|
-
const watcher =
|
|
3110
|
+
const watcher = fs15.watch(target, { persistent: false }, () => {
|
|
3224
3111
|
const existing = timers.get(relative);
|
|
3225
3112
|
if (existing) clearTimeout(existing);
|
|
3226
3113
|
timers.set(
|
|
@@ -3262,6 +3149,122 @@ var init_dirWatchers = __esm({
|
|
|
3262
3149
|
});
|
|
3263
3150
|
|
|
3264
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
|
+
}
|
|
3265
3268
|
function registerWsGateway(app, manager) {
|
|
3266
3269
|
app.get("/ws", { websocket: true }, (socket, req) => {
|
|
3267
3270
|
const query = req.query;
|
|
@@ -3270,92 +3273,17 @@ function registerWsGateway(app, manager) {
|
|
|
3270
3273
|
socket.close(1008, "Unauthorized");
|
|
3271
3274
|
return;
|
|
3272
3275
|
}
|
|
3273
|
-
|
|
3274
|
-
if (!sessionId) {
|
|
3276
|
+
if (!query.sessionId) {
|
|
3275
3277
|
send(socket, { type: "error", message: "Missing sessionId" });
|
|
3276
3278
|
socket.close(1008, "Missing sessionId");
|
|
3277
3279
|
return;
|
|
3278
3280
|
}
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
} catch {
|
|
3286
|
-
return null;
|
|
3287
|
-
}
|
|
3288
|
-
},
|
|
3289
|
-
onChange: (path18) => send(socket, { type: "fs_changed", path: path18 })
|
|
3290
|
-
});
|
|
3291
|
-
let alive = true;
|
|
3292
|
-
let ready = false;
|
|
3293
|
-
const forward = (event) => {
|
|
3294
|
-
if (event.seq <= cursor) return;
|
|
3295
|
-
cursor = event.seq;
|
|
3296
|
-
send(socket, { type: "event", event });
|
|
3297
|
-
};
|
|
3298
|
-
const attach = async () => {
|
|
3299
|
-
try {
|
|
3300
|
-
await manager.ensureOpen(sessionId);
|
|
3301
|
-
} catch (err) {
|
|
3302
|
-
send(socket, { type: "error", message: err.message });
|
|
3303
|
-
socket.close(1011, "open failed");
|
|
3304
|
-
return;
|
|
3305
|
-
}
|
|
3306
|
-
const store = manager.getStore(sessionId);
|
|
3307
|
-
if (!store) {
|
|
3308
|
-
send(socket, { type: "error", message: "Session store unavailable" });
|
|
3309
|
-
socket.close(1011, "no store");
|
|
3310
|
-
return;
|
|
3311
|
-
}
|
|
3312
|
-
const { events, gap } = store.getSince(cursor);
|
|
3313
|
-
if (gap) {
|
|
3314
|
-
send(socket, {
|
|
3315
|
-
type: "resync",
|
|
3316
|
-
reason: "cursor older than buffer; refetch full transcript"
|
|
3317
|
-
});
|
|
3318
|
-
cursor = store.head();
|
|
3319
|
-
} else {
|
|
3320
|
-
for (const e of events) forward(e);
|
|
3321
|
-
}
|
|
3322
|
-
send(socket, { type: "replay_complete", head: store.head() });
|
|
3323
|
-
unsubscribe = manager.onEvent(sessionId, forward);
|
|
3324
|
-
ready = true;
|
|
3325
|
-
};
|
|
3326
|
-
void attach();
|
|
3327
|
-
const heartbeat = setInterval(() => {
|
|
3328
|
-
if (!alive) {
|
|
3329
|
-
socket.terminate();
|
|
3330
|
-
return;
|
|
3331
|
-
}
|
|
3332
|
-
alive = false;
|
|
3333
|
-
try {
|
|
3334
|
-
socket.ping();
|
|
3335
|
-
} catch {
|
|
3336
|
-
}
|
|
3337
|
-
}, HEARTBEAT_MS);
|
|
3338
|
-
heartbeat.unref?.();
|
|
3339
|
-
socket.on("pong", () => {
|
|
3340
|
-
alive = true;
|
|
3341
|
-
});
|
|
3342
|
-
socket.on("message", (raw) => {
|
|
3343
|
-
alive = true;
|
|
3344
|
-
if (!ready) return;
|
|
3345
|
-
let msg;
|
|
3346
|
-
try {
|
|
3347
|
-
msg = JSON.parse(raw.toString());
|
|
3348
|
-
} catch {
|
|
3349
|
-
send(socket, { type: "error", message: "Invalid JSON" });
|
|
3350
|
-
return;
|
|
3351
|
-
}
|
|
3352
|
-
void handleClientMessage(socket, manager, sessionId, msg, watchers);
|
|
3353
|
-
});
|
|
3354
|
-
socket.on("close", () => {
|
|
3355
|
-
clearInterval(heartbeat);
|
|
3356
|
-
unsubscribe?.();
|
|
3357
|
-
watchers.close();
|
|
3358
|
-
});
|
|
3281
|
+
handleConnection(
|
|
3282
|
+
socket,
|
|
3283
|
+
manager,
|
|
3284
|
+
query.sessionId,
|
|
3285
|
+
Number.parseInt(query.cursor ?? "0", 10) || 0
|
|
3286
|
+
);
|
|
3359
3287
|
});
|
|
3360
3288
|
}
|
|
3361
3289
|
var HEARTBEAT_MS;
|
|
@@ -3363,7 +3291,6 @@ var init_gateway = __esm({
|
|
|
3363
3291
|
"server/src/ws/gateway.ts"() {
|
|
3364
3292
|
"use strict";
|
|
3365
3293
|
init_auth();
|
|
3366
|
-
init_dispatch();
|
|
3367
3294
|
init_dirWatchers();
|
|
3368
3295
|
init_paths2();
|
|
3369
3296
|
HEARTBEAT_MS = 2e4;
|
|
@@ -3371,7 +3298,7 @@ var init_gateway = __esm({
|
|
|
3371
3298
|
});
|
|
3372
3299
|
|
|
3373
3300
|
// server/src/app.ts
|
|
3374
|
-
import
|
|
3301
|
+
import fs16 from "node:fs";
|
|
3375
3302
|
import Fastify from "fastify";
|
|
3376
3303
|
import cors from "@fastify/cors";
|
|
3377
3304
|
import websocket from "@fastify/websocket";
|
|
@@ -3409,20 +3336,20 @@ async function buildApp() {
|
|
|
3409
3336
|
registerWorkspaceRoutes(app, manager);
|
|
3410
3337
|
registerUploadRoutes(app, manager);
|
|
3411
3338
|
registerWsGateway(app, manager);
|
|
3412
|
-
if (
|
|
3339
|
+
if (fs16.existsSync(config.webDist)) {
|
|
3413
3340
|
await app.register(fastifyStatic, {
|
|
3414
3341
|
root: config.webDist
|
|
3415
3342
|
});
|
|
3416
3343
|
app.addHook("onSend", async (req, reply) => {
|
|
3417
|
-
const
|
|
3418
|
-
if (
|
|
3344
|
+
const path19 = req.url.split("?")[0] ?? "";
|
|
3345
|
+
if (path19 === "/manifest.json" || path19.startsWith("/icons/")) {
|
|
3419
3346
|
reply.header("cache-control", "public, max-age=300");
|
|
3420
3347
|
}
|
|
3421
3348
|
});
|
|
3422
3349
|
app.setNotFoundHandler((req, reply) => {
|
|
3423
|
-
const
|
|
3424
|
-
const looksLikeAsset =
|
|
3425
|
-
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) {
|
|
3426
3353
|
reply.code(404).send({ error: "Not found" });
|
|
3427
3354
|
return;
|
|
3428
3355
|
}
|
|
@@ -3496,15 +3423,15 @@ __export(doctor_exports, {
|
|
|
3496
3423
|
mcpWiring: () => mcpWiring
|
|
3497
3424
|
});
|
|
3498
3425
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3499
|
-
import
|
|
3500
|
-
import
|
|
3426
|
+
import fs17 from "node:fs";
|
|
3427
|
+
import path17 from "node:path";
|
|
3501
3428
|
function which(bin) {
|
|
3502
|
-
if (bin.includes("/")) return
|
|
3503
|
-
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)) {
|
|
3504
3431
|
if (dir === "") continue;
|
|
3505
|
-
const candidate =
|
|
3432
|
+
const candidate = path17.join(dir, bin);
|
|
3506
3433
|
try {
|
|
3507
|
-
|
|
3434
|
+
fs17.accessSync(candidate, fs17.constants.X_OK);
|
|
3508
3435
|
return candidate;
|
|
3509
3436
|
} catch {
|
|
3510
3437
|
}
|
|
@@ -3528,7 +3455,7 @@ function mcpWiring(agentJson, resolve) {
|
|
|
3528
3455
|
return { ok: false, detail: `${command || "(no command)"} not found - widgets are unavailable` };
|
|
3529
3456
|
}
|
|
3530
3457
|
const script = args.find((a) => a.endsWith(".js"));
|
|
3531
|
-
if (script && !
|
|
3458
|
+
if (script && !fs17.existsSync(script)) {
|
|
3532
3459
|
return { ok: false, detail: `${script} is gone - delete the agent file and restart to rewrite it` };
|
|
3533
3460
|
}
|
|
3534
3461
|
return { ok: true, detail: script ?? `${command} ${args.join(" ")}` };
|
|
@@ -3560,7 +3487,7 @@ function checks() {
|
|
|
3560
3487
|
const settings = readSettings(config.configFile);
|
|
3561
3488
|
const hasToken = typeof settings.token === "string" && settings.token !== "";
|
|
3562
3489
|
out.push(
|
|
3563
|
-
|
|
3490
|
+
fs17.existsSync(config.configFile) ? {
|
|
3564
3491
|
ok: hasToken,
|
|
3565
3492
|
name: "settings",
|
|
3566
3493
|
detail: hasToken ? `${config.configFile} (token set)` : `${config.configFile} exists but has no token - auth is DISABLED`
|
|
@@ -3570,8 +3497,8 @@ function checks() {
|
|
|
3570
3497
|
detail: `${config.configFile} not found; a token is generated on first run`
|
|
3571
3498
|
}
|
|
3572
3499
|
);
|
|
3573
|
-
if (
|
|
3574
|
-
const mode =
|
|
3500
|
+
if (fs17.existsSync(config.configFile)) {
|
|
3501
|
+
const mode = fs17.statSync(config.configFile).mode & 511;
|
|
3575
3502
|
out.push({
|
|
3576
3503
|
ok: mode === 384,
|
|
3577
3504
|
name: "settings mode",
|
|
@@ -3579,10 +3506,10 @@ function checks() {
|
|
|
3579
3506
|
});
|
|
3580
3507
|
}
|
|
3581
3508
|
try {
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
const db2 =
|
|
3585
|
-
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` : "";
|
|
3586
3513
|
out.push({ ok: true, name: "data dir", detail: `${config.casperDataDir} writable${size}` });
|
|
3587
3514
|
} catch (err) {
|
|
3588
3515
|
out.push({
|
|
@@ -3593,22 +3520,22 @@ function checks() {
|
|
|
3593
3520
|
});
|
|
3594
3521
|
}
|
|
3595
3522
|
out.push(
|
|
3596
|
-
|
|
3523
|
+
fs17.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
|
|
3597
3524
|
ok: false,
|
|
3598
3525
|
fatal: true,
|
|
3599
3526
|
name: "web app",
|
|
3600
3527
|
detail: `${config.webDist} not found - the package is incomplete or CASPER_WEB_DIST is wrong`
|
|
3601
3528
|
}
|
|
3602
3529
|
);
|
|
3603
|
-
const agent =
|
|
3604
|
-
const hasAgent =
|
|
3530
|
+
const agent = path17.join(config.kiroSessionsDir, "..", "..", "agents", "casper.json");
|
|
3531
|
+
const hasAgent = fs17.existsSync(agent);
|
|
3605
3532
|
out.push({
|
|
3606
3533
|
ok: hasAgent,
|
|
3607
3534
|
name: "casper agent",
|
|
3608
3535
|
detail: hasAgent ? agent : `${agent} missing - it is installed on first run`
|
|
3609
3536
|
});
|
|
3610
3537
|
if (hasAgent) {
|
|
3611
|
-
const wiring = mcpWiring(
|
|
3538
|
+
const wiring = mcpWiring(fs17.readFileSync(agent, "utf8"), which);
|
|
3612
3539
|
out.push({ ok: wiring.ok, name: "casper mcp", detail: wiring.detail });
|
|
3613
3540
|
}
|
|
3614
3541
|
return out;
|
|
@@ -4072,14 +3999,14 @@ var init_server2 = __esm({
|
|
|
4072
3999
|
"server/src/mcp/server.ts"() {
|
|
4073
4000
|
"use strict";
|
|
4074
4001
|
init_protocol();
|
|
4075
|
-
VERSION = true ? "0.
|
|
4002
|
+
VERSION = true ? "0.10.0" : "dev";
|
|
4076
4003
|
}
|
|
4077
4004
|
});
|
|
4078
4005
|
|
|
4079
4006
|
// server/src/cli/program.ts
|
|
4080
|
-
import
|
|
4007
|
+
import fs18 from "node:fs";
|
|
4081
4008
|
import os4 from "node:os";
|
|
4082
|
-
import
|
|
4009
|
+
import path18 from "node:path";
|
|
4083
4010
|
|
|
4084
4011
|
// server/node_modules/commander/lib/error.js
|
|
4085
4012
|
var CommanderError = class extends Error {
|
|
@@ -7192,9 +7119,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
7192
7119
|
* @param {string} [path]
|
|
7193
7120
|
* @return {(string|null|Command)}
|
|
7194
7121
|
*/
|
|
7195
|
-
executableDir(
|
|
7196
|
-
if (
|
|
7197
|
-
this._executableDir =
|
|
7122
|
+
executableDir(path19) {
|
|
7123
|
+
if (path19 === void 0) return this._executableDir;
|
|
7124
|
+
this._executableDir = path19;
|
|
7198
7125
|
return this;
|
|
7199
7126
|
}
|
|
7200
7127
|
/**
|
|
@@ -7452,7 +7379,7 @@ var program = new Command();
|
|
|
7452
7379
|
// server/src/cli/program.ts
|
|
7453
7380
|
init_paths();
|
|
7454
7381
|
init_settings();
|
|
7455
|
-
var VERSION2 = true ? "0.
|
|
7382
|
+
var VERSION2 = true ? "0.10.0" : "dev";
|
|
7456
7383
|
async function bootstrap() {
|
|
7457
7384
|
const file = configFilePath();
|
|
7458
7385
|
const settings = readSettings(file);
|
|
@@ -7498,8 +7425,8 @@ async function resetToken(value) {
|
|
|
7498
7425
|
updateSettings(file, { token });
|
|
7499
7426
|
process.stdout.write(`wrote a new token to ${file}
|
|
7500
7427
|
`);
|
|
7501
|
-
const dbFile =
|
|
7502
|
-
if (
|
|
7428
|
+
const dbFile = path18.join(dataDirPath(), "casper.db");
|
|
7429
|
+
if (fs18.existsSync(dbFile)) {
|
|
7503
7430
|
const { LoginStore: LoginStore2 } = await Promise.resolve().then(() => (init_logins(), logins_exports));
|
|
7504
7431
|
new LoginStore2().revokeAll();
|
|
7505
7432
|
process.stdout.write("revoked all device sessions\n");
|