@joeyshi12/casper 0.11.1 → 0.13.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 +4 -1
- package/dist/agents/prompt.txt +1 -1
- package/dist/mcp.js +1 -1
- package/dist/server.js +617 -634
- package/dist/web/assets/{abnfDiagram-N423BO3Z-B88fK1c8.js → abnfDiagram-N423BO3Z-Ckdr5R00.js} +1 -1
- package/dist/web/assets/{arc-DMIevERD.js → arc-RD0bGtxd.js} +1 -1
- package/dist/web/assets/{architectureDiagram-T3A2C74G-CJ5mXlmv.js → architectureDiagram-T3A2C74G-CHkgCulK.js} +1 -1
- package/dist/web/assets/{blockDiagram-VBNYF7ZC-BssSdi9Q.js → blockDiagram-VBNYF7ZC-bs7XxVhZ.js} +1 -1
- package/dist/web/assets/{c4Diagram-5PPSVZJV-BJHrRcC9.js → c4Diagram-5PPSVZJV-sipmg1A6.js} +1 -1
- package/dist/web/assets/channel-D8Rxtl4g.js +1 -0
- package/dist/web/assets/{chunk-2GRJ4B5K-Bmq_IVc0.js → chunk-2GRJ4B5K-Dk5uhiWq.js} +1 -1
- package/dist/web/assets/{chunk-2Q5K7J3B-B2erbhym.js → chunk-2Q5K7J3B-RLD9D6CE.js} +1 -1
- package/dist/web/assets/{chunk-5RXB4S5H-B_DuY9SH.js → chunk-5RXB4S5H-Bisltr8Y.js} +1 -1
- package/dist/web/assets/{chunk-5VM5RSS4-BgiHQCU1.js → chunk-5VM5RSS4-BFuA55Yw.js} +1 -1
- package/dist/web/assets/{chunk-6Q2QTUOP-pmOmsjWC.js → chunk-6Q2QTUOP-Dgi0Ckwy.js} +1 -1
- package/dist/web/assets/{chunk-GF5L2VYU-B-PjTMWL.js → chunk-GF5L2VYU-sLBx1TNb.js} +1 -1
- package/dist/web/assets/{chunk-JWPE2WC7-Be9UnGmk.js → chunk-JWPE2WC7-BoIB6XgD.js} +1 -1
- package/dist/web/assets/{chunk-KBJHAD2P-DPxdXs9r.js → chunk-KBJHAD2P-DE2z1pFd.js} +1 -1
- package/dist/web/assets/{chunk-RYQCIY6F-BKc5gOjQ.js → chunk-RYQCIY6F-S_juj3_n.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-CoOnbv-S.js → chunk-XXDRQBXY-gCHzWtzi.js} +1 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-Ch31vHtu.js +1 -0
- package/dist/web/assets/classDiagram-v2-OCEON4UE-Ch31vHtu.js +1 -0
- package/dist/web/assets/{cose-bilkent-JH36ORCC-BZ5Oon1z.js → cose-bilkent-JH36ORCC-Dp9lkOsx.js} +1 -1
- package/dist/web/assets/{cynefin-VYW2F7L2-DlXmf5Bs.js → cynefin-VYW2F7L2-OwRF9q4d.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-MW4NZA55-ClZQ1oTx.js → cynefinDiagram-MW4NZA55-DQsh1pP_.js} +1 -1
- package/dist/web/assets/{dagre-VZM6K2ZE-CX3pDtYQ.js → dagre-VZM6K2ZE-BOwA_-xr.js} +1 -1
- package/dist/web/assets/{diagram-7IWD3JNH-DBelWFkJ.js → diagram-7IWD3JNH-DCcvWrT9.js} +1 -1
- package/dist/web/assets/{diagram-B4RE2ZJO-CDqvztqK.js → diagram-B4RE2ZJO-1TUDDf93.js} +1 -1
- package/dist/web/assets/{diagram-LBJQPF4R-DWL1uAJS.js → diagram-LBJQPF4R-CQ9CHasC.js} +1 -1
- package/dist/web/assets/{diagram-Q27KOJAE-BRsHqM1Z.js → diagram-Q27KOJAE-CnzspMbf.js} +1 -1
- package/dist/web/assets/{diagram-UB23O5K3-Ci1U9Hdl.js → diagram-UB23O5K3-DwSm7ENR.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-BXEA7PRR-CBPphElN.js → ebnfDiagram-BXEA7PRR-B88O4qYG.js} +1 -1
- package/dist/web/assets/{erDiagram-JOGREHBK-FI4byJC2.js → erDiagram-JOGREHBK-RVTjJMdw.js} +1 -1
- package/dist/web/assets/{flowDiagram-UKHOOZJN-BCKR_o2t.js → flowDiagram-UKHOOZJN-CAGMFzJU.js} +1 -1
- package/dist/web/assets/{ganttDiagram-PKOTCBZU-BYp35VGe.js → ganttDiagram-PKOTCBZU-CEvl6Pim.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-DS77QQ5N--AySchaL.js → gitGraphDiagram-DS77QQ5N-CH6kGWAl.js} +1 -1
- package/dist/web/assets/{index-D9ooBx1d.js → index-B1jqxtEG.js} +1 -1
- package/dist/web/assets/index-Bm1zLB-5.js +249 -0
- package/dist/web/assets/index-bZk3Q_Yv.css +1 -0
- package/dist/web/assets/{infoDiagram-6WML65LV-CbKYvHyN.js → infoDiagram-6WML65LV-DAcfWiSl.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-dLs0tgi6.js → ishikawaDiagram-WSZJBQD7-C0CNHY8o.js} +1 -1
- package/dist/web/assets/{journeyDiagram-NVQOT4AX-CXUsDb3x.js → journeyDiagram-NVQOT4AX-Dkf1b7P5.js} +1 -1
- package/dist/web/assets/{kanban-definition-27J2QSJJ-r33P8KpU.js → kanban-definition-27J2QSJJ-DEyWowZE.js} +1 -1
- package/dist/web/assets/{linear-s7N3jDHb.js → linear-BIoTAhlf.js} +1 -1
- package/dist/web/assets/{mermaid.core-Cw282Xmk.js → mermaid.core-DTmrl8Wn.js} +4 -4
- package/dist/web/assets/{mindmap-definition-FAOFIHXS-BXPAvM6U.js → mindmap-definition-FAOFIHXS-mw_tNbPO.js} +1 -1
- package/dist/web/assets/{pegDiagram-VL7TDLO6-D-H5hjvM.js → pegDiagram-VL7TDLO6-BhX4qWsU.js} +1 -1
- package/dist/web/assets/{pieDiagram-7S7Q4E2Y-D3W5_BlG.js → pieDiagram-7S7Q4E2Y-D8y4mlrR.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-BNSHhxkB.js → quadrantDiagram-CIZ2JOQS-eRi0JVMP.js} +1 -1
- package/dist/web/assets/{railroadDiagram-AXF67PYL-BvLjuIqc.js → railroadDiagram-AXF67PYL-BtWC75OM.js} +1 -1
- package/dist/web/assets/{requirementDiagram-LRYGKXZP-CZTpqoEk.js → requirementDiagram-LRYGKXZP-QqWK5Q2W.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-W5VNT64P-xvU-3Mf_.js → sankeyDiagram-W5VNT64P-DWPZuz_o.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-SI44F4Z6-Bpugvm4V.js → sequenceDiagram-SI44F4Z6-BjtTZ0wA.js} +1 -1
- package/dist/web/assets/{sizeCapture-X5ZJPWSS-B87TxLF7.js → sizeCapture-X5ZJPWSS-CHpioWdU.js} +1 -1
- package/dist/web/assets/{stateDiagram-OKZ733FA-BEQB_m5r.js → stateDiagram-OKZ733FA-CUyQMj6M.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-BStIRqFz.js +1 -0
- package/dist/web/assets/{swimlanes-SLNWSIFB-CVMCMeYR.js → swimlanes-SLNWSIFB-DDbnLkK8.js} +2 -2
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-D-e3uCkk.js +8 -0
- package/dist/web/assets/{timeline-definition-Z64GVDOM-Dylz4pg-.js → timeline-definition-Z64GVDOM-Cxc91WrS.js} +1 -1
- package/dist/web/assets/{vennDiagram-T6HMQDX7-BmS_JQjJ.js → vennDiagram-T6HMQDX7-CjOhqkxb.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-T6FBY63Y-C0qCz7i-.js → wardleyDiagram-T6FBY63Y-4Ag9jAUE.js} +1 -1
- package/dist/web/assets/{xychartDiagram-ELKLHX3M-aOG1xxkv.js → xychartDiagram-ELKLHX3M-BbQy0Bji.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +1 -2
- package/dist/web/assets/channel-CFhLIKKC.js +0 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-CyTqHDI_.js +0 -1
- package/dist/web/assets/classDiagram-v2-OCEON4UE-CyTqHDI_.js +0 -1
- package/dist/web/assets/index-Dd6MG4c6.css +0 -1
- package/dist/web/assets/index-DigNQUdR.js +0 -253
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-OtsvcH8I.js +0 -1
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-TYfbhlBN.js +0 -8
package/dist/server.js
CHANGED
|
@@ -29,7 +29,7 @@ var init_paths = __esm({
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
// server/src/cli/settings.ts
|
|
32
|
-
import
|
|
32
|
+
import crypto2 from "node:crypto";
|
|
33
33
|
import fs2 from "node:fs";
|
|
34
34
|
import path3 from "node:path";
|
|
35
35
|
function readSettings(file) {
|
|
@@ -50,7 +50,7 @@ function updateSettings(file, changes) {
|
|
|
50
50
|
fs2.renameSync(tmp, file);
|
|
51
51
|
}
|
|
52
52
|
function generateToken() {
|
|
53
|
-
return
|
|
53
|
+
return crypto2.randomBytes(24).toString("hex");
|
|
54
54
|
}
|
|
55
55
|
function printTokenBlock(token, heading) {
|
|
56
56
|
const line = "\u2500".repeat(Math.max(heading.length, token.length) + 2);
|
|
@@ -71,6 +71,17 @@ var init_settings = __esm({
|
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
// server/src/util/hash.ts
|
|
75
|
+
import { createHash } from "node:crypto";
|
|
76
|
+
function sha256(text2) {
|
|
77
|
+
return createHash("sha256").update(text2).digest("hex");
|
|
78
|
+
}
|
|
79
|
+
var init_hash = __esm({
|
|
80
|
+
"server/src/util/hash.ts"() {
|
|
81
|
+
"use strict";
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
74
85
|
// server/src/cli/agentFile.ts
|
|
75
86
|
var agentFile_exports = {};
|
|
76
87
|
__export(agentFile_exports, {
|
|
@@ -78,7 +89,6 @@ __export(agentFile_exports, {
|
|
|
78
89
|
agentPrompt: () => agentPrompt,
|
|
79
90
|
installAgentFile: () => installAgentFile
|
|
80
91
|
});
|
|
81
|
-
import crypto2 from "node:crypto";
|
|
82
92
|
import fs3 from "node:fs";
|
|
83
93
|
import path4 from "node:path";
|
|
84
94
|
import url from "node:url";
|
|
@@ -121,9 +131,6 @@ function agentConfig(prompt, mcp) {
|
|
|
121
131
|
model: null
|
|
122
132
|
};
|
|
123
133
|
}
|
|
124
|
-
function sha256(text2) {
|
|
125
|
-
return crypto2.createHash("sha256").update(text2).digest("hex");
|
|
126
|
-
}
|
|
127
134
|
function installAgentFile(home2, dataDir) {
|
|
128
135
|
const target = path4.join(home2, ".kiro", "agents", "casper.json");
|
|
129
136
|
const prompt = agentPrompt();
|
|
@@ -165,6 +172,7 @@ function installAgentFile(home2, dataDir) {
|
|
|
165
172
|
var init_agentFile = __esm({
|
|
166
173
|
"server/src/cli/agentFile.ts"() {
|
|
167
174
|
"use strict";
|
|
175
|
+
init_hash();
|
|
168
176
|
}
|
|
169
177
|
});
|
|
170
178
|
|
|
@@ -320,7 +328,7 @@ var init_config = __esm({
|
|
|
320
328
|
*/
|
|
321
329
|
defaultAgent: setting("DEFAULT_AGENT", "defaultAgent", "casper"),
|
|
322
330
|
/**
|
|
323
|
-
* Filesystem root the file endpoints (/api/fs/dirs, /api/fs/
|
|
331
|
+
* Filesystem root the file endpoints (/api/fs/dirs, /api/fs/file) are confined to; anything
|
|
324
332
|
* resolving outside it is rejected. Defaults to /, so browsing spans everything the process
|
|
325
333
|
* can read - set a narrower path to keep authenticated users out of system files such as /etc
|
|
326
334
|
* or SSH keys.
|
|
@@ -383,8 +391,9 @@ var init_db = __esm({
|
|
|
383
391
|
"use strict";
|
|
384
392
|
init_config();
|
|
385
393
|
SCHEMA = `
|
|
386
|
-
CREATE TABLE IF NOT EXISTS
|
|
387
|
-
|
|
394
|
+
CREATE TABLE IF NOT EXISTS chats (
|
|
395
|
+
chat_id TEXT PRIMARY KEY,
|
|
396
|
+
session_id TEXT UNIQUE,
|
|
388
397
|
title TEXT,
|
|
389
398
|
cwd TEXT
|
|
390
399
|
);
|
|
@@ -396,6 +405,15 @@ CREATE TABLE IF NOT EXISTS logins (
|
|
|
396
405
|
user_agent TEXT
|
|
397
406
|
);
|
|
398
407
|
CREATE INDEX IF NOT EXISTS logins_hash ON logins (hash);
|
|
408
|
+
CREATE TABLE IF NOT EXISTS message_attachments (
|
|
409
|
+
chat_id TEXT NOT NULL,
|
|
410
|
+
ordinal INTEGER NOT NULL,
|
|
411
|
+
path TEXT NOT NULL,
|
|
412
|
+
name TEXT NOT NULL,
|
|
413
|
+
size INTEGER NOT NULL,
|
|
414
|
+
kind TEXT NOT NULL,
|
|
415
|
+
PRIMARY KEY (chat_id, ordinal, path)
|
|
416
|
+
);
|
|
399
417
|
`;
|
|
400
418
|
}
|
|
401
419
|
});
|
|
@@ -405,10 +423,7 @@ var logins_exports = {};
|
|
|
405
423
|
__export(logins_exports, {
|
|
406
424
|
LoginStore: () => LoginStore
|
|
407
425
|
});
|
|
408
|
-
import {
|
|
409
|
-
function sha2562(s) {
|
|
410
|
-
return createHash("sha256").update(s).digest("hex");
|
|
411
|
-
}
|
|
426
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
412
427
|
function sameHash(a, b) {
|
|
413
428
|
if (a.length !== b.length) return false;
|
|
414
429
|
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
@@ -419,6 +434,7 @@ var init_logins = __esm({
|
|
|
419
434
|
"use strict";
|
|
420
435
|
init_config();
|
|
421
436
|
init_db();
|
|
437
|
+
init_hash();
|
|
422
438
|
LAST_SEEN_WRITE_INTERVAL_MS = 6e4;
|
|
423
439
|
str = (v) => typeof v === "string" ? v : "";
|
|
424
440
|
toRecord = (r) => ({
|
|
@@ -446,7 +462,7 @@ var init_logins = __esm({
|
|
|
446
462
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
447
463
|
const record = {
|
|
448
464
|
id: randomBytes(9).toString("base64url"),
|
|
449
|
-
hash:
|
|
465
|
+
hash: sha256(token),
|
|
450
466
|
createdAt: nowIso,
|
|
451
467
|
lastSeenAt: nowIso,
|
|
452
468
|
userAgent
|
|
@@ -464,7 +480,7 @@ var init_logins = __esm({
|
|
|
464
480
|
verify(token) {
|
|
465
481
|
if (!token) return null;
|
|
466
482
|
const now = Date.now();
|
|
467
|
-
const row = db().prepare("SELECT * FROM logins WHERE hash = ?").get(
|
|
483
|
+
const row = db().prepare("SELECT * FROM logins WHERE hash = ?").get(sha256(token));
|
|
468
484
|
if (!row) return null;
|
|
469
485
|
const record = toRecord(row);
|
|
470
486
|
if (now - Date.parse(record.lastSeenAt) > this.ttlMs()) {
|
|
@@ -480,7 +496,7 @@ var init_logins = __esm({
|
|
|
480
496
|
/** List all active devices, marking the one owning `currentToken`. */
|
|
481
497
|
list(currentToken) {
|
|
482
498
|
this.pruneExpired();
|
|
483
|
-
const currentHash = currentToken ?
|
|
499
|
+
const currentHash = currentToken ? sha256(currentToken) : void 0;
|
|
484
500
|
const rows = db().prepare("SELECT * FROM logins ORDER BY last_seen_at DESC").all();
|
|
485
501
|
return rows.map(toRecord).map((r) => ({
|
|
486
502
|
id: r.id,
|
|
@@ -493,7 +509,7 @@ var init_logins = __esm({
|
|
|
493
509
|
/** Revoke the device holding this token (used on logout). */
|
|
494
510
|
revokeToken(token) {
|
|
495
511
|
if (!token) return;
|
|
496
|
-
db().prepare("DELETE FROM logins WHERE hash = ?").run(
|
|
512
|
+
db().prepare("DELETE FROM logins WHERE hash = ?").run(sha256(token));
|
|
497
513
|
}
|
|
498
514
|
/** Revoke a device by its public id. Returns false if there was no such device. */
|
|
499
515
|
revokeId(id) {
|
|
@@ -660,45 +676,20 @@ var init_acp = __esm({
|
|
|
660
676
|
});
|
|
661
677
|
|
|
662
678
|
// shared/dist/attachments.js
|
|
663
|
-
function attachmentPaths(text2) {
|
|
664
|
-
const line = text2.split("\n").find((l) => l.startsWith(ATTACHMENTS_PREFIX));
|
|
665
|
-
if (!line)
|
|
666
|
-
return [];
|
|
667
|
-
return line.slice(ATTACHMENTS_PREFIX.length).split(", ").map((s) => s.trim()).filter(Boolean);
|
|
668
|
-
}
|
|
669
|
-
function imageAttachmentPaths(text2) {
|
|
670
|
-
return attachmentPaths(text2).filter((p) => {
|
|
671
|
-
const lower = p.toLowerCase();
|
|
672
|
-
return IMAGE_EXTS.some((ext) => lower.endsWith(ext));
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
679
|
function stripAttachmentsLine(text2) {
|
|
676
680
|
return text2.split("\n").filter((l) => !l.startsWith(ATTACHMENTS_PREFIX)).join("\n").trim();
|
|
677
681
|
}
|
|
678
|
-
var ATTACHMENTS_PREFIX
|
|
682
|
+
var ATTACHMENTS_PREFIX;
|
|
679
683
|
var init_attachments = __esm({
|
|
680
684
|
"shared/dist/attachments.js"() {
|
|
681
685
|
"use strict";
|
|
682
686
|
ATTACHMENTS_PREFIX = "Attached files: ";
|
|
683
|
-
IMAGE_EXTS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico", ".avif"];
|
|
684
687
|
}
|
|
685
688
|
});
|
|
686
689
|
|
|
687
690
|
// shared/dist/observability.js
|
|
688
691
|
function emptyObservabilitySnapshot() {
|
|
689
|
-
return {
|
|
690
|
-
turnStatus: "idle",
|
|
691
|
-
creditsSpent: 0,
|
|
692
|
-
lastTurnCredits: 0,
|
|
693
|
-
contextUsagePercentage: 0,
|
|
694
|
-
lastTurnDurationMs: 0,
|
|
695
|
-
subagents: [],
|
|
696
|
-
pendingStages: [],
|
|
697
|
-
mcpServers: [],
|
|
698
|
-
availableCommands: [],
|
|
699
|
-
oauthPrompts: [],
|
|
700
|
-
compacting: false
|
|
701
|
-
};
|
|
692
|
+
return { turnStatus: "idle", contextUsagePercentage: 0, compacting: false };
|
|
702
693
|
}
|
|
703
694
|
var init_observability = __esm({
|
|
704
695
|
"shared/dist/observability.js"() {
|
|
@@ -806,27 +797,32 @@ var init_agents = __esm({
|
|
|
806
797
|
}
|
|
807
798
|
});
|
|
808
799
|
|
|
809
|
-
// server/src/session/
|
|
810
|
-
import crypto3 from "node:crypto";
|
|
800
|
+
// server/src/session/chats.ts
|
|
811
801
|
import fs7 from "node:fs";
|
|
812
802
|
import path8 from "node:path";
|
|
813
|
-
function
|
|
814
|
-
return path8.join(config.casperDataDir, "
|
|
803
|
+
function chatsRoot() {
|
|
804
|
+
return path8.join(config.casperDataDir, "chats");
|
|
805
|
+
}
|
|
806
|
+
function chatDir(chatId) {
|
|
807
|
+
return path8.join(chatsRoot(), chatId);
|
|
815
808
|
}
|
|
816
|
-
function
|
|
817
|
-
return path8.join(
|
|
809
|
+
function chatUploadsDir(chatId) {
|
|
810
|
+
return path8.join(chatDir(chatId), "uploads");
|
|
818
811
|
}
|
|
819
|
-
function
|
|
820
|
-
|
|
821
|
-
|
|
812
|
+
function chatWorkspaceDir(chatId) {
|
|
813
|
+
return path8.join(chatDir(chatId), "workspace");
|
|
814
|
+
}
|
|
815
|
+
function createChatWorkspace(chatId) {
|
|
816
|
+
const dir = chatWorkspaceDir(chatId);
|
|
822
817
|
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
823
|
-
return
|
|
818
|
+
return dir;
|
|
824
819
|
}
|
|
825
|
-
function
|
|
826
|
-
|
|
820
|
+
function isManagedWorkspace(dir) {
|
|
821
|
+
const root = chatsRoot();
|
|
822
|
+
return dir === root || dir.startsWith(root + path8.sep);
|
|
827
823
|
}
|
|
828
|
-
var
|
|
829
|
-
"server/src/session/
|
|
824
|
+
var init_chats = __esm({
|
|
825
|
+
"server/src/session/chats.ts"() {
|
|
830
826
|
"use strict";
|
|
831
827
|
init_config();
|
|
832
828
|
}
|
|
@@ -868,6 +864,9 @@ async function realConfineToRoot(root, absPath) {
|
|
|
868
864
|
function isValidSessionId(id) {
|
|
869
865
|
return /^[A-Za-z0-9._-]+$/.test(id) && id !== "." && !id.includes("..");
|
|
870
866
|
}
|
|
867
|
+
function isValidChatId(id) {
|
|
868
|
+
return !!id && isValidSessionId(id);
|
|
869
|
+
}
|
|
871
870
|
var realRootCache;
|
|
872
871
|
var init_paths2 = __esm({
|
|
873
872
|
"server/src/util/paths.ts"() {
|
|
@@ -1083,9 +1082,9 @@ var init_KiroProcess = __esm({
|
|
|
1083
1082
|
this.child.stdout.on("error", () => {
|
|
1084
1083
|
});
|
|
1085
1084
|
}
|
|
1086
|
-
/** Perform the ACP initialize handshake.
|
|
1085
|
+
/** Perform the ACP initialize handshake. kiro's reply is not used. */
|
|
1087
1086
|
async initialize() {
|
|
1088
|
-
|
|
1087
|
+
await this.client.request(ACP_METHODS.initialize, {
|
|
1089
1088
|
protocolVersion: 1,
|
|
1090
1089
|
clientCapabilities: {
|
|
1091
1090
|
fs: { readTextFile: false, writeTextFile: false },
|
|
@@ -1095,7 +1094,6 @@ var init_KiroProcess = __esm({
|
|
|
1095
1094
|
// worth wiring to package.json.
|
|
1096
1095
|
clientInfo: { name: "casper", version: "0.5.0" }
|
|
1097
1096
|
});
|
|
1098
|
-
return result;
|
|
1099
1097
|
}
|
|
1100
1098
|
newSession(params) {
|
|
1101
1099
|
return this.client.request(ACP_METHODS.sessionNew, params);
|
|
@@ -1120,10 +1118,10 @@ var init_KiroProcess = __esm({
|
|
|
1120
1118
|
setModel(sessionId, modelId) {
|
|
1121
1119
|
return this.client.request(ACP_METHODS.sessionSetModel, { sessionId, modelId });
|
|
1122
1120
|
}
|
|
1123
|
-
execCommand(sessionId, command
|
|
1121
|
+
execCommand(sessionId, command) {
|
|
1124
1122
|
return this.client.request(ACP_METHODS.commandsExecute, {
|
|
1125
1123
|
sessionId,
|
|
1126
|
-
command: { command: command.replace(/^\//, ""), args:
|
|
1124
|
+
command: { command: command.replace(/^\//, ""), args: {} }
|
|
1127
1125
|
});
|
|
1128
1126
|
}
|
|
1129
1127
|
/** Cleanly shut down: close stdin (triggers kiro's graceful exit), then kill. */
|
|
@@ -1192,13 +1190,13 @@ var init_EventStore = __esm({
|
|
|
1192
1190
|
"use strict";
|
|
1193
1191
|
init_config();
|
|
1194
1192
|
EventStore = class extends EventEmitter4 {
|
|
1195
|
-
|
|
1193
|
+
chatId;
|
|
1196
1194
|
buffer = [];
|
|
1197
1195
|
capacity;
|
|
1198
1196
|
seq = 0;
|
|
1199
|
-
constructor(
|
|
1197
|
+
constructor(chatId) {
|
|
1200
1198
|
super();
|
|
1201
|
-
this.
|
|
1199
|
+
this.chatId = chatId;
|
|
1202
1200
|
this.capacity = config.eventBufferSize;
|
|
1203
1201
|
}
|
|
1204
1202
|
/** Append an event, assign it the next seq, and fan it out. */
|
|
@@ -1207,7 +1205,7 @@ var init_EventStore = __esm({
|
|
|
1207
1205
|
const event = {
|
|
1208
1206
|
seq: this.seq,
|
|
1209
1207
|
ts: Date.now(),
|
|
1210
|
-
|
|
1208
|
+
chatId: this.chatId,
|
|
1211
1209
|
payload
|
|
1212
1210
|
};
|
|
1213
1211
|
this.buffer.push(event);
|
|
@@ -1255,83 +1253,36 @@ var init_TurnState = __esm({
|
|
|
1255
1253
|
get() {
|
|
1256
1254
|
return this.snapshot;
|
|
1257
1255
|
}
|
|
1258
|
-
/** Apply one event; returns the updated snapshot. */
|
|
1259
1256
|
apply(payload) {
|
|
1260
1257
|
const s = this.snapshot;
|
|
1261
1258
|
switch (payload.kind) {
|
|
1262
|
-
case "turn_started":
|
|
1263
|
-
this.snapshot = { ...s, turnStatus: "running"
|
|
1259
|
+
case "turn_started":
|
|
1260
|
+
this.snapshot = { ...s, turnStatus: "running" };
|
|
1264
1261
|
break;
|
|
1265
|
-
|
|
1266
|
-
case "
|
|
1267
|
-
this.snapshot = { ...s, turnStatus: "idle"
|
|
1262
|
+
case "turn_ended":
|
|
1263
|
+
case "turn_error":
|
|
1264
|
+
this.snapshot = { ...s, turnStatus: "idle" };
|
|
1268
1265
|
break;
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
this.snapshot = { ...s, turnStatus: "idle", currentTurnStartedAt: void 0 };
|
|
1266
|
+
case "process_exited":
|
|
1267
|
+
this.snapshot = { ...s, turnStatus: "idle", compacting: false };
|
|
1272
1268
|
break;
|
|
1273
|
-
|
|
1274
|
-
case "process_exited": {
|
|
1275
|
-
this.snapshot = { ...s, turnStatus: "idle", currentTurnStartedAt: void 0 };
|
|
1276
|
-
break;
|
|
1277
|
-
}
|
|
1278
|
-
case "metadata": {
|
|
1279
|
-
const p = payload.params;
|
|
1280
|
-
const turnCredits = (p.meteringUsage ?? []).reduce((sum, m) => sum + m.value, 0);
|
|
1281
|
-
this.snapshot = {
|
|
1282
|
-
...s,
|
|
1283
|
-
contextUsagePercentage: p.contextUsagePercentage ?? s.contextUsagePercentage,
|
|
1284
|
-
// meteringUsage is emitted per-turn; accumulate into the session total.
|
|
1285
|
-
creditsSpent: turnCredits > 0 ? s.creditsSpent + turnCredits : s.creditsSpent,
|
|
1286
|
-
lastTurnCredits: turnCredits > 0 ? turnCredits : s.lastTurnCredits,
|
|
1287
|
-
lastTurnDurationMs: p.turnDurationMs ?? s.lastTurnDurationMs
|
|
1288
|
-
};
|
|
1289
|
-
break;
|
|
1290
|
-
}
|
|
1291
|
-
case "subagent_update": {
|
|
1269
|
+
case "metadata":
|
|
1292
1270
|
this.snapshot = {
|
|
1293
1271
|
...s,
|
|
1294
|
-
|
|
1295
|
-
pendingStages: payload.params.pendingStages
|
|
1272
|
+
contextUsagePercentage: payload.params.contextUsagePercentage ?? s.contextUsagePercentage
|
|
1296
1273
|
};
|
|
1297
1274
|
break;
|
|
1298
|
-
|
|
1299
|
-
case "mcp_health": {
|
|
1300
|
-
const { serverName, error } = payload.params;
|
|
1301
|
-
const status = payload.ok ? "initialized" : "failed";
|
|
1302
|
-
const next = s.mcpServers.filter((m) => m.serverName !== serverName);
|
|
1303
|
-
next.push({ serverName, status, error, updatedAt: Date.now() });
|
|
1304
|
-
this.snapshot = { ...s, mcpServers: next };
|
|
1305
|
-
break;
|
|
1306
|
-
}
|
|
1307
|
-
case "commands_available": {
|
|
1308
|
-
this.snapshot = { ...s, availableCommands: payload.params.commands };
|
|
1309
|
-
break;
|
|
1310
|
-
}
|
|
1311
|
-
case "compaction": {
|
|
1275
|
+
case "compaction":
|
|
1312
1276
|
this.snapshot = { ...s, compacting: payload.params.status.type === "started" };
|
|
1313
1277
|
break;
|
|
1314
|
-
}
|
|
1315
|
-
case "oauth_request": {
|
|
1316
|
-
const prompts = [
|
|
1317
|
-
...s.oauthPrompts,
|
|
1318
|
-
{
|
|
1319
|
-
serverName: payload.params.serverName,
|
|
1320
|
-
url: payload.params.url,
|
|
1321
|
-
createdAt: Date.now()
|
|
1322
|
-
}
|
|
1323
|
-
];
|
|
1324
|
-
this.snapshot = { ...s, oauthPrompts: prompts };
|
|
1325
|
-
break;
|
|
1326
|
-
}
|
|
1327
1278
|
default:
|
|
1328
1279
|
break;
|
|
1329
1280
|
}
|
|
1330
1281
|
return this.snapshot;
|
|
1331
1282
|
}
|
|
1332
|
-
/** Seed
|
|
1333
|
-
seed(
|
|
1334
|
-
this.snapshot = { ...this.snapshot,
|
|
1283
|
+
/** Seed context usage from kiro's persisted metadata on resume. */
|
|
1284
|
+
seed(contextUsagePercentage) {
|
|
1285
|
+
this.snapshot = { ...this.snapshot, contextUsagePercentage };
|
|
1335
1286
|
}
|
|
1336
1287
|
};
|
|
1337
1288
|
}
|
|
@@ -1369,10 +1320,6 @@ function renderableBlocks(content) {
|
|
|
1369
1320
|
function summarize(j) {
|
|
1370
1321
|
const state = j.session_state;
|
|
1371
1322
|
const turns = state?.conversation_metadata?.user_turn_metadatas ?? [];
|
|
1372
|
-
const creditsSpent = turns.reduce(
|
|
1373
|
-
(sum, t) => sum + (t.metering_usage ?? []).reduce((a, m) => a + m.value, 0),
|
|
1374
|
-
0
|
|
1375
|
-
);
|
|
1376
1323
|
const contextUsagePercentage = state?.rts_model_state?.context_usage_percentage ?? turns[turns.length - 1]?.context_usage_percentage;
|
|
1377
1324
|
return {
|
|
1378
1325
|
sessionId: j.session_id,
|
|
@@ -1381,42 +1328,18 @@ function summarize(j) {
|
|
|
1381
1328
|
cwd: j.cwd,
|
|
1382
1329
|
createdAt: j.created_at,
|
|
1383
1330
|
updatedAt: j.updated_at,
|
|
1384
|
-
liveness: "dormant",
|
|
1385
1331
|
agentId: state?.agent_name,
|
|
1386
1332
|
modelId: state?.rts_model_state?.model_info?.model_id,
|
|
1387
|
-
running: false,
|
|
1388
|
-
creditsSpent,
|
|
1389
1333
|
contextUsagePercentage
|
|
1390
1334
|
};
|
|
1391
1335
|
}
|
|
1392
|
-
async function listPersistedSessions(log) {
|
|
1393
|
-
let files;
|
|
1394
|
-
try {
|
|
1395
|
-
files = await fs9.readdir(config.kiroSessionsDir);
|
|
1396
|
-
} catch {
|
|
1397
|
-
return [];
|
|
1398
|
-
}
|
|
1399
|
-
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
1400
|
-
const summaries = [];
|
|
1401
|
-
await Promise.all(
|
|
1402
|
-
jsonFiles.map(async (f) => {
|
|
1403
|
-
try {
|
|
1404
|
-
const raw = await fs9.readFile(path10.join(config.kiroSessionsDir, f), "utf8");
|
|
1405
|
-
summaries.push(summarize(JSON.parse(raw)));
|
|
1406
|
-
} catch (err) {
|
|
1407
|
-
log.debug({ err, f }, "kiroFiles: skipping unreadable session file");
|
|
1408
|
-
}
|
|
1409
|
-
})
|
|
1410
|
-
);
|
|
1411
|
-
summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1412
|
-
return summaries;
|
|
1413
|
-
}
|
|
1414
1336
|
async function deletePersistedSession(sessionId) {
|
|
1415
1337
|
if (!isValidSessionId(sessionId)) return;
|
|
1416
1338
|
const targets = [
|
|
1417
1339
|
path10.join(config.kiroSessionsDir, `${sessionId}.json`),
|
|
1418
1340
|
path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`),
|
|
1419
1341
|
path10.join(config.kiroSessionsDir, `${sessionId}.history`),
|
|
1342
|
+
path10.join(config.kiroSessionsDir, `${sessionId}.lock`),
|
|
1420
1343
|
path10.join(config.kiroSessionsDir, sessionId)
|
|
1421
1344
|
];
|
|
1422
1345
|
await Promise.all(targets.map((f) => fs9.rm(f, { recursive: true, force: true })));
|
|
@@ -1433,48 +1356,54 @@ async function readPersistedSession(sessionId) {
|
|
|
1433
1356
|
return null;
|
|
1434
1357
|
}
|
|
1435
1358
|
}
|
|
1436
|
-
async function
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
const st = await fs9.stat(path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`));
|
|
1440
|
-
return st.size > 0;
|
|
1441
|
-
} catch {
|
|
1442
|
-
return false;
|
|
1443
|
-
}
|
|
1359
|
+
async function promptCount(sessionId) {
|
|
1360
|
+
const entries = await readJsonlEntries(sessionId);
|
|
1361
|
+
return entries.filter((e) => e.kind === "Prompt").length;
|
|
1444
1362
|
}
|
|
1445
|
-
async function
|
|
1363
|
+
async function readJsonlEntries(sessionId) {
|
|
1446
1364
|
if (!isValidSessionId(sessionId)) return [];
|
|
1447
1365
|
let raw;
|
|
1448
1366
|
try {
|
|
1449
|
-
raw = await fs9.readFile(
|
|
1450
|
-
path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`),
|
|
1451
|
-
"utf8"
|
|
1452
|
-
);
|
|
1367
|
+
raw = await fs9.readFile(path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`), "utf8");
|
|
1453
1368
|
} catch {
|
|
1454
1369
|
return [];
|
|
1455
1370
|
}
|
|
1456
|
-
const
|
|
1457
|
-
const toolsById = /* @__PURE__ */ new Map();
|
|
1371
|
+
const entries = [];
|
|
1458
1372
|
for (const line of raw.split("\n")) {
|
|
1459
1373
|
const trimmed = line.trim();
|
|
1460
1374
|
if (!trimmed) continue;
|
|
1461
|
-
let entry;
|
|
1462
1375
|
try {
|
|
1463
|
-
|
|
1376
|
+
entries.push(JSON.parse(trimmed));
|
|
1464
1377
|
} catch {
|
|
1465
|
-
continue;
|
|
1466
1378
|
}
|
|
1379
|
+
}
|
|
1380
|
+
return entries;
|
|
1381
|
+
}
|
|
1382
|
+
async function hasRecordedTurns(sessionId) {
|
|
1383
|
+
if (!isValidSessionId(sessionId)) return false;
|
|
1384
|
+
try {
|
|
1385
|
+
const st = await fs9.stat(path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`));
|
|
1386
|
+
return st.size > 0;
|
|
1387
|
+
} catch {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
async function hydrateTranscript(sessionId, attachments) {
|
|
1392
|
+
const items = [];
|
|
1393
|
+
let userOrdinal = 0;
|
|
1394
|
+
const toolsById = /* @__PURE__ */ new Map();
|
|
1395
|
+
for (const entry of await readJsonlEntries(sessionId)) {
|
|
1467
1396
|
const content = contentBlocks(entry.data.content);
|
|
1468
1397
|
const textOf = (kind) => content.filter((c) => c.kind === kind).map(blockText).join("");
|
|
1469
1398
|
const baseId = entry.data.message_id ?? `${items.length}`;
|
|
1470
1399
|
const ts = entry.data.meta?.timestamp;
|
|
1471
1400
|
const pushMsg = (msg) => items.push({ type: "message", message: msg });
|
|
1472
1401
|
if (entry.kind === "Prompt") {
|
|
1473
|
-
const
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1476
|
-
if (text2.trim() ||
|
|
1477
|
-
pushMsg({ id: `u-${baseId}`, role: "user", text: text2, timestamp: ts,
|
|
1402
|
+
const text2 = stripAttachmentsLine(textOf("text"));
|
|
1403
|
+
const attached = attachments?.get(userOrdinal);
|
|
1404
|
+
userOrdinal++;
|
|
1405
|
+
if (text2.trim() || attached?.length)
|
|
1406
|
+
pushMsg({ id: `u-${baseId}`, role: "user", text: text2, timestamp: ts, attachments: attached });
|
|
1478
1407
|
} else if (entry.kind === "AssistantMessage") {
|
|
1479
1408
|
const thinking = textOf("thinking");
|
|
1480
1409
|
if (thinking.trim())
|
|
@@ -1524,38 +1453,92 @@ var init_kiroFiles = __esm({
|
|
|
1524
1453
|
}
|
|
1525
1454
|
});
|
|
1526
1455
|
|
|
1527
|
-
// server/src/session/
|
|
1528
|
-
var
|
|
1529
|
-
var
|
|
1530
|
-
"server/src/session/
|
|
1456
|
+
// server/src/session/chatStore.ts
|
|
1457
|
+
var ChatStore;
|
|
1458
|
+
var init_chatStore = __esm({
|
|
1459
|
+
"server/src/session/chatStore.ts"() {
|
|
1531
1460
|
"use strict";
|
|
1532
1461
|
init_db();
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1462
|
+
ChatStore = class {
|
|
1463
|
+
/** Every chat. Ordering is kiro's business, applied once the file is joined on. */
|
|
1464
|
+
all() {
|
|
1465
|
+
return db().prepare("SELECT chat_id, session_id, title, cwd FROM chats").all().map((r) => ({ chatId: r.chat_id, sessionId: r.session_id, title: r.title, cwd: r.cwd }));
|
|
1466
|
+
}
|
|
1467
|
+
get(chatId) {
|
|
1468
|
+
const r = db().prepare("SELECT chat_id, session_id, title, cwd FROM chats WHERE chat_id = ?").get(chatId);
|
|
1469
|
+
return r ? { chatId: r.chat_id, sessionId: r.session_id, title: r.title, cwd: r.cwd } : void 0;
|
|
1470
|
+
}
|
|
1471
|
+
create(chatId) {
|
|
1472
|
+
db().prepare("INSERT INTO chats (chat_id) VALUES (?) ON CONFLICT(chat_id) DO NOTHING").run(chatId);
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* kiro names the session once it starts one; bind it to the chat that owns it. A session
|
|
1476
|
+
* belongs to exactly one chat, so any earlier claim on it is released.
|
|
1477
|
+
*/
|
|
1478
|
+
bindSession(chatId, sessionId) {
|
|
1479
|
+
db().prepare("UPDATE chats SET session_id = NULL WHERE session_id = ? AND chat_id <> ?").run(sessionId, chatId);
|
|
1480
|
+
this.write(chatId, "session_id", sessionId);
|
|
1481
|
+
}
|
|
1482
|
+
sessionIdForChat(chatId) {
|
|
1483
|
+
const row = db().prepare("SELECT session_id FROM chats WHERE chat_id = ?").get(chatId);
|
|
1484
|
+
return row?.session_id ?? void 0;
|
|
1536
1485
|
}
|
|
1537
|
-
|
|
1538
|
-
this.
|
|
1486
|
+
getTitle(chatId) {
|
|
1487
|
+
return this.read(chatId, "title");
|
|
1539
1488
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1489
|
+
setTitle(chatId, title) {
|
|
1490
|
+
this.write(chatId, "title", title);
|
|
1542
1491
|
}
|
|
1543
|
-
|
|
1544
|
-
this.
|
|
1492
|
+
getCwd(chatId) {
|
|
1493
|
+
return this.read(chatId, "cwd");
|
|
1545
1494
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
db().prepare("DELETE FROM sessions WHERE session_id = ?").run(sessionId);
|
|
1495
|
+
setCwd(chatId, cwd) {
|
|
1496
|
+
this.write(chatId, "cwd", cwd);
|
|
1549
1497
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1498
|
+
/** Record what was attached to one prompt. See db.ts for why the key is an ordinal. */
|
|
1499
|
+
setAttachments(chatId, ordinal, files) {
|
|
1500
|
+
if (files.length === 0) return;
|
|
1501
|
+
const stmt = db().prepare(
|
|
1502
|
+
`INSERT INTO message_attachments (chat_id, ordinal, path, name, size, kind)
|
|
1503
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1504
|
+
ON CONFLICT(chat_id, ordinal, path) DO NOTHING`
|
|
1505
|
+
);
|
|
1506
|
+
for (const f of files) {
|
|
1507
|
+
stmt.run(chatId, ordinal, f.path, f.name, f.size, f.kind);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
/** Every attachment in a chat, grouped by the message it belongs to. */
|
|
1511
|
+
attachmentsByChat(chatId) {
|
|
1512
|
+
const rows = db().prepare(
|
|
1513
|
+
`SELECT ordinal, path, name, size, kind FROM message_attachments
|
|
1514
|
+
WHERE chat_id = ? ORDER BY ordinal, path`
|
|
1515
|
+
).all(chatId);
|
|
1516
|
+
const out = /* @__PURE__ */ new Map();
|
|
1517
|
+
for (const r of rows) {
|
|
1518
|
+
const list = out.get(r.ordinal) ?? [];
|
|
1519
|
+
list.push({
|
|
1520
|
+
path: r.path,
|
|
1521
|
+
name: r.name,
|
|
1522
|
+
size: r.size,
|
|
1523
|
+
kind: r.kind
|
|
1524
|
+
});
|
|
1525
|
+
out.set(r.ordinal, list);
|
|
1526
|
+
}
|
|
1527
|
+
return out;
|
|
1528
|
+
}
|
|
1529
|
+
remove(chatId) {
|
|
1530
|
+
db().prepare("DELETE FROM chats WHERE chat_id = ?").run(chatId);
|
|
1531
|
+
db().prepare("DELETE FROM message_attachments WHERE chat_id = ?").run(chatId);
|
|
1532
|
+
}
|
|
1533
|
+
read(chatId, column) {
|
|
1534
|
+
const row = db().prepare(`SELECT ${column} AS value FROM chats WHERE chat_id = ?`).get(chatId);
|
|
1552
1535
|
return row?.value ?? void 0;
|
|
1553
1536
|
}
|
|
1554
|
-
write(
|
|
1537
|
+
write(chatId, column, value) {
|
|
1555
1538
|
db().prepare(
|
|
1556
|
-
`INSERT INTO
|
|
1557
|
-
ON CONFLICT(
|
|
1558
|
-
).run(
|
|
1539
|
+
`INSERT INTO chats (chat_id, ${column}) VALUES (?, ?)
|
|
1540
|
+
ON CONFLICT(chat_id) DO UPDATE SET ${column} = excluded.${column}`
|
|
1541
|
+
).run(chatId, value);
|
|
1559
1542
|
}
|
|
1560
1543
|
};
|
|
1561
1544
|
}
|
|
@@ -1590,18 +1573,8 @@ function mapNotification(n) {
|
|
|
1590
1573
|
return { kind: "session_update", update: n.params.update };
|
|
1591
1574
|
case KIRO_NOTIFICATIONS.metadata:
|
|
1592
1575
|
return { kind: "metadata", params: n.params };
|
|
1593
|
-
case KIRO_NOTIFICATIONS.subagentListUpdate:
|
|
1594
|
-
return { kind: "subagent_update", params: n.params };
|
|
1595
|
-
case KIRO_NOTIFICATIONS.mcpServerInitialized:
|
|
1596
|
-
return { kind: "mcp_health", params: n.params, ok: true };
|
|
1597
|
-
case KIRO_NOTIFICATIONS.mcpServerInitFailure:
|
|
1598
|
-
return { kind: "mcp_health", params: n.params, ok: false };
|
|
1599
|
-
case KIRO_NOTIFICATIONS.commandsAvailable:
|
|
1600
|
-
return { kind: "commands_available", params: n.params };
|
|
1601
1576
|
case KIRO_NOTIFICATIONS.compactionStatus:
|
|
1602
1577
|
return { kind: "compaction", params: n.params };
|
|
1603
|
-
case KIRO_NOTIFICATIONS.mcpOauthRequest:
|
|
1604
|
-
return { kind: "oauth_request", params: n.params };
|
|
1605
1578
|
default:
|
|
1606
1579
|
return null;
|
|
1607
1580
|
}
|
|
@@ -1623,13 +1596,13 @@ var init_SessionManager = __esm({
|
|
|
1623
1596
|
init_dist();
|
|
1624
1597
|
init_config();
|
|
1625
1598
|
init_agents();
|
|
1626
|
-
|
|
1599
|
+
init_chats();
|
|
1627
1600
|
init_paths2();
|
|
1628
1601
|
init_KiroProcess();
|
|
1629
1602
|
init_EventStore();
|
|
1630
1603
|
init_TurnState();
|
|
1631
1604
|
init_kiroFiles();
|
|
1632
|
-
|
|
1605
|
+
init_chatStore();
|
|
1633
1606
|
Session = class {
|
|
1634
1607
|
sessionId;
|
|
1635
1608
|
store;
|
|
@@ -1688,7 +1661,7 @@ var init_SessionManager = __esm({
|
|
|
1688
1661
|
SessionManager = class {
|
|
1689
1662
|
sessions = /* @__PURE__ */ new Map();
|
|
1690
1663
|
log;
|
|
1691
|
-
store = new
|
|
1664
|
+
store = new ChatStore();
|
|
1692
1665
|
spawnProcess;
|
|
1693
1666
|
constructor(log, opts = {}) {
|
|
1694
1667
|
this.log = log;
|
|
@@ -1698,62 +1671,53 @@ var init_SessionManager = __esm({
|
|
|
1698
1671
|
* What a session is called, from every read path. The folder is skipped for a workspace of
|
|
1699
1672
|
* ours, whose name is a uuid.
|
|
1700
1673
|
*/
|
|
1701
|
-
titleOf(
|
|
1674
|
+
titleOf(chat, parts) {
|
|
1702
1675
|
return resolveSessionTitle({
|
|
1703
|
-
override:
|
|
1676
|
+
override: chat.title ?? void 0,
|
|
1704
1677
|
kiroTitle: parts.kiroTitle,
|
|
1705
1678
|
firstPrompt: parts.firstPrompt,
|
|
1706
|
-
folder:
|
|
1679
|
+
folder: isManagedWorkspace(parts.cwd) ? void 0 : path11.basename(parts.cwd)
|
|
1707
1680
|
});
|
|
1708
1681
|
}
|
|
1709
|
-
|
|
1710
|
-
renameSession(sessionId, title) {
|
|
1682
|
+
renameChat(chatId, title) {
|
|
1711
1683
|
const clean = sanitizeTitle(title);
|
|
1712
|
-
this.store.setTitle(
|
|
1713
|
-
const s = this.sessions.get(
|
|
1684
|
+
this.store.setTitle(chatId, clean);
|
|
1685
|
+
const s = this.sessions.get(this.store.sessionIdForChat(chatId) ?? "");
|
|
1714
1686
|
if (s) s.title = clean || s.title;
|
|
1715
1687
|
}
|
|
1716
1688
|
/**
|
|
1717
|
-
* Re-point a session at a different working directory
|
|
1718
|
-
* or deleted. Validates and creates the target exactly like a new session's cwd, so a
|
|
1719
|
-
* folder is only created on this explicit action. Any live process was spawned with the
|
|
1689
|
+
* Re-point a session at a different working directory. Any live process was spawned with the
|
|
1720
1690
|
* old cwd, so it is disposed and the next turn respawns in the new one, transcript intact.
|
|
1721
1691
|
*/
|
|
1722
|
-
async
|
|
1692
|
+
async setChatCwd(chatId, input) {
|
|
1723
1693
|
const resolved = resolveCwd(input);
|
|
1724
|
-
const s = await this.ensureOpen(
|
|
1725
|
-
this.store.setCwd(
|
|
1694
|
+
const s = await this.ensureOpen(chatId);
|
|
1695
|
+
this.store.setCwd(chatId, resolved);
|
|
1726
1696
|
if (s.cwd !== resolved) {
|
|
1727
1697
|
s.cwd = resolved;
|
|
1728
1698
|
s.proc?.dispose();
|
|
1729
1699
|
s.proc = void 0;
|
|
1730
|
-
this.log.info({
|
|
1700
|
+
this.log.info({ chatId, cwd: resolved }, "chat working directory changed");
|
|
1731
1701
|
}
|
|
1732
1702
|
return resolved;
|
|
1733
1703
|
}
|
|
1734
1704
|
/**
|
|
1735
|
-
* Restart the session's kiro child, so
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
* No ACP method does this. kiro advertises no reload command, and `session/set_mode`
|
|
1740
|
-
* leaves the MCP servers it started alone, so replacing the process is the only
|
|
1741
|
-
* way - the transcript comes back with `session/load`.
|
|
1705
|
+
* Restart the session's kiro child, so what kiro reads only at startup is read again: the
|
|
1706
|
+
* agent definition, the workspace's `.kiro`, and the MCP servers kiro launches itself.
|
|
1707
|
+
* Replacing the process is the only way - no ACP method does it, and `session/set_mode`
|
|
1708
|
+
* leaves those MCP servers alone.
|
|
1742
1709
|
*
|
|
1743
|
-
* The old child
|
|
1744
|
-
*
|
|
1745
|
-
* conversation. Respawned eagerly rather than at the next prompt, so the caller
|
|
1746
|
-
* gets a detail that reflects what the fresh process reported.
|
|
1747
|
-
*
|
|
1748
|
-
* Needs a session that has recorded a turn, so it can be loaded back. Verified
|
|
1749
|
-
* against kiro 2.11: loading one with an empty event log fails with "Session not
|
|
1750
|
-
* found", and kiro deletes both its files when that process exits.
|
|
1710
|
+
* The old child must be awaited out first: kiro flushes its session file on shutdown and
|
|
1711
|
+
* the replacement reads that file to reload the conversation.
|
|
1751
1712
|
*/
|
|
1752
|
-
async
|
|
1753
|
-
const s = await this.ensureOpen(
|
|
1713
|
+
async reloadChat(chatId) {
|
|
1714
|
+
const s = await this.ensureOpen(chatId);
|
|
1754
1715
|
if (s.running) {
|
|
1755
1716
|
throw new Error("Cannot reload while a turn is running");
|
|
1756
1717
|
}
|
|
1718
|
+
if (s.turnState.get().compacting) {
|
|
1719
|
+
throw new Error("Cannot reload while the conversation is being compacted");
|
|
1720
|
+
}
|
|
1757
1721
|
if (s.reloading) {
|
|
1758
1722
|
throw new Error("A reload is already running for this session");
|
|
1759
1723
|
}
|
|
@@ -1762,14 +1726,14 @@ var init_SessionManager = __esm({
|
|
|
1762
1726
|
ready = resolve;
|
|
1763
1727
|
});
|
|
1764
1728
|
try {
|
|
1765
|
-
return await this.replaceProcess(s);
|
|
1729
|
+
return await this.replaceProcess(chatId, s);
|
|
1766
1730
|
} finally {
|
|
1767
1731
|
s.reloading = void 0;
|
|
1768
1732
|
ready();
|
|
1769
1733
|
}
|
|
1770
1734
|
}
|
|
1771
1735
|
/** The reload itself. Only ever called with the session's reload claim held. */
|
|
1772
|
-
async replaceProcess(s) {
|
|
1736
|
+
async replaceProcess(chatId, s) {
|
|
1773
1737
|
if (!await hasRecordedTurns(s.sessionId)) {
|
|
1774
1738
|
throw new Error(
|
|
1775
1739
|
"kiro has not saved this session yet. Send a message first, then reload."
|
|
@@ -1788,7 +1752,7 @@ var init_SessionManager = __esm({
|
|
|
1788
1752
|
s.lastActivity = Date.now();
|
|
1789
1753
|
invalidateAgents();
|
|
1790
1754
|
this.log.info({ sessionId: s.sessionId, cwd: s.cwd }, "session reloaded");
|
|
1791
|
-
return this.getDetail(
|
|
1755
|
+
return this.getDetail(chatId);
|
|
1792
1756
|
}
|
|
1793
1757
|
/**
|
|
1794
1758
|
* Wait out a reload before touching the session's process. Without this an action
|
|
@@ -1805,33 +1769,34 @@ var init_SessionManager = __esm({
|
|
|
1805
1769
|
// -------------------------------------------------------------------------
|
|
1806
1770
|
// Event subscription - works for any opened session, spawned or not.
|
|
1807
1771
|
// -------------------------------------------------------------------------
|
|
1808
|
-
onEvent(
|
|
1809
|
-
const s = this.sessions.get(
|
|
1772
|
+
onEvent(chatId, cb) {
|
|
1773
|
+
const s = this.sessions.get(this.store.sessionIdForChat(chatId) ?? "");
|
|
1810
1774
|
if (!s) return null;
|
|
1811
1775
|
s.store.on("event", cb);
|
|
1812
1776
|
return () => s.store.off("event", cb);
|
|
1813
1777
|
}
|
|
1814
|
-
getStore(
|
|
1815
|
-
return this.sessions.get(
|
|
1778
|
+
getStore(chatId) {
|
|
1779
|
+
return this.sessions.get(this.store.sessionIdForChat(chatId) ?? "")?.store;
|
|
1816
1780
|
}
|
|
1817
|
-
/**
|
|
1818
|
-
async
|
|
1819
|
-
const s = await this.ensureOpen(
|
|
1781
|
+
/** A chat's working directory. Opens it in memory if needed. */
|
|
1782
|
+
async getChatCwd(chatId) {
|
|
1783
|
+
const s = await this.ensureOpen(chatId);
|
|
1820
1784
|
return s.cwd;
|
|
1821
1785
|
}
|
|
1822
|
-
/** Open a session in memory (store + metadata) WITHOUT spawning a process. */
|
|
1823
|
-
async ensureOpen(
|
|
1786
|
+
/** Open a chat's session in memory (store + metadata) WITHOUT spawning a process. */
|
|
1787
|
+
async ensureOpen(chatId) {
|
|
1788
|
+
const sessionId = this.sessionIdOf(chatId);
|
|
1824
1789
|
const existing = this.sessions.get(sessionId);
|
|
1825
1790
|
if (existing) return existing;
|
|
1826
1791
|
const persisted = await readPersistedSession(sessionId);
|
|
1827
1792
|
if (!persisted) throw new Error(`Unknown session: ${sessionId}`);
|
|
1828
|
-
const effectiveCwd = this.store.getCwd(
|
|
1793
|
+
const effectiveCwd = this.store.getCwd(chatId) ?? persisted.cwd;
|
|
1829
1794
|
if (!isWithinRoot(config.fileRoot, effectiveCwd)) {
|
|
1830
1795
|
throw new Error(
|
|
1831
1796
|
`Session working directory is outside the allowed root: ${effectiveCwd}`
|
|
1832
1797
|
);
|
|
1833
1798
|
}
|
|
1834
|
-
const store = new EventStore(
|
|
1799
|
+
const store = new EventStore(chatId);
|
|
1835
1800
|
const s = new Session(sessionId, store, effectiveCwd);
|
|
1836
1801
|
s.title = persisted.title;
|
|
1837
1802
|
s.agentId = persisted.agentId;
|
|
@@ -1840,10 +1805,7 @@ var init_SessionManager = __esm({
|
|
|
1840
1805
|
s.createdAt = persisted.createdAt;
|
|
1841
1806
|
s.updatedAt = persisted.updatedAt;
|
|
1842
1807
|
s.markLive();
|
|
1843
|
-
s.turnState.seed(
|
|
1844
|
-
persisted.creditsSpent ?? 0,
|
|
1845
|
-
persisted.contextUsagePercentage ?? 0
|
|
1846
|
-
);
|
|
1808
|
+
s.turnState.seed(persisted.contextUsagePercentage ?? 0);
|
|
1847
1809
|
this.sessions.set(sessionId, s);
|
|
1848
1810
|
return s;
|
|
1849
1811
|
}
|
|
@@ -1930,10 +1892,11 @@ var init_SessionManager = __esm({
|
|
|
1930
1892
|
// Creating / opening
|
|
1931
1893
|
// -------------------------------------------------------------------------
|
|
1932
1894
|
/** Create a new session. Spawns immediately so we get a real kiro sessionId. */
|
|
1933
|
-
async
|
|
1934
|
-
const
|
|
1895
|
+
async createChat(opts) {
|
|
1896
|
+
const chatId = isValidChatId(opts.chatId) ? opts.chatId : crypto.randomUUID();
|
|
1897
|
+
const cwd = opts.freshWorkspace ? createChatWorkspace(chatId) : resolveCwd(opts.cwd);
|
|
1935
1898
|
const tempId = `pending-${Date.now()}-${Math.floor(this.sessions.size)}`;
|
|
1936
|
-
const store = new EventStore(
|
|
1899
|
+
const store = new EventStore(chatId);
|
|
1937
1900
|
const s = new Session(tempId, store, cwd);
|
|
1938
1901
|
s.agentId = opts.agentId ?? config.defaultAgent;
|
|
1939
1902
|
s.currentModeId = s.agentId;
|
|
@@ -1945,18 +1908,20 @@ var init_SessionManager = __esm({
|
|
|
1945
1908
|
this.evict(s.sessionId);
|
|
1946
1909
|
throw err;
|
|
1947
1910
|
}
|
|
1911
|
+
this.store.create(chatId);
|
|
1912
|
+
this.store.bindSession(chatId, s.sessionId);
|
|
1948
1913
|
const name = sanitizeTitle(opts.title ?? "") || (opts.freshWorkspace ? "" : path11.basename(s.cwd));
|
|
1949
1914
|
if (name) {
|
|
1950
|
-
this.store.setTitle(
|
|
1915
|
+
this.store.setTitle(chatId, name);
|
|
1951
1916
|
s.title = name;
|
|
1952
1917
|
}
|
|
1953
|
-
return this.buildDetail(s, []);
|
|
1918
|
+
return this.buildDetail(this.store.get(chatId), s, []);
|
|
1954
1919
|
}
|
|
1955
1920
|
// -------------------------------------------------------------------------
|
|
1956
1921
|
// Actions - these spawn the process lazily.
|
|
1957
1922
|
// -------------------------------------------------------------------------
|
|
1958
|
-
async runPrompt(
|
|
1959
|
-
const s = await this.ensureOpen(
|
|
1923
|
+
async runPrompt(chatId, content, attachments) {
|
|
1924
|
+
const s = await this.ensureOpen(chatId);
|
|
1960
1925
|
await this.settleReload(s);
|
|
1961
1926
|
if (s.running) throw new Error("A turn is already running for this session");
|
|
1962
1927
|
s.running = true;
|
|
@@ -1968,14 +1933,20 @@ var init_SessionManager = __esm({
|
|
|
1968
1933
|
s.running = false;
|
|
1969
1934
|
throw err;
|
|
1970
1935
|
}
|
|
1971
|
-
if (!this.store.getTitle(
|
|
1936
|
+
if (!this.store.getTitle(chatId)) {
|
|
1972
1937
|
const title = titleFromPrompt(content);
|
|
1973
1938
|
if (title) {
|
|
1974
|
-
this.store.setTitle(
|
|
1939
|
+
this.store.setTitle(chatId, title);
|
|
1975
1940
|
s.title = title;
|
|
1976
1941
|
}
|
|
1977
1942
|
}
|
|
1978
|
-
|
|
1943
|
+
let recorded;
|
|
1944
|
+
if (attachments?.length) {
|
|
1945
|
+
const ordinal = await promptCount(s.sessionId);
|
|
1946
|
+
this.store.setAttachments(chatId, ordinal, attachments);
|
|
1947
|
+
recorded = attachments;
|
|
1948
|
+
}
|
|
1949
|
+
s.record({ kind: "turn_started", prompt: content, attachments: recorded });
|
|
1979
1950
|
proc.prompt({ sessionId: s.sessionId, prompt: content }).then((res) => s.record({ kind: "turn_ended", stopReason: res.stopReason })).catch((err) => {
|
|
1980
1951
|
this.log.error({ err, sessionId: s.sessionId }, "prompt turn failed");
|
|
1981
1952
|
const tail = proc.stderrTail();
|
|
@@ -1989,12 +1960,12 @@ ${tail}` : err.message;
|
|
|
1989
1960
|
s.lastActivity = Date.now();
|
|
1990
1961
|
});
|
|
1991
1962
|
}
|
|
1992
|
-
cancel(
|
|
1993
|
-
const s = this.sessions.get(
|
|
1963
|
+
cancel(chatId) {
|
|
1964
|
+
const s = this.sessions.get(this.store.sessionIdForChat(chatId) ?? "");
|
|
1994
1965
|
s?.proc?.cancel(s.sessionId);
|
|
1995
1966
|
}
|
|
1996
|
-
async setMode(
|
|
1997
|
-
const s = await this.ensureOpen(
|
|
1967
|
+
async setMode(chatId, modeId) {
|
|
1968
|
+
const s = await this.ensureOpen(chatId);
|
|
1998
1969
|
await this.settleReload(s);
|
|
1999
1970
|
const proc = await this.ensureProc(s);
|
|
2000
1971
|
await proc.setMode(s.sessionId, modeId);
|
|
@@ -2002,114 +1973,124 @@ ${tail}` : err.message;
|
|
|
2002
1973
|
s.agentId = modeId;
|
|
2003
1974
|
s.lastActivity = Date.now();
|
|
2004
1975
|
}
|
|
2005
|
-
async setModel(
|
|
2006
|
-
const s = await this.ensureOpen(
|
|
1976
|
+
async setModel(chatId, modelId) {
|
|
1977
|
+
const s = await this.ensureOpen(chatId);
|
|
2007
1978
|
await this.settleReload(s);
|
|
2008
1979
|
const proc = await this.ensureProc(s);
|
|
2009
1980
|
await proc.setModel(s.sessionId, modelId);
|
|
2010
1981
|
s.modelId = modelId;
|
|
2011
1982
|
s.lastActivity = Date.now();
|
|
2012
1983
|
}
|
|
2013
|
-
async execCommand(
|
|
2014
|
-
const s = await this.ensureOpen(
|
|
1984
|
+
async execCommand(chatId, command) {
|
|
1985
|
+
const s = await this.ensureOpen(chatId);
|
|
2015
1986
|
await this.settleReload(s);
|
|
2016
1987
|
const proc = await this.ensureProc(s);
|
|
2017
|
-
await proc.execCommand(s.sessionId, command
|
|
1988
|
+
await proc.execCommand(s.sessionId, command);
|
|
2018
1989
|
s.lastActivity = Date.now();
|
|
2019
1990
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
1991
|
+
// -------------------------------------------------------------------------
|
|
1992
|
+
// Listing / detail - never spawns.
|
|
1993
|
+
// -------------------------------------------------------------------------
|
|
1994
|
+
/**
|
|
1995
|
+
* The one place a ChatSummary is assembled: kiro's file and Casper's live state each hold
|
|
1996
|
+
* part of the truth, so every read path projects them through here. `persisted` is absent for
|
|
1997
|
+
* a session with no file yet, `live` for a dormant one, and callers guarantee one of them.
|
|
1998
|
+
*/
|
|
1999
|
+
summaryOf(chat, live, persisted, transcript) {
|
|
2022
2000
|
const snap = live?.turnState.get();
|
|
2023
|
-
const cwd = live?.cwd ??
|
|
2001
|
+
const cwd = live?.cwd ?? chat.cwd ?? persisted?.cwd ?? config.defaultCwd;
|
|
2024
2002
|
return {
|
|
2025
|
-
|
|
2026
|
-
|
|
2003
|
+
chatId: chat.chatId,
|
|
2004
|
+
sessionId: live?.sessionId ?? chat.sessionId ?? void 0,
|
|
2005
|
+
title: this.titleOf(chat, {
|
|
2027
2006
|
// kiro's file is what kiro called it; the live copy is only a cache of it.
|
|
2028
2007
|
kiroTitle: persisted?.title || live?.title,
|
|
2029
2008
|
firstPrompt: transcript && firstPromptText(transcript),
|
|
2030
2009
|
cwd
|
|
2031
2010
|
}),
|
|
2032
2011
|
cwd,
|
|
2033
|
-
createdAt: persisted?.createdAt ?? live.
|
|
2012
|
+
createdAt: persisted?.createdAt ?? live?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2034
2013
|
// kiro's file and our own activity move separately.
|
|
2035
|
-
updatedAt: live ? newerOf(persisted?.updatedAt, live.updatedAt) : persisted.
|
|
2014
|
+
updatedAt: live ? newerOf(persisted?.updatedAt, live.updatedAt) : persisted?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2036
2015
|
liveness: live?.proc ? "live" : "dormant",
|
|
2037
2016
|
agentId: live?.agentId ?? persisted?.agentId,
|
|
2038
2017
|
modelId: live?.modelId ?? persisted?.modelId,
|
|
2039
2018
|
running: live?.running ?? false,
|
|
2040
|
-
// A
|
|
2041
|
-
//
|
|
2042
|
-
creditsSpent: snap?.creditsSpent || persisted?.creditsSpent,
|
|
2019
|
+
// A live snapshot starts at zero until the first turn reports, so a dormant
|
|
2020
|
+
// session's value comes from kiro's file.
|
|
2043
2021
|
contextUsagePercentage: snap?.contextUsagePercentage || persisted?.contextUsagePercentage
|
|
2044
2022
|
};
|
|
2045
2023
|
}
|
|
2046
|
-
async
|
|
2047
|
-
const
|
|
2048
|
-
const files =
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2024
|
+
async listChats() {
|
|
2025
|
+
const rows = this.store.all();
|
|
2026
|
+
const files = await Promise.all(
|
|
2027
|
+
rows.map((r) => r.sessionId ? readPersistedSession(r.sessionId) : null)
|
|
2028
|
+
);
|
|
2029
|
+
const out = [];
|
|
2030
|
+
rows.forEach((row, i) => {
|
|
2031
|
+
const live = row.sessionId ? this.sessions.get(row.sessionId) : void 0;
|
|
2032
|
+
const persisted = files[i] ?? void 0;
|
|
2033
|
+
if (live && isGhost(live, !!persisted)) {
|
|
2034
|
+
this.evict(live.sessionId);
|
|
2035
|
+
return;
|
|
2057
2036
|
}
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2037
|
+
if (!persisted && !live) return;
|
|
2038
|
+
out.push(this.summaryOf(row, live, persisted));
|
|
2039
|
+
});
|
|
2040
|
+
return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2041
|
+
}
|
|
2042
|
+
/** kiro's session id for a chat, or a throw if the chat is unknown. */
|
|
2043
|
+
sessionIdOf(chatId) {
|
|
2044
|
+
const sessionId = this.store.sessionIdForChat(chatId);
|
|
2045
|
+
if (!sessionId) throw new Error(`Unknown chat: ${chatId}`);
|
|
2046
|
+
return sessionId;
|
|
2047
|
+
}
|
|
2048
|
+
async getDetail(chatId) {
|
|
2049
|
+
const chat = this.store.get(chatId);
|
|
2050
|
+
if (!chat) throw new Error(`Unknown chat: ${chatId}`);
|
|
2051
|
+
const sessionId = chat.sessionId;
|
|
2063
2052
|
const [transcript, persisted] = await Promise.all([
|
|
2064
|
-
hydrateTranscript(sessionId),
|
|
2065
|
-
readPersistedSession(sessionId)
|
|
2053
|
+
sessionId ? hydrateTranscript(sessionId, this.store.attachmentsByChat(chatId)) : Promise.resolve([]),
|
|
2054
|
+
sessionId ? readPersistedSession(sessionId) : Promise.resolve(null)
|
|
2066
2055
|
]);
|
|
2067
|
-
const s = this.sessions.get(sessionId);
|
|
2068
|
-
if (s
|
|
2069
|
-
|
|
2070
|
-
return {
|
|
2071
|
-
summary: this.summaryOf(void 0, persisted, transcript),
|
|
2072
|
-
modes: [],
|
|
2073
|
-
currentModeId: persisted.agentId,
|
|
2074
|
-
transcript: transcript.slice(-TRANSCRIPT_PAGE_SIZE),
|
|
2075
|
-
transcriptTotal: transcript.length,
|
|
2076
|
-
observability: {
|
|
2077
|
-
...emptyObservabilitySnapshot(),
|
|
2078
|
-
creditsSpent: persisted.creditsSpent ?? 0,
|
|
2079
|
-
contextUsagePercentage: persisted.contextUsagePercentage ?? 0
|
|
2080
|
-
},
|
|
2081
|
-
head: 0
|
|
2082
|
-
};
|
|
2056
|
+
const s = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
2057
|
+
if (!s && !persisted) throw new Error(`Unknown chat: ${chatId}`);
|
|
2058
|
+
return this.buildDetail(chat, s, transcript, persisted ?? void 0);
|
|
2083
2059
|
}
|
|
2084
2060
|
/**
|
|
2085
2061
|
* A slice of the transcript for lazy "load older on scroll up". Re-hydrates
|
|
2086
2062
|
* from disk (fast: ~150ms even for a multi-MB session) and returns the
|
|
2087
2063
|
* requested window. offset/limit are clamped to the transcript bounds.
|
|
2088
2064
|
*/
|
|
2089
|
-
async getTranscriptPage(
|
|
2090
|
-
const transcript = await hydrateTranscript(
|
|
2065
|
+
async getTranscriptPage(chatId, offset, limit) {
|
|
2066
|
+
const transcript = await hydrateTranscript(
|
|
2067
|
+
this.sessionIdOf(chatId),
|
|
2068
|
+
this.store.attachmentsByChat(chatId)
|
|
2069
|
+
);
|
|
2091
2070
|
const start = Math.max(0, Math.min(offset, transcript.length));
|
|
2092
2071
|
const end = Math.max(start, Math.min(start + limit, transcript.length));
|
|
2093
2072
|
return transcript.slice(start, end);
|
|
2094
2073
|
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2074
|
+
/** One projection, so a dormant session and a live one cannot disagree. */
|
|
2075
|
+
buildDetail(chat, s, transcript, persisted) {
|
|
2097
2076
|
return {
|
|
2098
|
-
summary: this.summaryOf(s, persisted, transcript),
|
|
2099
|
-
modes: s
|
|
2100
|
-
currentModeId: s
|
|
2077
|
+
summary: this.summaryOf(chat, s, persisted, transcript),
|
|
2078
|
+
modes: s?.availableModes ?? [],
|
|
2079
|
+
currentModeId: s?.currentModeId ?? persisted?.agentId,
|
|
2101
2080
|
// Only the tail is sent on load; replayHead/title use the full transcript.
|
|
2102
2081
|
transcript: transcript.slice(-TRANSCRIPT_PAGE_SIZE),
|
|
2103
2082
|
transcriptTotal: transcript.length,
|
|
2104
|
-
observability:
|
|
2105
|
-
|
|
2083
|
+
observability: s?.turnState.get() ?? {
|
|
2084
|
+
...emptyObservabilitySnapshot(),
|
|
2085
|
+
contextUsagePercentage: persisted?.contextUsagePercentage ?? 0
|
|
2086
|
+
},
|
|
2087
|
+
head: s ? this.replayHead(s, transcript) : 0
|
|
2106
2088
|
};
|
|
2107
2089
|
}
|
|
2108
2090
|
/**
|
|
2109
|
-
* The cursor a reconnecting client
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
* against duplication in case kiro ever persists the prompt at turn start.
|
|
2091
|
+
* The cursor a reconnecting client starts from. Normally the store head, but kiro writes a turn
|
|
2092
|
+
* to its jsonl only once it completes, so a hydrated transcript is missing one in flight:
|
|
2093
|
+
* rewind to just before its turn_started and let the socket replay the whole turn.
|
|
2113
2094
|
*/
|
|
2114
2095
|
replayHead(s, transcript) {
|
|
2115
2096
|
const head = s.store.head();
|
|
@@ -2147,16 +2128,18 @@ ${tail}` : err.message;
|
|
|
2147
2128
|
}
|
|
2148
2129
|
// Permanently delete a session: evict it from memory, remove its on-disk
|
|
2149
2130
|
// files, and drop any title override.
|
|
2150
|
-
async
|
|
2151
|
-
const
|
|
2131
|
+
async deleteChat(chatId) {
|
|
2132
|
+
const sessionId = this.store.sessionIdForChat(chatId);
|
|
2133
|
+
const s = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
2152
2134
|
if (s?.proc) {
|
|
2153
2135
|
await s.proc.disposeAndWait().catch(() => {
|
|
2154
2136
|
});
|
|
2155
2137
|
s.proc = void 0;
|
|
2156
2138
|
s.running = false;
|
|
2157
2139
|
}
|
|
2140
|
+
this.store.remove(chatId);
|
|
2141
|
+
if (!sessionId) return;
|
|
2158
2142
|
this.evict(sessionId);
|
|
2159
|
-
this.store.remove(sessionId);
|
|
2160
2143
|
await deletePersistedSession(sessionId);
|
|
2161
2144
|
setTimeout(() => void deletePersistedSession(sessionId).catch(() => {
|
|
2162
2145
|
}), 2500).unref?.();
|
|
@@ -2214,13 +2197,17 @@ var init_rateLimit = __esm({
|
|
|
2214
2197
|
});
|
|
2215
2198
|
|
|
2216
2199
|
// server/src/routes/auth.ts
|
|
2217
|
-
import
|
|
2200
|
+
import crypto3 from "node:crypto";
|
|
2218
2201
|
import fastifyCookie from "@fastify/cookie";
|
|
2202
|
+
function logins() {
|
|
2203
|
+
loginStore ??= new LoginStore();
|
|
2204
|
+
return loginStore;
|
|
2205
|
+
}
|
|
2219
2206
|
function secretMatches(supplied, expected) {
|
|
2220
2207
|
if (typeof supplied !== "string") return false;
|
|
2221
|
-
const a =
|
|
2222
|
-
const b =
|
|
2223
|
-
return
|
|
2208
|
+
const a = crypto3.createHash("sha256").update(supplied).digest();
|
|
2209
|
+
const b = crypto3.createHash("sha256").update(expected).digest();
|
|
2210
|
+
return crypto3.timingSafeEqual(a, b);
|
|
2224
2211
|
}
|
|
2225
2212
|
function extractToken(req) {
|
|
2226
2213
|
const auth = req.headers["authorization"];
|
|
@@ -2238,7 +2225,7 @@ function sessionToken(req) {
|
|
|
2238
2225
|
return req.cookies?.[SESSION_COOKIE];
|
|
2239
2226
|
}
|
|
2240
2227
|
function hasValidSession(req) {
|
|
2241
|
-
return logins.verify(sessionToken(req)) !== null;
|
|
2228
|
+
return logins().verify(sessionToken(req)) !== null;
|
|
2242
2229
|
}
|
|
2243
2230
|
function cookieOptions() {
|
|
2244
2231
|
return {
|
|
@@ -2266,27 +2253,27 @@ async function registerAuth(app) {
|
|
|
2266
2253
|
loginLimiter.succeed(req.ip);
|
|
2267
2254
|
}
|
|
2268
2255
|
const ua = req.headers["user-agent"];
|
|
2269
|
-
const { token } = logins.create(typeof ua === "string" ? ua : void 0);
|
|
2256
|
+
const { token } = logins().create(typeof ua === "string" ? ua : void 0);
|
|
2270
2257
|
reply.setCookie(SESSION_COOKIE, token, cookieOptions());
|
|
2271
2258
|
return { ok: true };
|
|
2272
2259
|
});
|
|
2273
2260
|
app.post("/api/logout", async (req, reply) => {
|
|
2274
|
-
logins.revokeToken(sessionToken(req));
|
|
2261
|
+
logins().revokeToken(sessionToken(req));
|
|
2275
2262
|
reply.clearCookie(SESSION_COOKIE, { path: "/" });
|
|
2276
2263
|
return { ok: true };
|
|
2277
2264
|
});
|
|
2278
2265
|
app.get("/api/devices", async (req) => {
|
|
2279
|
-
return { devices: logins.list(sessionToken(req)) };
|
|
2266
|
+
return { devices: logins().list(sessionToken(req)) };
|
|
2280
2267
|
});
|
|
2281
2268
|
app.delete("/api/devices/:id", async (req, reply) => {
|
|
2282
2269
|
const { id } = req.params;
|
|
2283
|
-
const current = logins.list(sessionToken(req)).find((d) => d.current);
|
|
2284
|
-
const removed = logins.revokeId(id);
|
|
2270
|
+
const current = logins().list(sessionToken(req)).find((d) => d.current);
|
|
2271
|
+
const removed = logins().revokeId(id);
|
|
2285
2272
|
if (current?.id === id) reply.clearCookie(SESSION_COOKIE, { path: "/" });
|
|
2286
2273
|
return { ok: removed };
|
|
2287
2274
|
});
|
|
2288
2275
|
app.post("/api/logout-all", async (_req, reply) => {
|
|
2289
|
-
logins.revokeAll();
|
|
2276
|
+
logins().revokeAll();
|
|
2290
2277
|
reply.clearCookie(SESSION_COOKIE, { path: "/" });
|
|
2291
2278
|
return { ok: true };
|
|
2292
2279
|
});
|
|
@@ -2298,7 +2285,7 @@ async function registerAuth(app) {
|
|
|
2298
2285
|
reply.code(401).send({ error: "Unauthorized: log in first" });
|
|
2299
2286
|
});
|
|
2300
2287
|
}
|
|
2301
|
-
var SESSION_COOKIE,
|
|
2288
|
+
var SESSION_COOKIE, loginStore, loginLimiter;
|
|
2302
2289
|
var init_auth = __esm({
|
|
2303
2290
|
"server/src/routes/auth.ts"() {
|
|
2304
2291
|
"use strict";
|
|
@@ -2306,7 +2293,6 @@ var init_auth = __esm({
|
|
|
2306
2293
|
init_logins();
|
|
2307
2294
|
init_rateLimit();
|
|
2308
2295
|
SESSION_COOKIE = "casper.sid";
|
|
2309
|
-
logins = new LoginStore();
|
|
2310
2296
|
loginLimiter = new AttemptLimiter(10, 15 * 60 * 1e3);
|
|
2311
2297
|
}
|
|
2312
2298
|
});
|
|
@@ -2430,12 +2416,12 @@ function replyWith(reply, failure) {
|
|
|
2430
2416
|
reply.code(failure.status);
|
|
2431
2417
|
return { error: failure.error };
|
|
2432
2418
|
}
|
|
2433
|
-
async function
|
|
2419
|
+
async function resolveChatPath(sessions, sessionId, requestedPath, require2) {
|
|
2434
2420
|
let cwd;
|
|
2435
2421
|
try {
|
|
2436
|
-
cwd = await sessions.
|
|
2422
|
+
cwd = await sessions.getChatCwd(sessionId);
|
|
2437
2423
|
} catch {
|
|
2438
|
-
return { ok: false, status: 404, error: "
|
|
2424
|
+
return { ok: false, status: 404, error: "Chat not found" };
|
|
2439
2425
|
}
|
|
2440
2426
|
const relative = (requestedPath ?? "").replace(/^\/+/, "");
|
|
2441
2427
|
if (require2 === "file" && !relative) {
|
|
@@ -2520,15 +2506,15 @@ function looksBinary(buf) {
|
|
|
2520
2506
|
}
|
|
2521
2507
|
function classifyKind(name) {
|
|
2522
2508
|
const ext = path13.extname(name).toLowerCase();
|
|
2523
|
-
if (ext in
|
|
2509
|
+
if (ext in IMAGE_EXTS) return "image";
|
|
2524
2510
|
if (TEXT_EXTS.has(ext)) return "text";
|
|
2525
2511
|
return "binary";
|
|
2526
2512
|
}
|
|
2527
|
-
var
|
|
2513
|
+
var IMAGE_EXTS, TEXT_EXTS, MIME_MAP;
|
|
2528
2514
|
var init_filekind = __esm({
|
|
2529
2515
|
"server/src/util/filekind.ts"() {
|
|
2530
2516
|
"use strict";
|
|
2531
|
-
|
|
2517
|
+
IMAGE_EXTS = {
|
|
2532
2518
|
".png": "image/png",
|
|
2533
2519
|
".jpg": "image/jpeg",
|
|
2534
2520
|
".jpeg": "image/jpeg",
|
|
@@ -2600,7 +2586,7 @@ var init_filekind = __esm({
|
|
|
2600
2586
|
".tex"
|
|
2601
2587
|
]);
|
|
2602
2588
|
MIME_MAP = {
|
|
2603
|
-
...
|
|
2589
|
+
...IMAGE_EXTS,
|
|
2604
2590
|
".txt": "text/plain",
|
|
2605
2591
|
".md": "text/markdown",
|
|
2606
2592
|
".csv": "text/csv",
|
|
@@ -2628,10 +2614,128 @@ var init_filekind = __esm({
|
|
|
2628
2614
|
}
|
|
2629
2615
|
});
|
|
2630
2616
|
|
|
2631
|
-
// server/src/routes/
|
|
2617
|
+
// server/src/routes/filePreview.ts
|
|
2632
2618
|
import fs12 from "node:fs/promises";
|
|
2633
2619
|
import { createReadStream } from "node:fs";
|
|
2634
2620
|
import path14 from "node:path";
|
|
2621
|
+
function tooLargeForPreview(size, maxBytes) {
|
|
2622
|
+
return {
|
|
2623
|
+
error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
function hexdump(buf) {
|
|
2627
|
+
const lines = [];
|
|
2628
|
+
for (let off = 0; off < buf.length; off += 16) {
|
|
2629
|
+
const slice = buf.subarray(off, off + 16);
|
|
2630
|
+
const hex = [];
|
|
2631
|
+
let ascii = "";
|
|
2632
|
+
for (let i = 0; i < 16; i++) {
|
|
2633
|
+
if (i < slice.length) {
|
|
2634
|
+
const b = slice[i];
|
|
2635
|
+
hex.push(b.toString(16).padStart(2, "0"));
|
|
2636
|
+
ascii += b >= 32 && b <= 126 ? String.fromCharCode(b) : ".";
|
|
2637
|
+
} else {
|
|
2638
|
+
hex.push(" ");
|
|
2639
|
+
}
|
|
2640
|
+
if (i === 7) hex.push("");
|
|
2641
|
+
}
|
|
2642
|
+
lines.push(`${off.toString(16).padStart(8, "0")} ${hex.join(" ")} |${ascii}|`);
|
|
2643
|
+
}
|
|
2644
|
+
return lines.join("\n");
|
|
2645
|
+
}
|
|
2646
|
+
async function sendFilePreview(req, reply, realTarget, stat) {
|
|
2647
|
+
const ext = path14.extname(realTarget).toLowerCase();
|
|
2648
|
+
const mime = mimeForExt(ext);
|
|
2649
|
+
const isImage = mime.startsWith("image/");
|
|
2650
|
+
const isPdf = mime === "application/pdf";
|
|
2651
|
+
const isHtml = mime === "text/html" && req.query.raw === "1";
|
|
2652
|
+
const kind = classifyKind(realTarget);
|
|
2653
|
+
if (isImage || isPdf) {
|
|
2654
|
+
if (stat.size > MAX_IMAGE_PREVIEW_BYTES) {
|
|
2655
|
+
reply.code(413);
|
|
2656
|
+
return tooLargeForPreview(stat.size, MAX_IMAGE_PREVIEW_BYTES);
|
|
2657
|
+
}
|
|
2658
|
+
} else if (kind !== "binary") {
|
|
2659
|
+
if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
|
|
2660
|
+
reply.code(413);
|
|
2661
|
+
return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
if (isHtml) {
|
|
2665
|
+
if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
|
|
2666
|
+
reply.code(413);
|
|
2667
|
+
return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
|
|
2668
|
+
}
|
|
2669
|
+
reply.header("Content-Security-Policy", "sandbox allow-scripts allow-forms");
|
|
2670
|
+
reply.header("Content-Type", "text/html; charset=utf-8");
|
|
2671
|
+
reply.header("Cache-Control", "no-store");
|
|
2672
|
+
return reply.send(createReadStream(realTarget));
|
|
2673
|
+
}
|
|
2674
|
+
if (isImage || isPdf) {
|
|
2675
|
+
const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
|
|
2676
|
+
reply.header("Cache-Control", "private, no-cache");
|
|
2677
|
+
reply.header("ETag", etag);
|
|
2678
|
+
reply.header("Last-Modified", stat.mtime.toUTCString());
|
|
2679
|
+
const ifNoneMatch = req.headers["if-none-match"];
|
|
2680
|
+
if (ifNoneMatch === etag) {
|
|
2681
|
+
reply.code(304);
|
|
2682
|
+
return reply.send();
|
|
2683
|
+
}
|
|
2684
|
+
reply.header("Content-Type", mime);
|
|
2685
|
+
reply.header("Content-Disposition", "inline");
|
|
2686
|
+
reply.header("Content-Length", stat.size);
|
|
2687
|
+
return reply.send(createReadStream(realTarget));
|
|
2688
|
+
}
|
|
2689
|
+
if (kind === "binary") {
|
|
2690
|
+
let fh;
|
|
2691
|
+
try {
|
|
2692
|
+
fh = await fs12.open(realTarget, "r");
|
|
2693
|
+
const buf = Buffer.alloc(Math.min(HEXDUMP_BYTES, stat.size));
|
|
2694
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
2695
|
+
const head = buf.subarray(0, bytesRead);
|
|
2696
|
+
if (!looksBinary(head)) {
|
|
2697
|
+
if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
|
|
2698
|
+
reply.code(413);
|
|
2699
|
+
return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
|
|
2700
|
+
}
|
|
2701
|
+
reply.header("Content-Type", "text/plain; charset=utf-8");
|
|
2702
|
+
reply.header("Content-Disposition", "inline");
|
|
2703
|
+
reply.header("Content-Length", stat.size);
|
|
2704
|
+
return reply.send(createReadStream(realTarget));
|
|
2705
|
+
}
|
|
2706
|
+
const dump = hexdump(head);
|
|
2707
|
+
const header = `Binary file - ${stat.size} bytes
|
|
2708
|
+
Showing first ${bytesRead} bytes as hexdump:
|
|
2709
|
+
|
|
2710
|
+
`;
|
|
2711
|
+
const body = header + dump + (stat.size > bytesRead ? "\n\n\u2026 (truncated)" : "");
|
|
2712
|
+
reply.header("Content-Type", "text/plain; charset=utf-8");
|
|
2713
|
+
reply.header("Content-Disposition", "inline");
|
|
2714
|
+
return reply.send(body);
|
|
2715
|
+
} finally {
|
|
2716
|
+
await fh?.close();
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
reply.header("Content-Type", "text/plain; charset=utf-8");
|
|
2720
|
+
reply.header("Content-Disposition", "inline");
|
|
2721
|
+
reply.header("Content-Length", stat.size);
|
|
2722
|
+
return reply.send(createReadStream(realTarget));
|
|
2723
|
+
}
|
|
2724
|
+
var HEXDUMP_BYTES, MAX_TEXT_PREVIEW_BYTES, MAX_IMAGE_PREVIEW_BYTES;
|
|
2725
|
+
var init_filePreview = __esm({
|
|
2726
|
+
"server/src/routes/filePreview.ts"() {
|
|
2727
|
+
"use strict";
|
|
2728
|
+
init_filekind();
|
|
2729
|
+
HEXDUMP_BYTES = 4096;
|
|
2730
|
+
MAX_TEXT_PREVIEW_BYTES = 1024 * 1024;
|
|
2731
|
+
MAX_IMAGE_PREVIEW_BYTES = 20 * 1024 * 1024;
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
|
|
2735
|
+
// server/src/routes/fs.ts
|
|
2736
|
+
import fs13 from "node:fs/promises";
|
|
2737
|
+
import { createReadStream as createReadStream2 } from "node:fs";
|
|
2738
|
+
import path15 from "node:path";
|
|
2635
2739
|
function registerFsRoutes(app) {
|
|
2636
2740
|
app.get(
|
|
2637
2741
|
"/api/fs/dirs",
|
|
@@ -2639,10 +2743,10 @@ function registerFsRoutes(app) {
|
|
|
2639
2743
|
const input = (req.query.path ?? "").trim();
|
|
2640
2744
|
const base = config.defaultCwd;
|
|
2641
2745
|
const endsWithSep = input.endsWith("/");
|
|
2642
|
-
const resolved = input ?
|
|
2643
|
-
const dir = endsWithSep || !input ? resolved :
|
|
2644
|
-
const prefix = endsWithSep || !input ? "" :
|
|
2645
|
-
const targetKind = await
|
|
2746
|
+
const resolved = input ? path15.resolve(base, input) : base;
|
|
2747
|
+
const dir = endsWithSep || !input ? resolved : path15.dirname(resolved);
|
|
2748
|
+
const prefix = endsWithSep || !input ? "" : path15.basename(resolved);
|
|
2749
|
+
const targetKind = await fs13.stat(resolved).then((s) => s.isDirectory() ? "directory" : "file").catch(() => "missing");
|
|
2646
2750
|
const listing = await resolveAbsolutePath(dir, "directory");
|
|
2647
2751
|
if (!listing.ok) {
|
|
2648
2752
|
if (listing.status === 403) return replyWith(reply, listing);
|
|
@@ -2651,7 +2755,7 @@ function registerFsRoutes(app) {
|
|
|
2651
2755
|
const realDir = listing.real;
|
|
2652
2756
|
let entries = [];
|
|
2653
2757
|
try {
|
|
2654
|
-
const dirents = await
|
|
2758
|
+
const dirents = await fs13.readdir(realDir, { withFileTypes: true });
|
|
2655
2759
|
const checks2 = await Promise.all(
|
|
2656
2760
|
dirents.map(async (d) => {
|
|
2657
2761
|
const target = await classifyDirent(realDir, d, absoluteRoots());
|
|
@@ -2662,7 +2766,7 @@ function registerFsRoutes(app) {
|
|
|
2662
2766
|
entries = checks2.filter((name) => name !== null).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase())).sort((a, b) => {
|
|
2663
2767
|
if (isDot(a) !== isDot(b)) return isDot(a) ? 1 : -1;
|
|
2664
2768
|
return a.localeCompare(b);
|
|
2665
|
-
}).slice(0, 500).map((name) =>
|
|
2769
|
+
}).slice(0, 500).map((name) => path15.join(dir, name));
|
|
2666
2770
|
} catch {
|
|
2667
2771
|
entries = [];
|
|
2668
2772
|
}
|
|
@@ -2670,75 +2774,66 @@ function registerFsRoutes(app) {
|
|
|
2670
2774
|
}
|
|
2671
2775
|
);
|
|
2672
2776
|
app.get(
|
|
2673
|
-
"/api/fs/
|
|
2777
|
+
"/api/fs/file",
|
|
2674
2778
|
async (req, reply) => {
|
|
2675
2779
|
const filePath = (req.query.path ?? "").trim();
|
|
2676
2780
|
if (!filePath) {
|
|
2677
2781
|
reply.code(400);
|
|
2678
2782
|
return { error: "path parameter is required" };
|
|
2679
2783
|
}
|
|
2680
|
-
if (!
|
|
2784
|
+
if (!path15.isAbsolute(filePath)) {
|
|
2681
2785
|
reply.code(400);
|
|
2682
2786
|
return { error: "path must be absolute" };
|
|
2683
2787
|
}
|
|
2684
|
-
const
|
|
2685
|
-
if (
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
reply.
|
|
2694
|
-
return { error: "Image too large" };
|
|
2695
|
-
}
|
|
2696
|
-
const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
|
|
2697
|
-
reply.header("Cache-Control", "private, no-cache");
|
|
2698
|
-
reply.header("ETag", etag);
|
|
2699
|
-
reply.header("Last-Modified", stat.mtime.toUTCString());
|
|
2700
|
-
if (req.headers["if-none-match"] === etag) {
|
|
2701
|
-
reply.code(304);
|
|
2702
|
-
return reply.send();
|
|
2788
|
+
const resolved = await resolveAbsolutePath(filePath, "file");
|
|
2789
|
+
if (!resolved.ok) return replyWith(reply, resolved);
|
|
2790
|
+
if (req.query.download === "1") {
|
|
2791
|
+
reply.header("Content-Type", "application/octet-stream");
|
|
2792
|
+
reply.header(
|
|
2793
|
+
"Content-Disposition",
|
|
2794
|
+
`attachment; filename="${path15.basename(resolved.real).replace(/"/g, "")}"`
|
|
2795
|
+
);
|
|
2796
|
+
reply.header("Content-Length", resolved.stat.size);
|
|
2797
|
+
return reply.send(createReadStream2(resolved.real));
|
|
2703
2798
|
}
|
|
2704
|
-
reply.
|
|
2705
|
-
reply.header("Content-Length", stat.size);
|
|
2706
|
-
return reply.send(createReadStream(real));
|
|
2799
|
+
return sendFilePreview(req, reply, resolved.real, resolved.stat);
|
|
2707
2800
|
}
|
|
2708
2801
|
);
|
|
2709
2802
|
}
|
|
2710
|
-
var MAX_IMAGE_BYTES;
|
|
2711
2803
|
var init_fs = __esm({
|
|
2712
2804
|
"server/src/routes/fs.ts"() {
|
|
2713
2805
|
"use strict";
|
|
2714
2806
|
init_config();
|
|
2715
2807
|
init_confinedFile();
|
|
2716
|
-
|
|
2717
|
-
MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
2808
|
+
init_filePreview();
|
|
2718
2809
|
}
|
|
2719
2810
|
});
|
|
2720
2811
|
|
|
2721
|
-
// server/src/routes/
|
|
2722
|
-
function
|
|
2723
|
-
app.get("/api/
|
|
2724
|
-
return {
|
|
2812
|
+
// server/src/routes/chats.ts
|
|
2813
|
+
function registerChatRoutes(app, manager) {
|
|
2814
|
+
app.get("/api/chats", async () => {
|
|
2815
|
+
return { chats: await manager.listChats() };
|
|
2725
2816
|
});
|
|
2726
|
-
app.post("/api/
|
|
2817
|
+
app.post("/api/chats", async (req, reply) => {
|
|
2727
2818
|
const body = req.body ?? {};
|
|
2728
2819
|
try {
|
|
2729
|
-
return await manager.
|
|
2820
|
+
return await manager.createChat({
|
|
2730
2821
|
cwd: body.cwd,
|
|
2731
2822
|
agentId: body.agentId,
|
|
2732
2823
|
modelId: body.modelId,
|
|
2733
2824
|
freshWorkspace: body.freshWorkspace,
|
|
2734
|
-
title: body.title
|
|
2825
|
+
title: body.title,
|
|
2826
|
+
// Not optional in practice: the client's chat already owns any file uploaded before
|
|
2827
|
+
// the session existed, so dropping it here strands those files under an id nothing
|
|
2828
|
+
// refers to again.
|
|
2829
|
+
chatId: body.chatId
|
|
2735
2830
|
});
|
|
2736
2831
|
} catch (err) {
|
|
2737
2832
|
reply.code(400);
|
|
2738
2833
|
return { error: err.message };
|
|
2739
2834
|
}
|
|
2740
2835
|
});
|
|
2741
|
-
app.get("/api/
|
|
2836
|
+
app.get("/api/chats/:id", async (req, reply) => {
|
|
2742
2837
|
try {
|
|
2743
2838
|
return await manager.getDetail(req.params.id);
|
|
2744
2839
|
} catch (err) {
|
|
@@ -2747,7 +2842,7 @@ function registerSessionRoutes(app, manager) {
|
|
|
2747
2842
|
}
|
|
2748
2843
|
});
|
|
2749
2844
|
app.get(
|
|
2750
|
-
"/api/
|
|
2845
|
+
"/api/chats/:id/transcript",
|
|
2751
2846
|
async (req, reply) => {
|
|
2752
2847
|
const offset = Number.parseInt(req.query.offset ?? "", 10);
|
|
2753
2848
|
const limit = Number.parseInt(req.query.limit ?? "", 10);
|
|
@@ -2764,10 +2859,10 @@ function registerSessionRoutes(app, manager) {
|
|
|
2764
2859
|
}
|
|
2765
2860
|
);
|
|
2766
2861
|
app.post(
|
|
2767
|
-
"/api/
|
|
2862
|
+
"/api/chats/:id/prompt",
|
|
2768
2863
|
async (req, reply) => {
|
|
2769
2864
|
try {
|
|
2770
|
-
await manager.runPrompt(req.params.id, req.body.prompt);
|
|
2865
|
+
await manager.runPrompt(req.params.id, req.body.prompt, req.body.attachments);
|
|
2771
2866
|
return { ok: true };
|
|
2772
2867
|
} catch (err) {
|
|
2773
2868
|
reply.code(400);
|
|
@@ -2775,33 +2870,33 @@ function registerSessionRoutes(app, manager) {
|
|
|
2775
2870
|
}
|
|
2776
2871
|
}
|
|
2777
2872
|
);
|
|
2778
|
-
app.post("/api/
|
|
2873
|
+
app.post("/api/chats/:id/cancel", async (req) => {
|
|
2779
2874
|
manager.cancel(req.params.id);
|
|
2780
2875
|
return { ok: true };
|
|
2781
2876
|
});
|
|
2782
2877
|
app.post(
|
|
2783
|
-
"/api/
|
|
2878
|
+
"/api/chats/:id/model",
|
|
2784
2879
|
async (req) => {
|
|
2785
2880
|
await manager.setModel(req.params.id, req.body.modelId);
|
|
2786
2881
|
return { ok: true };
|
|
2787
2882
|
}
|
|
2788
2883
|
);
|
|
2789
2884
|
app.post(
|
|
2790
|
-
"/api/
|
|
2885
|
+
"/api/chats/:id/mode",
|
|
2791
2886
|
async (req) => {
|
|
2792
2887
|
await manager.setMode(req.params.id, req.body.modeId);
|
|
2793
2888
|
return { ok: true };
|
|
2794
2889
|
}
|
|
2795
2890
|
);
|
|
2796
2891
|
app.post(
|
|
2797
|
-
"/api/
|
|
2892
|
+
"/api/chats/:id/rename",
|
|
2798
2893
|
async (req) => {
|
|
2799
|
-
manager.
|
|
2894
|
+
manager.renameChat(req.params.id, req.body.title);
|
|
2800
2895
|
return { ok: true };
|
|
2801
2896
|
}
|
|
2802
2897
|
);
|
|
2803
2898
|
app.post(
|
|
2804
|
-
"/api/
|
|
2899
|
+
"/api/chats/:id/cwd",
|
|
2805
2900
|
async (req, reply) => {
|
|
2806
2901
|
const cwd = (req.body?.cwd ?? "").trim();
|
|
2807
2902
|
if (!cwd) {
|
|
@@ -2809,66 +2904,41 @@ function registerSessionRoutes(app, manager) {
|
|
|
2809
2904
|
return { error: "cwd is required" };
|
|
2810
2905
|
}
|
|
2811
2906
|
try {
|
|
2812
|
-
return { ok: true, cwd: await manager.
|
|
2907
|
+
return { ok: true, cwd: await manager.setChatCwd(req.params.id, cwd) };
|
|
2813
2908
|
} catch (err) {
|
|
2814
2909
|
reply.code(400);
|
|
2815
2910
|
return { error: err.message };
|
|
2816
2911
|
}
|
|
2817
2912
|
}
|
|
2818
2913
|
);
|
|
2819
|
-
app.post("/api/
|
|
2914
|
+
app.post("/api/chats/:id/reload", async (req, reply) => {
|
|
2820
2915
|
try {
|
|
2821
|
-
return await manager.
|
|
2916
|
+
return await manager.reloadChat(req.params.id);
|
|
2822
2917
|
} catch (err) {
|
|
2823
2918
|
reply.code(400);
|
|
2824
2919
|
return { error: err.message };
|
|
2825
2920
|
}
|
|
2826
2921
|
});
|
|
2827
|
-
app.delete("/api/
|
|
2828
|
-
await manager.
|
|
2922
|
+
app.delete("/api/chats/:id", async (req) => {
|
|
2923
|
+
await manager.deleteChat(req.params.id);
|
|
2829
2924
|
return { ok: true };
|
|
2830
2925
|
});
|
|
2831
2926
|
}
|
|
2832
|
-
var
|
|
2833
|
-
"server/src/routes/
|
|
2927
|
+
var init_chats2 = __esm({
|
|
2928
|
+
"server/src/routes/chats.ts"() {
|
|
2834
2929
|
"use strict";
|
|
2835
2930
|
}
|
|
2836
2931
|
});
|
|
2837
2932
|
|
|
2838
2933
|
// server/src/routes/workspace.ts
|
|
2839
|
-
import
|
|
2840
|
-
import { createReadStream as
|
|
2841
|
-
import
|
|
2842
|
-
function tooLargeForPreview(size, maxBytes) {
|
|
2843
|
-
return {
|
|
2844
|
-
error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
|
|
2845
|
-
};
|
|
2846
|
-
}
|
|
2847
|
-
function hexdump(buf) {
|
|
2848
|
-
const lines = [];
|
|
2849
|
-
for (let off = 0; off < buf.length; off += 16) {
|
|
2850
|
-
const slice = buf.subarray(off, off + 16);
|
|
2851
|
-
const hex = [];
|
|
2852
|
-
let ascii = "";
|
|
2853
|
-
for (let i = 0; i < 16; i++) {
|
|
2854
|
-
if (i < slice.length) {
|
|
2855
|
-
const b = slice[i];
|
|
2856
|
-
hex.push(b.toString(16).padStart(2, "0"));
|
|
2857
|
-
ascii += b >= 32 && b <= 126 ? String.fromCharCode(b) : ".";
|
|
2858
|
-
} else {
|
|
2859
|
-
hex.push(" ");
|
|
2860
|
-
}
|
|
2861
|
-
if (i === 7) hex.push("");
|
|
2862
|
-
}
|
|
2863
|
-
lines.push(`${off.toString(16).padStart(8, "0")} ${hex.join(" ")} |${ascii}|`);
|
|
2864
|
-
}
|
|
2865
|
-
return lines.join("\n");
|
|
2866
|
-
}
|
|
2934
|
+
import fs14 from "node:fs/promises";
|
|
2935
|
+
import { createReadStream as createReadStream3 } from "node:fs";
|
|
2936
|
+
import path16 from "node:path";
|
|
2867
2937
|
function registerWorkspaceRoutes(app, manager) {
|
|
2868
2938
|
app.get(
|
|
2869
|
-
"/api/
|
|
2939
|
+
"/api/chats/:id/tree",
|
|
2870
2940
|
async (req, reply) => {
|
|
2871
|
-
const resolved = await
|
|
2941
|
+
const resolved = await resolveChatPath(
|
|
2872
2942
|
manager,
|
|
2873
2943
|
req.params.id,
|
|
2874
2944
|
req.query.path,
|
|
@@ -2878,7 +2948,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2878
2948
|
const { cwd, relative, real: realTarget } = resolved;
|
|
2879
2949
|
let dirents;
|
|
2880
2950
|
try {
|
|
2881
|
-
dirents = await
|
|
2951
|
+
dirents = await fs14.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
|
|
2882
2952
|
} catch {
|
|
2883
2953
|
return replyWith(reply, await workspaceNotFound(cwd));
|
|
2884
2954
|
}
|
|
@@ -2892,7 +2962,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2892
2962
|
continue;
|
|
2893
2963
|
}
|
|
2894
2964
|
try {
|
|
2895
|
-
const stat = await
|
|
2965
|
+
const stat = await fs14.stat(target.real);
|
|
2896
2966
|
entries.push({
|
|
2897
2967
|
name: d.name,
|
|
2898
2968
|
path: entryRelative,
|
|
@@ -2912,17 +2982,17 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2912
2982
|
}
|
|
2913
2983
|
);
|
|
2914
2984
|
app.get(
|
|
2915
|
-
"/api/
|
|
2985
|
+
"/api/chats/:id/download",
|
|
2916
2986
|
async (req, reply) => {
|
|
2917
|
-
const resolved = await
|
|
2987
|
+
const resolved = await resolveChatPath(manager, req.params.id, req.query.path, "file");
|
|
2918
2988
|
if (!resolved.ok) return replyWith(reply, resolved);
|
|
2919
2989
|
const { real: realTarget, stat } = resolved;
|
|
2920
2990
|
if (stat.size > MAX_DOWNLOAD_BYTES) {
|
|
2921
2991
|
reply.code(413);
|
|
2922
2992
|
return { error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 100 MB)` };
|
|
2923
2993
|
}
|
|
2924
|
-
const ext =
|
|
2925
|
-
const filename =
|
|
2994
|
+
const ext = path16.extname(realTarget);
|
|
2995
|
+
const filename = path16.basename(realTarget);
|
|
2926
2996
|
const asciiName = filename.replace(/[^\w.\-]/g, "_");
|
|
2927
2997
|
reply.header("Content-Type", mimeForExt(ext));
|
|
2928
2998
|
reply.header(
|
|
@@ -2930,141 +3000,61 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
2930
3000
|
`attachment; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(filename)}`
|
|
2931
3001
|
);
|
|
2932
3002
|
reply.header("Content-Length", stat.size);
|
|
2933
|
-
return reply.send(
|
|
3003
|
+
return reply.send(createReadStream3(realTarget));
|
|
2934
3004
|
}
|
|
2935
3005
|
);
|
|
2936
3006
|
app.get(
|
|
2937
|
-
"/api/
|
|
3007
|
+
"/api/chats/:id/preview",
|
|
2938
3008
|
async (req, reply) => {
|
|
2939
|
-
const resolved = await
|
|
3009
|
+
const resolved = await resolveChatPath(manager, req.params.id, req.query.path, "file");
|
|
2940
3010
|
if (!resolved.ok) return replyWith(reply, resolved);
|
|
2941
3011
|
const { real: realTarget, stat } = resolved;
|
|
2942
|
-
|
|
2943
|
-
const mime = mimeForExt(ext);
|
|
2944
|
-
const isImage = mime.startsWith("image/");
|
|
2945
|
-
const isPdf = mime === "application/pdf";
|
|
2946
|
-
const isHtml = mime === "text/html" && req.query.raw === "1";
|
|
2947
|
-
const kind = classifyKind(realTarget);
|
|
2948
|
-
if (isImage || isPdf) {
|
|
2949
|
-
if (stat.size > MAX_IMAGE_PREVIEW_BYTES) {
|
|
2950
|
-
reply.code(413);
|
|
2951
|
-
return tooLargeForPreview(stat.size, MAX_IMAGE_PREVIEW_BYTES);
|
|
2952
|
-
}
|
|
2953
|
-
} else if (kind !== "binary") {
|
|
2954
|
-
if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
|
|
2955
|
-
reply.code(413);
|
|
2956
|
-
return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
if (isHtml) {
|
|
2960
|
-
if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
|
|
2961
|
-
reply.code(413);
|
|
2962
|
-
return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
|
|
2963
|
-
}
|
|
2964
|
-
reply.header("Content-Security-Policy", "sandbox allow-scripts allow-forms");
|
|
2965
|
-
reply.header("Content-Type", "text/html; charset=utf-8");
|
|
2966
|
-
reply.header("Cache-Control", "no-store");
|
|
2967
|
-
return reply.send(createReadStream2(realTarget));
|
|
2968
|
-
}
|
|
2969
|
-
if (isImage || isPdf) {
|
|
2970
|
-
const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
|
|
2971
|
-
reply.header("Cache-Control", "private, no-cache");
|
|
2972
|
-
reply.header("ETag", etag);
|
|
2973
|
-
reply.header("Last-Modified", stat.mtime.toUTCString());
|
|
2974
|
-
const ifNoneMatch = req.headers["if-none-match"];
|
|
2975
|
-
if (ifNoneMatch === etag) {
|
|
2976
|
-
reply.code(304);
|
|
2977
|
-
return reply.send();
|
|
2978
|
-
}
|
|
2979
|
-
reply.header("Content-Type", mime);
|
|
2980
|
-
reply.header("Content-Disposition", "inline");
|
|
2981
|
-
reply.header("Content-Length", stat.size);
|
|
2982
|
-
return reply.send(createReadStream2(realTarget));
|
|
2983
|
-
}
|
|
2984
|
-
if (kind === "binary") {
|
|
2985
|
-
let fh;
|
|
2986
|
-
try {
|
|
2987
|
-
fh = await fs13.open(realTarget, "r");
|
|
2988
|
-
const buf = Buffer.alloc(Math.min(HEXDUMP_BYTES, stat.size));
|
|
2989
|
-
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
2990
|
-
const head = buf.subarray(0, bytesRead);
|
|
2991
|
-
if (!looksBinary(head)) {
|
|
2992
|
-
if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
|
|
2993
|
-
reply.code(413);
|
|
2994
|
-
return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
|
|
2995
|
-
}
|
|
2996
|
-
reply.header("Content-Type", "text/plain; charset=utf-8");
|
|
2997
|
-
reply.header("Content-Disposition", "inline");
|
|
2998
|
-
reply.header("Content-Length", stat.size);
|
|
2999
|
-
return reply.send(createReadStream2(realTarget));
|
|
3000
|
-
}
|
|
3001
|
-
const dump = hexdump(head);
|
|
3002
|
-
const header = `Binary file - ${stat.size} bytes
|
|
3003
|
-
Showing first ${bytesRead} bytes as hexdump:
|
|
3004
|
-
|
|
3005
|
-
`;
|
|
3006
|
-
const body = header + dump + (stat.size > bytesRead ? "\n\n\u2026 (truncated)" : "");
|
|
3007
|
-
reply.header("Content-Type", "text/plain; charset=utf-8");
|
|
3008
|
-
reply.header("Content-Disposition", "inline");
|
|
3009
|
-
return reply.send(body);
|
|
3010
|
-
} finally {
|
|
3011
|
-
await fh?.close();
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
reply.header("Content-Type", "text/plain; charset=utf-8");
|
|
3015
|
-
reply.header("Content-Disposition", "inline");
|
|
3016
|
-
reply.header("Content-Length", stat.size);
|
|
3017
|
-
return reply.send(createReadStream2(realTarget));
|
|
3012
|
+
return sendFilePreview(req, reply, realTarget, stat);
|
|
3018
3013
|
}
|
|
3019
3014
|
);
|
|
3020
3015
|
}
|
|
3021
|
-
var MAX_DOWNLOAD_BYTES
|
|
3016
|
+
var MAX_DOWNLOAD_BYTES;
|
|
3022
3017
|
var init_workspace = __esm({
|
|
3023
3018
|
"server/src/routes/workspace.ts"() {
|
|
3024
3019
|
"use strict";
|
|
3025
3020
|
init_config();
|
|
3026
3021
|
init_confinedFile();
|
|
3027
3022
|
init_filekind();
|
|
3023
|
+
init_filePreview();
|
|
3028
3024
|
MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
|
|
3029
|
-
HEXDUMP_BYTES = 4096;
|
|
3030
|
-
MAX_TEXT_PREVIEW_BYTES = 1024 * 1024;
|
|
3031
|
-
MAX_IMAGE_PREVIEW_BYTES = 20 * 1024 * 1024;
|
|
3032
3025
|
}
|
|
3033
3026
|
});
|
|
3034
3027
|
|
|
3035
3028
|
// server/src/routes/uploads.ts
|
|
3036
|
-
import { createReadStream as
|
|
3037
|
-
import
|
|
3038
|
-
import
|
|
3039
|
-
import
|
|
3029
|
+
import { createReadStream as createReadStream4, createWriteStream } from "node:fs";
|
|
3030
|
+
import fs15 from "node:fs/promises";
|
|
3031
|
+
import path17 from "node:path";
|
|
3032
|
+
import crypto4 from "node:crypto";
|
|
3040
3033
|
import { execFile as execFile3 } from "node:child_process";
|
|
3041
3034
|
import { pipeline } from "node:stream/promises";
|
|
3042
|
-
function uploadDirFor(sessionId) {
|
|
3043
|
-
return path16.join(config.casperDataDir, "sessions", sessionId, "uploads");
|
|
3044
|
-
}
|
|
3045
3035
|
function sanitizeName(raw) {
|
|
3046
|
-
const base =
|
|
3036
|
+
const base = path17.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
|
|
3047
3037
|
if (!base || base === "." || base === "..") return `upload-${Date.now()}`;
|
|
3048
3038
|
return base.replace(/^\.+/, "") || `upload-${Date.now()}`;
|
|
3049
3039
|
}
|
|
3050
3040
|
async function uniquePath(dir, name) {
|
|
3051
|
-
const ext =
|
|
3041
|
+
const ext = path17.extname(name);
|
|
3052
3042
|
const stem = name.slice(0, name.length - ext.length);
|
|
3053
3043
|
let candidate = name;
|
|
3054
3044
|
let n = 1;
|
|
3055
3045
|
while (true) {
|
|
3056
3046
|
try {
|
|
3057
|
-
await
|
|
3047
|
+
await fs15.access(path17.join(dir, candidate));
|
|
3058
3048
|
candidate = `${stem} (${n++})${ext}`;
|
|
3059
3049
|
} catch {
|
|
3060
|
-
return
|
|
3050
|
+
return path17.join(dir, candidate);
|
|
3061
3051
|
}
|
|
3062
3052
|
}
|
|
3063
3053
|
}
|
|
3064
3054
|
function sha256File(absPath) {
|
|
3065
3055
|
return new Promise((resolve, reject) => {
|
|
3066
|
-
const hash =
|
|
3067
|
-
|
|
3056
|
+
const hash = crypto4.createHash("sha256");
|
|
3057
|
+
createReadStream4(absPath).on("data", (d) => hash.update(d)).on("end", () => resolve(hash.digest("hex"))).on("error", reject);
|
|
3068
3058
|
});
|
|
3069
3059
|
}
|
|
3070
3060
|
function fileType(absPath) {
|
|
@@ -3077,7 +3067,7 @@ function fileType(absPath) {
|
|
|
3077
3067
|
async function sampleStrings(absPath, limit = 40) {
|
|
3078
3068
|
let fh;
|
|
3079
3069
|
try {
|
|
3080
|
-
fh = await
|
|
3070
|
+
fh = await fs15.open(absPath, "r");
|
|
3081
3071
|
const buf = Buffer.alloc(STRINGS_SCAN_BYTES);
|
|
3082
3072
|
const { bytesRead } = await fh.read(buf, 0, STRINGS_SCAN_BYTES, 0);
|
|
3083
3073
|
const out = [];
|
|
@@ -3100,22 +3090,16 @@ async function sampleStrings(absPath, limit = 40) {
|
|
|
3100
3090
|
await fh?.close();
|
|
3101
3091
|
}
|
|
3102
3092
|
}
|
|
3103
|
-
function registerUploadRoutes(app
|
|
3093
|
+
function registerUploadRoutes(app) {
|
|
3104
3094
|
app.post(
|
|
3105
|
-
"/api/
|
|
3095
|
+
"/api/chats/:chatId/uploads",
|
|
3106
3096
|
async (req, reply) => {
|
|
3107
|
-
|
|
3108
|
-
await manager.getSessionCwd(req.params.id);
|
|
3109
|
-
} catch {
|
|
3110
|
-
reply.code(404);
|
|
3111
|
-
return { error: "Session not found" };
|
|
3112
|
-
}
|
|
3113
|
-
if (!isValidSessionId(req.params.id)) {
|
|
3097
|
+
if (!isValidChatId(req.params.chatId)) {
|
|
3114
3098
|
reply.code(400);
|
|
3115
|
-
return { error: "Invalid
|
|
3099
|
+
return { error: "Invalid chat id" };
|
|
3116
3100
|
}
|
|
3117
|
-
const uploadDir =
|
|
3118
|
-
await
|
|
3101
|
+
const uploadDir = chatUploadsDir(req.params.chatId);
|
|
3102
|
+
await fs15.mkdir(uploadDir, { recursive: true, mode: 448 });
|
|
3119
3103
|
const results = [];
|
|
3120
3104
|
const parts = req.files();
|
|
3121
3105
|
for await (const part of parts) {
|
|
@@ -3129,17 +3113,17 @@ function registerUploadRoutes(app, manager) {
|
|
|
3129
3113
|
return { error: `Failed to store ${name}` };
|
|
3130
3114
|
}
|
|
3131
3115
|
if (part.file.truncated) {
|
|
3132
|
-
await
|
|
3116
|
+
await fs15.rm(dest, { force: true });
|
|
3133
3117
|
reply.code(413);
|
|
3134
3118
|
return {
|
|
3135
3119
|
error: `${name} exceeds the max upload size (${Math.round(config.maxUploadBytes / 1024 / 1024)} MB)`
|
|
3136
3120
|
};
|
|
3137
3121
|
}
|
|
3138
|
-
const stat = await
|
|
3139
|
-
const ext =
|
|
3122
|
+
const stat = await fs15.stat(dest);
|
|
3123
|
+
const ext = path17.extname(dest);
|
|
3140
3124
|
const kind = classifyKind(dest);
|
|
3141
3125
|
const uploaded = {
|
|
3142
|
-
name:
|
|
3126
|
+
name: path17.basename(dest),
|
|
3143
3127
|
// Absolute, because the file sits outside the session's workspace: a
|
|
3144
3128
|
// relative path would resolve against the wrong root for both the agent
|
|
3145
3129
|
// and the image endpoint.
|
|
@@ -3149,14 +3133,14 @@ function registerUploadRoutes(app, manager) {
|
|
|
3149
3133
|
kind
|
|
3150
3134
|
};
|
|
3151
3135
|
if (kind === "binary") {
|
|
3152
|
-
const [type,
|
|
3136
|
+
const [type, sha2562, strings] = await Promise.all([
|
|
3153
3137
|
fileType(dest),
|
|
3154
3138
|
sha256File(dest),
|
|
3155
3139
|
sampleStrings(dest)
|
|
3156
3140
|
]);
|
|
3157
3141
|
uploaded.triage = {
|
|
3158
3142
|
fileType: type || void 0,
|
|
3159
|
-
sha256:
|
|
3143
|
+
sha256: sha2562,
|
|
3160
3144
|
strings: strings.length ? strings : void 0
|
|
3161
3145
|
};
|
|
3162
3146
|
}
|
|
@@ -3174,6 +3158,7 @@ var init_uploads = __esm({
|
|
|
3174
3158
|
init_config();
|
|
3175
3159
|
init_filekind();
|
|
3176
3160
|
init_paths2();
|
|
3161
|
+
init_chats();
|
|
3177
3162
|
STRINGS_SCAN_BYTES = 256 * 1024;
|
|
3178
3163
|
}
|
|
3179
3164
|
});
|
|
@@ -3197,7 +3182,7 @@ var init_health = __esm({
|
|
|
3197
3182
|
});
|
|
3198
3183
|
|
|
3199
3184
|
// server/src/ws/dirWatchers.ts
|
|
3200
|
-
import
|
|
3185
|
+
import fs16 from "node:fs";
|
|
3201
3186
|
function diffWatchSet(current, next) {
|
|
3202
3187
|
const have = new Set(current);
|
|
3203
3188
|
const want = new Set(next.slice(0, MAX_WATCHES));
|
|
@@ -3221,7 +3206,7 @@ function createDirWatchers(opts) {
|
|
|
3221
3206
|
const target = await opts.resolve(relative);
|
|
3222
3207
|
if (!target || closed) return;
|
|
3223
3208
|
try {
|
|
3224
|
-
const watcher =
|
|
3209
|
+
const watcher = fs16.watch(target, { persistent: false }, () => {
|
|
3225
3210
|
const existing = timers.get(relative);
|
|
3226
3211
|
if (existing) clearTimeout(existing);
|
|
3227
3212
|
timers.set(
|
|
@@ -3266,7 +3251,7 @@ var init_dirWatchers = __esm({
|
|
|
3266
3251
|
function send(socket, msg) {
|
|
3267
3252
|
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify(msg));
|
|
3268
3253
|
}
|
|
3269
|
-
function handleConnection(socket, manager,
|
|
3254
|
+
function handleConnection(socket, manager, chatId, startCursor) {
|
|
3270
3255
|
let cursor = startCursor;
|
|
3271
3256
|
let unsubscribe = null;
|
|
3272
3257
|
let alive = true;
|
|
@@ -3274,12 +3259,12 @@ function handleConnection(socket, manager, sessionId, startCursor) {
|
|
|
3274
3259
|
const watchers = createDirWatchers({
|
|
3275
3260
|
resolve: async (relative) => {
|
|
3276
3261
|
try {
|
|
3277
|
-
return confineToRoot(await manager.
|
|
3262
|
+
return confineToRoot(await manager.getChatCwd(chatId), relative);
|
|
3278
3263
|
} catch {
|
|
3279
3264
|
return null;
|
|
3280
3265
|
}
|
|
3281
3266
|
},
|
|
3282
|
-
onChange: (
|
|
3267
|
+
onChange: (path20) => send(socket, { type: "fs_changed", path: path20 })
|
|
3283
3268
|
});
|
|
3284
3269
|
const forward = (event) => {
|
|
3285
3270
|
if (event.seq <= cursor) return;
|
|
@@ -3288,13 +3273,13 @@ function handleConnection(socket, manager, sessionId, startCursor) {
|
|
|
3288
3273
|
};
|
|
3289
3274
|
const attach = async () => {
|
|
3290
3275
|
try {
|
|
3291
|
-
await manager.ensureOpen(
|
|
3276
|
+
await manager.ensureOpen(chatId);
|
|
3292
3277
|
} catch (err) {
|
|
3293
3278
|
send(socket, { type: "error", message: err.message });
|
|
3294
3279
|
socket.close(1011, "open failed");
|
|
3295
3280
|
return;
|
|
3296
3281
|
}
|
|
3297
|
-
const store = manager.getStore(
|
|
3282
|
+
const store = manager.getStore(chatId);
|
|
3298
3283
|
if (!store) {
|
|
3299
3284
|
send(socket, { type: "error", message: "Session store unavailable" });
|
|
3300
3285
|
socket.close(1011, "no store");
|
|
@@ -3311,7 +3296,7 @@ function handleConnection(socket, manager, sessionId, startCursor) {
|
|
|
3311
3296
|
for (const e of events) forward(e);
|
|
3312
3297
|
}
|
|
3313
3298
|
send(socket, { type: "replay_complete", head: store.head() });
|
|
3314
|
-
unsubscribe = manager.onEvent(
|
|
3299
|
+
unsubscribe = manager.onEvent(chatId, forward);
|
|
3315
3300
|
ready = true;
|
|
3316
3301
|
};
|
|
3317
3302
|
void attach();
|
|
@@ -3330,17 +3315,17 @@ function handleConnection(socket, manager, sessionId, startCursor) {
|
|
|
3330
3315
|
case "watch_paths":
|
|
3331
3316
|
return watchers.sync(msg.paths);
|
|
3332
3317
|
case "prompt":
|
|
3333
|
-
return ack("prompt", () => manager.runPrompt(
|
|
3318
|
+
return ack("prompt", () => manager.runPrompt(chatId, msg.content, msg.attachments));
|
|
3334
3319
|
case "cancel":
|
|
3335
|
-
return ack("cancel", () => manager.cancel(
|
|
3320
|
+
return ack("cancel", () => manager.cancel(chatId));
|
|
3336
3321
|
case "set_mode":
|
|
3337
|
-
return ack("set_mode", () => manager.setMode(
|
|
3322
|
+
return ack("set_mode", () => manager.setMode(chatId, msg.modeId));
|
|
3338
3323
|
case "set_model":
|
|
3339
|
-
return ack("set_model", () => manager.setModel(
|
|
3324
|
+
return ack("set_model", () => manager.setModel(chatId, msg.modelId));
|
|
3340
3325
|
case "exec_command":
|
|
3341
3326
|
return ack(
|
|
3342
3327
|
"exec_command",
|
|
3343
|
-
() => manager.execCommand(
|
|
3328
|
+
() => manager.execCommand(chatId, msg.command)
|
|
3344
3329
|
);
|
|
3345
3330
|
default:
|
|
3346
3331
|
return send(socket, { type: "error", message: "Unknown message type" });
|
|
@@ -3387,15 +3372,15 @@ function registerWsGateway(app, manager) {
|
|
|
3387
3372
|
socket.close(1008, "Unauthorized");
|
|
3388
3373
|
return;
|
|
3389
3374
|
}
|
|
3390
|
-
if (!query.
|
|
3391
|
-
send(socket, { type: "error", message: "Missing
|
|
3392
|
-
socket.close(1008, "Missing
|
|
3375
|
+
if (!query.chatId) {
|
|
3376
|
+
send(socket, { type: "error", message: "Missing chatId" });
|
|
3377
|
+
socket.close(1008, "Missing chatId");
|
|
3393
3378
|
return;
|
|
3394
3379
|
}
|
|
3395
3380
|
handleConnection(
|
|
3396
3381
|
socket,
|
|
3397
3382
|
manager,
|
|
3398
|
-
query.
|
|
3383
|
+
query.chatId,
|
|
3399
3384
|
Number.parseInt(query.cursor ?? "0", 10) || 0
|
|
3400
3385
|
);
|
|
3401
3386
|
});
|
|
@@ -3412,9 +3397,8 @@ var init_gateway = __esm({
|
|
|
3412
3397
|
});
|
|
3413
3398
|
|
|
3414
3399
|
// server/src/app.ts
|
|
3415
|
-
import
|
|
3400
|
+
import fs17 from "node:fs";
|
|
3416
3401
|
import Fastify from "fastify";
|
|
3417
|
-
import cors from "@fastify/cors";
|
|
3418
3402
|
import websocket from "@fastify/websocket";
|
|
3419
3403
|
import fastifyStatic from "@fastify/static";
|
|
3420
3404
|
import multipart from "@fastify/multipart";
|
|
@@ -3434,7 +3418,6 @@ async function buildApp() {
|
|
|
3434
3418
|
});
|
|
3435
3419
|
const manager = new SessionManager(logger);
|
|
3436
3420
|
const startedAt = Date.now();
|
|
3437
|
-
await app.register(cors, { origin: true });
|
|
3438
3421
|
await app.register(websocket, {
|
|
3439
3422
|
options: { maxPayload: 16 * 1024 * 1024 }
|
|
3440
3423
|
});
|
|
@@ -3446,24 +3429,24 @@ async function buildApp() {
|
|
|
3446
3429
|
registerModelRoutes(app);
|
|
3447
3430
|
registerAgentRoutes(app);
|
|
3448
3431
|
registerFsRoutes(app);
|
|
3449
|
-
|
|
3432
|
+
registerChatRoutes(app, manager);
|
|
3450
3433
|
registerWorkspaceRoutes(app, manager);
|
|
3451
|
-
registerUploadRoutes(app
|
|
3434
|
+
registerUploadRoutes(app);
|
|
3452
3435
|
registerWsGateway(app, manager);
|
|
3453
|
-
if (
|
|
3436
|
+
if (fs17.existsSync(config.webDist)) {
|
|
3454
3437
|
await app.register(fastifyStatic, {
|
|
3455
3438
|
root: config.webDist
|
|
3456
3439
|
});
|
|
3457
3440
|
app.addHook("onSend", async (req, reply) => {
|
|
3458
|
-
const
|
|
3459
|
-
if (
|
|
3441
|
+
const path20 = req.url.split("?")[0] ?? "";
|
|
3442
|
+
if (path20 === "/manifest.json" || path20.startsWith("/icons/")) {
|
|
3460
3443
|
reply.header("cache-control", "public, max-age=300");
|
|
3461
3444
|
}
|
|
3462
3445
|
});
|
|
3463
3446
|
app.setNotFoundHandler((req, reply) => {
|
|
3464
|
-
const
|
|
3465
|
-
const looksLikeAsset =
|
|
3466
|
-
if (
|
|
3447
|
+
const path20 = req.url.split("?")[0] ?? "";
|
|
3448
|
+
const looksLikeAsset = path20.startsWith("/assets/") || /\.[a-zA-Z0-9]+$/.test(path20);
|
|
3449
|
+
if (path20.startsWith("/api/") || path20.startsWith("/ws") || looksLikeAsset) {
|
|
3467
3450
|
reply.code(404).send({ error: "Not found" });
|
|
3468
3451
|
return;
|
|
3469
3452
|
}
|
|
@@ -3482,7 +3465,7 @@ var init_app = __esm({
|
|
|
3482
3465
|
init_models2();
|
|
3483
3466
|
init_agents2();
|
|
3484
3467
|
init_fs();
|
|
3485
|
-
|
|
3468
|
+
init_chats2();
|
|
3486
3469
|
init_workspace();
|
|
3487
3470
|
init_uploads();
|
|
3488
3471
|
init_health();
|
|
@@ -3537,15 +3520,15 @@ __export(doctor_exports, {
|
|
|
3537
3520
|
mcpWiring: () => mcpWiring
|
|
3538
3521
|
});
|
|
3539
3522
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3540
|
-
import
|
|
3541
|
-
import
|
|
3523
|
+
import fs18 from "node:fs";
|
|
3524
|
+
import path18 from "node:path";
|
|
3542
3525
|
function which(bin) {
|
|
3543
|
-
if (bin.includes("/")) return
|
|
3544
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
3526
|
+
if (bin.includes("/")) return fs18.existsSync(bin) ? bin : void 0;
|
|
3527
|
+
for (const dir of (process.env.PATH ?? "").split(path18.delimiter)) {
|
|
3545
3528
|
if (dir === "") continue;
|
|
3546
|
-
const candidate =
|
|
3529
|
+
const candidate = path18.join(dir, bin);
|
|
3547
3530
|
try {
|
|
3548
|
-
|
|
3531
|
+
fs18.accessSync(candidate, fs18.constants.X_OK);
|
|
3549
3532
|
return candidate;
|
|
3550
3533
|
} catch {
|
|
3551
3534
|
}
|
|
@@ -3569,7 +3552,7 @@ function mcpWiring(agentJson, resolve) {
|
|
|
3569
3552
|
return { ok: false, detail: `${command || "(no command)"} not found - widgets are unavailable` };
|
|
3570
3553
|
}
|
|
3571
3554
|
const script = args.find((a) => a.endsWith(".js"));
|
|
3572
|
-
if (script && !
|
|
3555
|
+
if (script && !fs18.existsSync(script)) {
|
|
3573
3556
|
return { ok: false, detail: `${script} is gone - delete the agent file and restart to rewrite it` };
|
|
3574
3557
|
}
|
|
3575
3558
|
return { ok: true, detail: script ?? `${command} ${args.join(" ")}` };
|
|
@@ -3601,7 +3584,7 @@ function checks() {
|
|
|
3601
3584
|
const settings = readSettings(config.configFile);
|
|
3602
3585
|
const hasToken = typeof settings.token === "string" && settings.token !== "";
|
|
3603
3586
|
out.push(
|
|
3604
|
-
|
|
3587
|
+
fs18.existsSync(config.configFile) ? {
|
|
3605
3588
|
ok: hasToken,
|
|
3606
3589
|
name: "settings",
|
|
3607
3590
|
detail: hasToken ? `${config.configFile} (token set)` : `${config.configFile} exists but has no token - auth is DISABLED`
|
|
@@ -3611,8 +3594,8 @@ function checks() {
|
|
|
3611
3594
|
detail: `${config.configFile} not found; a token is generated on first run`
|
|
3612
3595
|
}
|
|
3613
3596
|
);
|
|
3614
|
-
if (
|
|
3615
|
-
const mode =
|
|
3597
|
+
if (fs18.existsSync(config.configFile)) {
|
|
3598
|
+
const mode = fs18.statSync(config.configFile).mode & 511;
|
|
3616
3599
|
out.push({
|
|
3617
3600
|
ok: mode === 384,
|
|
3618
3601
|
name: "settings mode",
|
|
@@ -3620,10 +3603,10 @@ function checks() {
|
|
|
3620
3603
|
});
|
|
3621
3604
|
}
|
|
3622
3605
|
try {
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
const db2 =
|
|
3626
|
-
const size =
|
|
3606
|
+
fs18.mkdirSync(config.casperDataDir, { recursive: true });
|
|
3607
|
+
fs18.accessSync(config.casperDataDir, fs18.constants.W_OK);
|
|
3608
|
+
const db2 = path18.join(config.casperDataDir, "casper.db");
|
|
3609
|
+
const size = fs18.existsSync(db2) ? `, casper.db ${(fs18.statSync(db2).size / 1024).toFixed(0)} KB` : "";
|
|
3627
3610
|
out.push({ ok: true, name: "data dir", detail: `${config.casperDataDir} writable${size}` });
|
|
3628
3611
|
} catch (err) {
|
|
3629
3612
|
out.push({
|
|
@@ -3634,22 +3617,22 @@ function checks() {
|
|
|
3634
3617
|
});
|
|
3635
3618
|
}
|
|
3636
3619
|
out.push(
|
|
3637
|
-
|
|
3620
|
+
fs18.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
|
|
3638
3621
|
ok: false,
|
|
3639
3622
|
fatal: true,
|
|
3640
3623
|
name: "web app",
|
|
3641
3624
|
detail: `${config.webDist} not found - the package is incomplete or CASPER_WEB_DIST is wrong`
|
|
3642
3625
|
}
|
|
3643
3626
|
);
|
|
3644
|
-
const agent =
|
|
3645
|
-
const hasAgent =
|
|
3627
|
+
const agent = path18.join(config.kiroSessionsDir, "..", "..", "agents", "casper.json");
|
|
3628
|
+
const hasAgent = fs18.existsSync(agent);
|
|
3646
3629
|
out.push({
|
|
3647
3630
|
ok: hasAgent,
|
|
3648
3631
|
name: "casper agent",
|
|
3649
3632
|
detail: hasAgent ? agent : `${agent} missing - it is installed on first run`
|
|
3650
3633
|
});
|
|
3651
3634
|
if (hasAgent) {
|
|
3652
|
-
const wiring = mcpWiring(
|
|
3635
|
+
const wiring = mcpWiring(fs18.readFileSync(agent, "utf8"), which);
|
|
3653
3636
|
out.push({ ok: wiring.ok, name: "casper mcp", detail: wiring.detail });
|
|
3654
3637
|
}
|
|
3655
3638
|
return out;
|
|
@@ -4113,14 +4096,14 @@ var init_server2 = __esm({
|
|
|
4113
4096
|
"server/src/mcp/server.ts"() {
|
|
4114
4097
|
"use strict";
|
|
4115
4098
|
init_protocol();
|
|
4116
|
-
VERSION = true ? "0.
|
|
4099
|
+
VERSION = true ? "0.13.0" : "dev";
|
|
4117
4100
|
}
|
|
4118
4101
|
});
|
|
4119
4102
|
|
|
4120
4103
|
// server/src/cli/program.ts
|
|
4121
|
-
import
|
|
4104
|
+
import fs19 from "node:fs";
|
|
4122
4105
|
import os4 from "node:os";
|
|
4123
|
-
import
|
|
4106
|
+
import path19 from "node:path";
|
|
4124
4107
|
|
|
4125
4108
|
// server/node_modules/commander/lib/error.js
|
|
4126
4109
|
var CommanderError = class extends Error {
|
|
@@ -7233,9 +7216,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
7233
7216
|
* @param {string} [path]
|
|
7234
7217
|
* @return {(string|null|Command)}
|
|
7235
7218
|
*/
|
|
7236
|
-
executableDir(
|
|
7237
|
-
if (
|
|
7238
|
-
this._executableDir =
|
|
7219
|
+
executableDir(path20) {
|
|
7220
|
+
if (path20 === void 0) return this._executableDir;
|
|
7221
|
+
this._executableDir = path20;
|
|
7239
7222
|
return this;
|
|
7240
7223
|
}
|
|
7241
7224
|
/**
|
|
@@ -7493,7 +7476,7 @@ var program = new Command();
|
|
|
7493
7476
|
// server/src/cli/program.ts
|
|
7494
7477
|
init_paths();
|
|
7495
7478
|
init_settings();
|
|
7496
|
-
var VERSION2 = true ? "0.
|
|
7479
|
+
var VERSION2 = true ? "0.13.0" : "dev";
|
|
7497
7480
|
async function bootstrap() {
|
|
7498
7481
|
const file = configFilePath();
|
|
7499
7482
|
const settings = readSettings(file);
|
|
@@ -7539,8 +7522,8 @@ async function resetToken(value) {
|
|
|
7539
7522
|
updateSettings(file, { token });
|
|
7540
7523
|
process.stdout.write(`wrote a new token to ${file}
|
|
7541
7524
|
`);
|
|
7542
|
-
const dbFile =
|
|
7543
|
-
if (
|
|
7525
|
+
const dbFile = path19.join(dataDirPath(), "casper.db");
|
|
7526
|
+
if (fs19.existsSync(dbFile)) {
|
|
7544
7527
|
const { LoginStore: LoginStore2 } = await Promise.resolve().then(() => (init_logins(), logins_exports));
|
|
7545
7528
|
new LoginStore2().revokeAll();
|
|
7546
7529
|
process.stdout.write("revoked all device sessions\n");
|
|
@@ -7557,26 +7540,26 @@ async function resetToken(value) {
|
|
|
7557
7540
|
}
|
|
7558
7541
|
function buildProgram() {
|
|
7559
7542
|
const program2 = new Command();
|
|
7560
|
-
program2.name("casper").description("Web client for kiro-cli over the Agent Client Protocol").version(VERSION2, "-v, --version").showHelpAfterError().
|
|
7543
|
+
program2.name("casper").description("Web client for kiro-cli over the Agent Client Protocol").version(VERSION2, "-v, --version").showHelpAfterError().addHelpText("after", `
|
|
7561
7544
|
Settings live in ${configFilePath()}.
|
|
7562
7545
|
Update with: npm install -g @joeyshi12/casper`);
|
|
7563
|
-
program2.command("start").description("run the server in the foreground").
|
|
7546
|
+
program2.command("start").description("run the server in the foreground").action(async () => {
|
|
7564
7547
|
await bootstrap();
|
|
7565
7548
|
const { serve: serve2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
7566
7549
|
await serve2();
|
|
7567
7550
|
});
|
|
7568
|
-
program2.command("token").description("print the access token").
|
|
7569
|
-
program2.command("reset-token").argument("[value]", "token to set; generated when omitted").description("set a new token and sign every device out").
|
|
7551
|
+
program2.command("token").description("print the access token").action(printToken);
|
|
7552
|
+
program2.command("reset-token").argument("[value]", "token to set; generated when omitted").description("set a new token and sign every device out").action(async (value) => {
|
|
7570
7553
|
await resetToken(value);
|
|
7571
7554
|
});
|
|
7572
|
-
program2.command("doctor").description("check kiro-cli, settings, data directory and web app").
|
|
7555
|
+
program2.command("doctor").description("check kiro-cli, settings, data directory and web app").action(async () => {
|
|
7573
7556
|
process.exitCode = (await Promise.resolve().then(() => (init_doctor(), doctor_exports))).doctor();
|
|
7574
7557
|
});
|
|
7575
|
-
program2.command("mcp").description("run the generative-UI MCP server on stdio (kiro spawns this itself)").
|
|
7558
|
+
program2.command("mcp").description("run the generative-UI MCP server on stdio (kiro spawns this itself)").action(async () => {
|
|
7576
7559
|
(await Promise.resolve().then(() => (init_server2(), server_exports2))).runMcpServer();
|
|
7577
7560
|
});
|
|
7578
7561
|
const service = program2.command("service").description("manage the systemd user service");
|
|
7579
|
-
const svcCommand = (name, describe, run) => service.command(name).description(describe).
|
|
7562
|
+
const svcCommand = (name, describe, run) => service.command(name).description(describe).action(async () => {
|
|
7580
7563
|
process.exitCode = run(await Promise.resolve().then(() => (init_service(), service_exports)));
|
|
7581
7564
|
});
|
|
7582
7565
|
svcCommand("install", "run Casper as a systemd user service", (m) => m.serviceInstall());
|