@joeyshi12/casper 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -109
- package/dist/mcp.js +1 -1
- package/dist/server.js +1092 -156
- package/dist/web/assets/{abnfDiagram-N423BO3Z-DQdd062N.js → abnfDiagram-N423BO3Z-BoCQbAw8.js} +1 -1
- package/dist/web/assets/{arc-WhOJ_KR1.js → arc-BmlY0mzb.js} +1 -1
- package/dist/web/assets/{architectureDiagram-T3A2C74G-3zZgBQZT.js → architectureDiagram-T3A2C74G-C2X7IIaV.js} +1 -1
- package/dist/web/assets/{blockDiagram-VBNYF7ZC-CW9mWWLl.js → blockDiagram-VBNYF7ZC-Canb-rMn.js} +1 -1
- package/dist/web/assets/{c4Diagram-5PPSVZJV-xXmfR_aG.js → c4Diagram-5PPSVZJV-C9X44WTg.js} +1 -1
- package/dist/web/assets/channel-BCtauCwa.js +1 -0
- package/dist/web/assets/{chunk-2GRJ4B5K-CuCN8EG_.js → chunk-2GRJ4B5K-C2Da_I9Z.js} +1 -1
- package/dist/web/assets/{chunk-2Q5K7J3B-BC-h61jv.js → chunk-2Q5K7J3B-CzLgrMav.js} +1 -1
- package/dist/web/assets/{chunk-5RXB4S5H-BqZ4ICSN.js → chunk-5RXB4S5H-BYnnZzKN.js} +1 -1
- package/dist/web/assets/{chunk-5VM5RSS4-_EcX9Eg6.js → chunk-5VM5RSS4-ByebZ4We.js} +1 -1
- package/dist/web/assets/{chunk-6Q2QTUOP-Rjg4xt7f.js → chunk-6Q2QTUOP-CG0km2-2.js} +1 -1
- package/dist/web/assets/{chunk-GF5L2VYU-CllNzjfD.js → chunk-GF5L2VYU-BKaNg3nx.js} +1 -1
- package/dist/web/assets/{chunk-JWPE2WC7-Cc24s09e.js → chunk-JWPE2WC7-Vr0Ss2mp.js} +1 -1
- package/dist/web/assets/{chunk-KBJHAD2P-DYClY3b3.js → chunk-KBJHAD2P-CvX7eHxP.js} +1 -1
- package/dist/web/assets/{chunk-RYQCIY6F-C5PSe7M_.js → chunk-RYQCIY6F-DLiGZQrb.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-DK8-XmXo.js → chunk-XXDRQBXY-CH6MJnvh.js} +1 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-DkV-_YLp.js +1 -0
- package/dist/web/assets/classDiagram-v2-OCEON4UE-DkV-_YLp.js +1 -0
- package/dist/web/assets/{cose-bilkent-JH36ORCC-Dxs4GA2q.js → cose-bilkent-JH36ORCC-DI8vHJuJ.js} +1 -1
- package/dist/web/assets/{cynefin-VYW2F7L2-kzjViLcF.js → cynefin-VYW2F7L2-C3l8t8n8.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-MW4NZA55-B9zBzt3B.js → cynefinDiagram-MW4NZA55-Cw_aPiR-.js} +1 -1
- package/dist/web/assets/{dagre-VZM6K2ZE-BNwMkCZi.js → dagre-VZM6K2ZE-B2NBfkar.js} +1 -1
- package/dist/web/assets/{diagram-7IWD3JNH-DpIoQsS7.js → diagram-7IWD3JNH-D24sgsQz.js} +1 -1
- package/dist/web/assets/{diagram-B4RE2ZJO-BKWdzhKi.js → diagram-B4RE2ZJO-ktHJzRIT.js} +1 -1
- package/dist/web/assets/{diagram-LBJQPF4R-DBbKFigX.js → diagram-LBJQPF4R-BEl0LP5l.js} +1 -1
- package/dist/web/assets/{diagram-Q27KOJAE-B7qLPRw9.js → diagram-Q27KOJAE-BTXlE1sY.js} +1 -1
- package/dist/web/assets/{diagram-UB23O5K3-DVncy6q5.js → diagram-UB23O5K3-DxtnHSA6.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-BXEA7PRR-B9rA3ylb.js → ebnfDiagram-BXEA7PRR-C8UWxRK7.js} +1 -1
- package/dist/web/assets/{erDiagram-JOGREHBK-BExDcOc7.js → erDiagram-JOGREHBK-D4SPk_Gi.js} +1 -1
- package/dist/web/assets/{flowDiagram-UKHOOZJN-B1xPLh9u.js → flowDiagram-UKHOOZJN-WyM_6rZg.js} +1 -1
- package/dist/web/assets/{ganttDiagram-PKOTCBZU-BYS5NkM6.js → ganttDiagram-PKOTCBZU-DDAIXRE2.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-CN4An7lt.js → gitGraphDiagram-DS77QQ5N-BWPlU8VD.js} +1 -1
- package/dist/web/assets/index-B9llrrPg.js +245 -0
- package/dist/web/assets/index-Bt9aLq70.css +1 -0
- package/dist/web/assets/{index-BuQTY976.js → index-DJ9oEaf1.js} +1 -1
- package/dist/web/assets/{infoDiagram-6WML65LV-sk97M0G1.js → infoDiagram-6WML65LV-Dhaf6giw.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-BzW2rVh6.js → ishikawaDiagram-WSZJBQD7-BjswJnci.js} +1 -1
- package/dist/web/assets/{journeyDiagram-NVQOT4AX-iaCo98OS.js → journeyDiagram-NVQOT4AX-2dyGch8v.js} +1 -1
- package/dist/web/assets/{kanban-definition-27J2QSJJ-Ce38xzYm.js → kanban-definition-27J2QSJJ-CkomDTZU.js} +1 -1
- package/dist/web/assets/{linear-BoUX9N6W.js → linear-BdH2JiwZ.js} +1 -1
- package/dist/web/assets/{mermaid.core-CC54dZcH.js → mermaid.core-DKzUBYVB.js} +4 -4
- package/dist/web/assets/{mindmap-definition-FAOFIHXS-BxRXpNJ-.js → mindmap-definition-FAOFIHXS-fc37UkLx.js} +1 -1
- package/dist/web/assets/{pegDiagram-VL7TDLO6-Cg0-J9c8.js → pegDiagram-VL7TDLO6-siQVbNWL.js} +1 -1
- package/dist/web/assets/{pieDiagram-7S7Q4E2Y-DUjXAJq4.js → pieDiagram-7S7Q4E2Y-BKSj3tLB.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-q1HsW0pP.js → quadrantDiagram-CIZ2JOQS-IezqOodw.js} +1 -1
- package/dist/web/assets/{railroadDiagram-AXF67PYL-ats1_veK.js → railroadDiagram-AXF67PYL-D5ZNyWv-.js} +1 -1
- package/dist/web/assets/{requirementDiagram-LRYGKXZP-CV2_uFpI.js → requirementDiagram-LRYGKXZP--cY_DZnk.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-W5VNT64P-BwyC8qNn.js → sankeyDiagram-W5VNT64P-CrkdA0Bu.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-SI44F4Z6-72yj0uCA.js → sequenceDiagram-SI44F4Z6-CbZ29vn1.js} +1 -1
- package/dist/web/assets/{sizeCapture-X5ZJPWSS-CV7PeUrN.js → sizeCapture-X5ZJPWSS-BwhicUCV.js} +1 -1
- package/dist/web/assets/{stateDiagram-OKZ733FA-B08_WgjO.js → stateDiagram-OKZ733FA-D-L3cYRc.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-BmuXsnqZ.js +1 -0
- package/dist/web/assets/{swimlanes-SLNWSIFB-DnBKpTQH.js → swimlanes-SLNWSIFB-BD4B8cj1.js} +2 -2
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-B8QpAfjq.js +8 -0
- package/dist/web/assets/{timeline-definition-Z64GVDOM-BAoT5Fu8.js → timeline-definition-Z64GVDOM-BiNW7Sq_.js} +1 -1
- package/dist/web/assets/{vennDiagram-T6HMQDX7-VLzfwvza.js → vennDiagram-T6HMQDX7-Df5ZKPMP.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-T6FBY63Y-QaCRHB2e.js → wardleyDiagram-T6FBY63Y-2CPsSRbV.js} +1 -1
- package/dist/web/assets/{xychartDiagram-ELKLHX3M-DCUP6k1x.js → xychartDiagram-ELKLHX3M-BdscU8xa.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/dist/web/assets/channel-QybFlytR.js +0 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-CcJ6l3H0.js +0 -1
- package/dist/web/assets/classDiagram-v2-OCEON4UE-CcJ6l3H0.js +0 -1
- package/dist/web/assets/index-BwBYAAwi.js +0 -244
- package/dist/web/assets/index-CRxbcY2R.css +0 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-80B2GltS.js +0 -1
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-CeWA7IPZ.js +0 -8
package/dist/server.js
CHANGED
|
@@ -350,6 +350,9 @@ function open() {
|
|
|
350
350
|
const d = new DatabaseSync(file);
|
|
351
351
|
d.exec("PRAGMA journal_mode = WAL");
|
|
352
352
|
d.exec(SCHEMA);
|
|
353
|
+
const columns = (table) => d.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name);
|
|
354
|
+
if (!columns("kiro_models").includes("effort_key")) d.exec("ALTER TABLE kiro_models ADD COLUMN effort_key TEXT");
|
|
355
|
+
if (!columns("chats").includes("pinned")) d.exec("ALTER TABLE chats ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0");
|
|
353
356
|
restrict(config.casperDataDir, 448);
|
|
354
357
|
for (const f of [file, `${file}-wal`, `${file}-shm`]) restrict(f, 384);
|
|
355
358
|
return d;
|
|
@@ -370,7 +373,8 @@ CREATE TABLE IF NOT EXISTS chats (
|
|
|
370
373
|
chat_id TEXT PRIMARY KEY,
|
|
371
374
|
session_id TEXT UNIQUE,
|
|
372
375
|
title TEXT,
|
|
373
|
-
cwd TEXT
|
|
376
|
+
cwd TEXT,
|
|
377
|
+
pinned INTEGER NOT NULL DEFAULT 0
|
|
374
378
|
);
|
|
375
379
|
CREATE TABLE IF NOT EXISTS logins (
|
|
376
380
|
id TEXT PRIMARY KEY,
|
|
@@ -400,6 +404,13 @@ CREATE TABLE IF NOT EXISTS turn_endings (
|
|
|
400
404
|
items TEXT NOT NULL
|
|
401
405
|
);
|
|
402
406
|
CREATE INDEX IF NOT EXISTS turn_endings_chat ON turn_endings (chat_id);
|
|
407
|
+
CREATE TABLE IF NOT EXISTS kiro_models (
|
|
408
|
+
model_id TEXT PRIMARY KEY,
|
|
409
|
+
name TEXT,
|
|
410
|
+
levels TEXT NOT NULL,
|
|
411
|
+
recommended TEXT,
|
|
412
|
+
effort_key TEXT
|
|
413
|
+
);
|
|
403
414
|
`;
|
|
404
415
|
}
|
|
405
416
|
});
|
|
@@ -640,6 +651,22 @@ function kiroToolName(call) {
|
|
|
640
651
|
const prefix = `@${server}/`;
|
|
641
652
|
return `${server}/${title.startsWith(prefix) ? title.slice(prefix.length) : title}`;
|
|
642
653
|
}
|
|
654
|
+
function contextBreakdownOf(raw) {
|
|
655
|
+
if (!raw || typeof raw !== "object")
|
|
656
|
+
return void 0;
|
|
657
|
+
const pct = (key) => {
|
|
658
|
+
const v = raw[key]?.percent;
|
|
659
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
660
|
+
};
|
|
661
|
+
return {
|
|
662
|
+
prompts: pct("yourPrompts"),
|
|
663
|
+
responses: pct("kiroResponses"),
|
|
664
|
+
tools: pct("tools"),
|
|
665
|
+
contextFiles: pct("contextFiles"),
|
|
666
|
+
sessionFiles: pct("sessionFiles"),
|
|
667
|
+
memory: pct("memory")
|
|
668
|
+
};
|
|
669
|
+
}
|
|
643
670
|
var SUBAGENT_TOOL, ACP_METHODS, KIRO_AGENT_NOT_FOUND;
|
|
644
671
|
var init_acp = __esm({
|
|
645
672
|
"shared/dist/acp.js"() {
|
|
@@ -653,9 +680,15 @@ var init_acp = __esm({
|
|
|
653
680
|
sessionCancel: "session/cancel",
|
|
654
681
|
sessionSetMode: "session/set_mode",
|
|
655
682
|
sessionSetConfigOption: "session/set_config_option",
|
|
683
|
+
sessionFork: "session/fork",
|
|
656
684
|
sessionUpdate: "session/update",
|
|
657
685
|
requestPermission: "session/request_permission",
|
|
658
|
-
compact: "_kiro/session/compact"
|
|
686
|
+
compact: "_kiro/session/compact",
|
|
687
|
+
sessionContext: "_kiro/session/context",
|
|
688
|
+
accountUsage: "_kiro/account/getUsage",
|
|
689
|
+
steer: "_session/steer",
|
|
690
|
+
steerClear: "_session/steer/clear",
|
|
691
|
+
checkpointRevert: "_kiro/checkpoint/revertMultiple"
|
|
659
692
|
};
|
|
660
693
|
KIRO_AGENT_NOT_FOUND = "_kiro/customAgent/not_found";
|
|
661
694
|
}
|
|
@@ -952,6 +985,62 @@ var init_chats = __esm({
|
|
|
952
985
|
}
|
|
953
986
|
});
|
|
954
987
|
|
|
988
|
+
// server/src/session/kiroModels.ts
|
|
989
|
+
function rememberKiroModels(options) {
|
|
990
|
+
const choices = options?.find((o) => o.id === "model")?.options;
|
|
991
|
+
if (!choices?.length) return;
|
|
992
|
+
const rows = [];
|
|
993
|
+
for (const c of choices) {
|
|
994
|
+
if (typeof c.value !== "string" || !c.value) continue;
|
|
995
|
+
const kiro = c._meta?.kiro;
|
|
996
|
+
const levels = Array.isArray(kiro?.effortLevels) ? kiro.effortLevels.filter((l) => typeof l === "string") : [];
|
|
997
|
+
const recommended = typeof kiro?.defaultEffortLevel === "string" ? kiro.defaultEffortLevel : null;
|
|
998
|
+
const effortKey = typeof kiro?.effortSchemaPath === "string" && kiro.effortSchemaPath ? kiro.effortSchemaPath : null;
|
|
999
|
+
rows.push([c.value, typeof c.name === "string" && c.name ? c.name : null, JSON.stringify(levels), recommended, effortKey]);
|
|
1000
|
+
}
|
|
1001
|
+
const key = JSON.stringify(rows);
|
|
1002
|
+
if (key === lastWritten) return;
|
|
1003
|
+
const d = db();
|
|
1004
|
+
d.exec("BEGIN");
|
|
1005
|
+
try {
|
|
1006
|
+
d.prepare("DELETE FROM kiro_models").run();
|
|
1007
|
+
const insert = d.prepare(
|
|
1008
|
+
"INSERT INTO kiro_models (model_id, name, levels, recommended, effort_key) VALUES (?, ?, ?, ?, ?)"
|
|
1009
|
+
);
|
|
1010
|
+
for (const r of rows) insert.run(...r);
|
|
1011
|
+
d.exec("COMMIT");
|
|
1012
|
+
lastWritten = key;
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
d.exec("ROLLBACK");
|
|
1015
|
+
throw err;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
function kiroModels() {
|
|
1019
|
+
const rows = db().prepare("SELECT model_id, name, levels, recommended, effort_key FROM kiro_models").all();
|
|
1020
|
+
const out = /* @__PURE__ */ new Map();
|
|
1021
|
+
for (const r of rows) {
|
|
1022
|
+
try {
|
|
1023
|
+
const levels = JSON.parse(r.levels);
|
|
1024
|
+
out.set(r.model_id, {
|
|
1025
|
+
...r.name ? { name: r.name } : {},
|
|
1026
|
+
levels,
|
|
1027
|
+
...r.recommended ? { recommended: r.recommended } : {},
|
|
1028
|
+
...r.effort_key ? { effortKey: r.effort_key } : {}
|
|
1029
|
+
});
|
|
1030
|
+
} catch {
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return out;
|
|
1034
|
+
}
|
|
1035
|
+
var lastWritten;
|
|
1036
|
+
var init_kiroModels = __esm({
|
|
1037
|
+
"server/src/session/kiroModels.ts"() {
|
|
1038
|
+
"use strict";
|
|
1039
|
+
init_db();
|
|
1040
|
+
lastWritten = "";
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
955
1044
|
// server/src/acp/errors.ts
|
|
956
1045
|
function describeError(error) {
|
|
957
1046
|
const detail = detailText(error.data);
|
|
@@ -1116,6 +1205,8 @@ var init_KiroProcess = __esm({
|
|
|
1116
1205
|
recentStderr = [];
|
|
1117
1206
|
// kiro's optional methods, as advertised by initialize.
|
|
1118
1207
|
extensionMethods = /* @__PURE__ */ new Set();
|
|
1208
|
+
// Whether kiro can rewind a session to before one of its prompts.
|
|
1209
|
+
checkpoints = false;
|
|
1119
1210
|
constructor(opts, log) {
|
|
1120
1211
|
super();
|
|
1121
1212
|
this.log = log;
|
|
@@ -1182,6 +1273,7 @@ var init_KiroProcess = __esm({
|
|
|
1182
1273
|
this.extensionMethods = new Set(
|
|
1183
1274
|
Array.isArray(methods) ? methods.filter((m) => typeof m === "string") : []
|
|
1184
1275
|
);
|
|
1276
|
+
this.checkpoints = res?.agentCapabilities?._meta?.kiro?.checkpoints === true;
|
|
1185
1277
|
}
|
|
1186
1278
|
newSession(params) {
|
|
1187
1279
|
return this.client.request(ACP_METHODS.sessionNew, params);
|
|
@@ -1189,6 +1281,9 @@ var init_KiroProcess = __esm({
|
|
|
1189
1281
|
loadSession(params) {
|
|
1190
1282
|
return this.client.request(ACP_METHODS.sessionLoad, params);
|
|
1191
1283
|
}
|
|
1284
|
+
forkSession(params) {
|
|
1285
|
+
return this.client.request(ACP_METHODS.sessionFork, params);
|
|
1286
|
+
}
|
|
1192
1287
|
/** Runs a prompt turn to completion. */
|
|
1193
1288
|
prompt(params) {
|
|
1194
1289
|
return this.client.request(
|
|
@@ -1210,6 +1305,50 @@ var init_KiroProcess = __esm({
|
|
|
1210
1305
|
value: modelId
|
|
1211
1306
|
});
|
|
1212
1307
|
}
|
|
1308
|
+
setEffort(sessionId, effortLevel) {
|
|
1309
|
+
return this.client.request(ACP_METHODS.sessionSetConfigOption, {
|
|
1310
|
+
sessionId,
|
|
1311
|
+
configId: "effortLevel",
|
|
1312
|
+
value: effortLevel
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
/** Sends a message into the running turn. kiro passes it to the model at its next step,
|
|
1316
|
+
* reporting that as `steering_injected`. Resolves with kiro's id for it. */
|
|
1317
|
+
async steer(sessionId, message, messageId) {
|
|
1318
|
+
const res = await this.client.request(ACP_METHODS.steer, {
|
|
1319
|
+
sessionId,
|
|
1320
|
+
message,
|
|
1321
|
+
messageId
|
|
1322
|
+
});
|
|
1323
|
+
if (res?.queued !== true) throw new Error("kiro did not take the message");
|
|
1324
|
+
return typeof res.messageId === "string" ? res.messageId : void 0;
|
|
1325
|
+
}
|
|
1326
|
+
/** Drops the messages sent into a turn that the model has not read yet. */
|
|
1327
|
+
clearSteers(sessionId) {
|
|
1328
|
+
return this.client.request(ACP_METHODS.steerClear, { sessionId });
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Rewinds the session to before the prompt `messageId`, removing it and everything after.
|
|
1332
|
+
* kiro also restores the files changed since, so this is only for a turn that changed
|
|
1333
|
+
* nothing. False when kiro cannot or did not.
|
|
1334
|
+
*/
|
|
1335
|
+
async revertTo(sessionId, messageId) {
|
|
1336
|
+
if (!this.checkpoints) return false;
|
|
1337
|
+
const res = await this.client.request(ACP_METHODS.checkpointRevert, { sessionId, messageId });
|
|
1338
|
+
return res?.success === true;
|
|
1339
|
+
}
|
|
1340
|
+
/** What the session's context holds, as kiro's `/context show` reports it. */
|
|
1341
|
+
async contextBreakdown(sessionId) {
|
|
1342
|
+
const res = await this.client.request(
|
|
1343
|
+
ACP_METHODS.sessionContext,
|
|
1344
|
+
{ sessionId, subcommand: "show" }
|
|
1345
|
+
);
|
|
1346
|
+
return res?.breakdown;
|
|
1347
|
+
}
|
|
1348
|
+
/** The user's plan and its use this billing cycle. Not session-scoped. */
|
|
1349
|
+
accountUsage() {
|
|
1350
|
+
return this.client.request(ACP_METHODS.accountUsage, {});
|
|
1351
|
+
}
|
|
1213
1352
|
/** Summarises the conversation so far. Resolves once kiro has finished. */
|
|
1214
1353
|
compact(sessionId) {
|
|
1215
1354
|
if (!this.extensionMethods.has(ACP_METHODS.compact)) {
|
|
@@ -1344,7 +1483,9 @@ var init_TurnState = __esm({
|
|
|
1344
1483
|
case "metadata":
|
|
1345
1484
|
this.snapshot = {
|
|
1346
1485
|
...s,
|
|
1347
|
-
contextUsagePercentage: payload.params.contextUsagePercentage ?? s.contextUsagePercentage
|
|
1486
|
+
contextUsagePercentage: payload.params.contextUsagePercentage ?? s.contextUsagePercentage,
|
|
1487
|
+
contextBreakdown: payload.params.contextBreakdown ?? s.contextBreakdown,
|
|
1488
|
+
sessionCredits: payload.params.sessionCredits ?? s.sessionCredits
|
|
1348
1489
|
};
|
|
1349
1490
|
break;
|
|
1350
1491
|
case "compaction":
|
|
@@ -1375,6 +1516,9 @@ function cap(value) {
|
|
|
1375
1516
|
}
|
|
1376
1517
|
return value;
|
|
1377
1518
|
}
|
|
1519
|
+
function steerMessage(text2, seq, ts) {
|
|
1520
|
+
return { type: "message", message: { id: `s-${seq}`, role: "user", text: text2, timestamp: ts, steer: true } };
|
|
1521
|
+
}
|
|
1378
1522
|
var MAX_STRING, isImage, bounded, TurnRecorder;
|
|
1379
1523
|
var init_TurnRecorder = __esm({
|
|
1380
1524
|
"server/src/session/TurnRecorder.ts"() {
|
|
@@ -1410,6 +1554,8 @@ var init_TurnRecorder = __esm({
|
|
|
1410
1554
|
);
|
|
1411
1555
|
} else if (p.kind === "session_update" && this.draft) {
|
|
1412
1556
|
this.draft = foldSessionUpdate(this.draft, p.update, e.seq, e.ts);
|
|
1557
|
+
} else if (p.kind === "steer_injected" && this.draft) {
|
|
1558
|
+
this.draft = { ...emptyDraft(), items: [...commitDraft(this.draft, `s-${e.seq}`, e.ts), steerMessage(p.text, e.seq, e.ts)] };
|
|
1413
1559
|
}
|
|
1414
1560
|
}
|
|
1415
1561
|
/** Null when no turn was being followed. */
|
|
@@ -1550,40 +1696,45 @@ async function v3SessionDir(sessionId) {
|
|
|
1550
1696
|
);
|
|
1551
1697
|
return found.find((d) => d !== void 0);
|
|
1552
1698
|
}
|
|
1553
|
-
function
|
|
1554
|
-
|
|
1699
|
+
function readTailLine(line, facts) {
|
|
1700
|
+
const usage = facts.contextUsage === void 0 && line.includes('"contextUsage"');
|
|
1701
|
+
const prompt = facts.lastPromptAt === void 0 && line.includes('"user"');
|
|
1702
|
+
if (!usage && !prompt) return;
|
|
1555
1703
|
try {
|
|
1556
|
-
const
|
|
1704
|
+
const l = JSON.parse(line);
|
|
1705
|
+
const p = l.payload;
|
|
1557
1706
|
const value = isRecord(p.value) ? p.value.usagePercentage : void 0;
|
|
1558
|
-
|
|
1707
|
+
if (usage && p.type === "session_metadata" && p.key === "contextUsage" && typeof value === "number") {
|
|
1708
|
+
facts.contextUsage = value;
|
|
1709
|
+
}
|
|
1710
|
+
if (prompt && p.type === "user" && typeof l.timestamp === "string") facts.lastPromptAt = iso(l.timestamp);
|
|
1559
1711
|
} catch {
|
|
1560
|
-
return void 0;
|
|
1561
1712
|
}
|
|
1562
1713
|
}
|
|
1563
|
-
async function
|
|
1714
|
+
async function readTail(file) {
|
|
1715
|
+
const facts = {};
|
|
1716
|
+
const done = () => facts.contextUsage !== void 0 && facts.lastPromptAt !== void 0;
|
|
1564
1717
|
let handle2;
|
|
1565
1718
|
try {
|
|
1566
1719
|
handle2 = await fs9.open(file, "r");
|
|
1567
1720
|
} catch {
|
|
1568
|
-
return
|
|
1721
|
+
return facts;
|
|
1569
1722
|
}
|
|
1570
1723
|
try {
|
|
1571
1724
|
const { size } = await handle2.stat();
|
|
1572
1725
|
let end = size;
|
|
1573
1726
|
let tail = "";
|
|
1574
|
-
while (end > 0 && size - end < CONTEXT_TAIL_BYTES) {
|
|
1727
|
+
while (end > 0 && size - end < CONTEXT_TAIL_BYTES && !done()) {
|
|
1575
1728
|
const start = Math.max(0, end - CONTEXT_CHUNK_BYTES);
|
|
1576
1729
|
const buf = Buffer.alloc(end - start);
|
|
1577
1730
|
await handle2.read(buf, 0, buf.length, start);
|
|
1578
1731
|
end = start;
|
|
1579
1732
|
const lines = (buf.toString("utf8") + tail).split("\n");
|
|
1580
1733
|
tail = end > 0 ? lines.shift() : "";
|
|
1581
|
-
for (let i = lines.length - 1; i >= 0; i--)
|
|
1582
|
-
const value = contextUsageOf(lines[i]);
|
|
1583
|
-
if (value !== void 0) return value;
|
|
1584
|
-
}
|
|
1734
|
+
for (let i = lines.length - 1; i >= 0 && !done(); i--) readTailLine(lines[i], facts);
|
|
1585
1735
|
}
|
|
1586
|
-
|
|
1736
|
+
if (tail && !done()) readTailLine(tail, facts);
|
|
1737
|
+
return facts;
|
|
1587
1738
|
} finally {
|
|
1588
1739
|
await handle2.close();
|
|
1589
1740
|
}
|
|
@@ -1597,15 +1748,19 @@ async function readV3Session(dir) {
|
|
|
1597
1748
|
}
|
|
1598
1749
|
const title = j.title?.trim() ?? "";
|
|
1599
1750
|
const createdAt = iso(j.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
1751
|
+
const tail = await readTail(path10.join(dir, "messages.jsonl"));
|
|
1600
1752
|
return {
|
|
1601
1753
|
sessionId: j.id,
|
|
1602
1754
|
title: title === PLACEHOLDER_TITLE ? "" : title,
|
|
1603
1755
|
cwd: j.workspacePaths?.[0] ?? "",
|
|
1604
1756
|
createdAt,
|
|
1605
|
-
|
|
1757
|
+
// The latest prompt, not lastModifiedAt: kiro's 2.x-to-V3 migration set lastModifiedAt,
|
|
1758
|
+
// and the time on every reply, to the moment it ran. Prompts kept their real times.
|
|
1759
|
+
updatedAt: tail.lastPromptAt ?? iso(j.lastModifiedAt) ?? createdAt,
|
|
1606
1760
|
agentId: j.agentMode,
|
|
1607
1761
|
modelId: j.modelId,
|
|
1608
|
-
|
|
1762
|
+
effortLevel: j.effortLevel,
|
|
1763
|
+
contextUsagePercentage: tail.contextUsage
|
|
1609
1764
|
};
|
|
1610
1765
|
}
|
|
1611
1766
|
async function readLines(file, filter) {
|
|
@@ -1626,6 +1781,20 @@ async function readLines(file, filter) {
|
|
|
1626
1781
|
}
|
|
1627
1782
|
return out;
|
|
1628
1783
|
}
|
|
1784
|
+
async function readV3Credits(dir) {
|
|
1785
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1786
|
+
let total = 0;
|
|
1787
|
+
for (const line of await readLines(path10.join(dir, "messages.jsonl"), '"usage_summary"')) {
|
|
1788
|
+
const p = line.payload;
|
|
1789
|
+
if (p.type !== "usage_summary" || seen.has(line.id)) continue;
|
|
1790
|
+
seen.add(line.id);
|
|
1791
|
+
for (const s of Array.isArray(p.promptTurnSummaries) ? p.promptTurnSummaries : []) {
|
|
1792
|
+
const usage = isRecord(s) ? s.usage : void 0;
|
|
1793
|
+
if (typeof usage === "number" && Number.isFinite(usage)) total += usage;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return total;
|
|
1797
|
+
}
|
|
1629
1798
|
function userText(content) {
|
|
1630
1799
|
if (typeof content === "string") return content;
|
|
1631
1800
|
if (!Array.isArray(content)) return "";
|
|
@@ -1655,6 +1824,7 @@ async function readV3Entries(file) {
|
|
|
1655
1824
|
out.push({ kind: "item", item: { type: "tool_call", tool } });
|
|
1656
1825
|
};
|
|
1657
1826
|
const seen = /* @__PURE__ */ new Set();
|
|
1827
|
+
const steers = /* @__PURE__ */ new Map();
|
|
1658
1828
|
for (const line of await readLines(file)) {
|
|
1659
1829
|
const copy = isCompactionCopy(line.id, seen);
|
|
1660
1830
|
seen.add(line.id);
|
|
@@ -1663,9 +1833,45 @@ async function readV3Entries(file) {
|
|
|
1663
1833
|
const parsed = line.timestamp ? Date.parse(line.timestamp) : NaN;
|
|
1664
1834
|
const at = Number.isNaN(parsed) ? void 0 : parsed;
|
|
1665
1835
|
switch (p.type) {
|
|
1666
|
-
case "user":
|
|
1836
|
+
case "user": {
|
|
1837
|
+
if (p.source === "steer") {
|
|
1838
|
+
const text2 = userText(p.content);
|
|
1839
|
+
if (text2.trim()) steers.set(line.id, text2);
|
|
1840
|
+
break;
|
|
1841
|
+
}
|
|
1842
|
+
const kiro = isRecord(p._meta) && isRecord(p._meta.kiro) ? p._meta.kiro : void 0;
|
|
1843
|
+
if (kiro?.syntheticUserMessageReason !== void 0) {
|
|
1844
|
+
const ids = Array.isArray(kiro.steeringConsumedIds) ? kiro.steeringConsumedIds : [];
|
|
1845
|
+
for (const id of ids) {
|
|
1846
|
+
const text2 = typeof id === "string" ? steers.get(id) : void 0;
|
|
1847
|
+
if (text2 === void 0) continue;
|
|
1848
|
+
out.push({ kind: "item", item: { type: "message", message: { id: `s-${id}`, role: "user", text: text2, timestamp: at, steer: true } } });
|
|
1849
|
+
}
|
|
1850
|
+
break;
|
|
1851
|
+
}
|
|
1667
1852
|
out.push({ kind: "prompt", id: line.id, text: userText(p.content), atMs: at, timestamp: at });
|
|
1668
1853
|
break;
|
|
1854
|
+
}
|
|
1855
|
+
case "tombstone": {
|
|
1856
|
+
const from = p.kind === "checkpoint_revert" ? str2(p.effectiveFromMessageId) : void 0;
|
|
1857
|
+
const at2 = from ? out.findIndex((e) => e.kind === "prompt" && e.id === from) : -1;
|
|
1858
|
+
if (at2 >= 0) out.length = at2;
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
case "usage_summary": {
|
|
1862
|
+
const credits = (Array.isArray(p.promptTurnSummaries) ? p.promptTurnSummaries : []).reduce(
|
|
1863
|
+
(sum, s) => sum + (isRecord(s) && typeof s.usage === "number" && Number.isFinite(s.usage) ? s.usage : 0),
|
|
1864
|
+
0
|
|
1865
|
+
);
|
|
1866
|
+
const elapsedMs = typeof p.elapsedTime === "number" && p.elapsedTime > 0 ? p.elapsedTime : void 0;
|
|
1867
|
+
if (credits > 0 || elapsedMs) {
|
|
1868
|
+
out.push({
|
|
1869
|
+
kind: "item",
|
|
1870
|
+
item: { type: "turn_stats", id: `u-${line.id}`, ...elapsedMs ? { elapsedMs } : {}, ...credits > 0 ? { credits } : {} }
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
break;
|
|
1874
|
+
}
|
|
1669
1875
|
case "assistant": {
|
|
1670
1876
|
const text2 = str2(p.content) ?? "";
|
|
1671
1877
|
if (!text2.trim()) break;
|
|
@@ -1813,8 +2019,15 @@ async function readEntries(sessionId) {
|
|
|
1813
2019
|
const dir = await v3SessionDir(sessionId);
|
|
1814
2020
|
return dir ? readV3Entries(path11.join(dir, "messages.jsonl")) : [];
|
|
1815
2021
|
}
|
|
2022
|
+
async function sessionCredits(sessionId) {
|
|
2023
|
+
const dir = await v3SessionDir(sessionId);
|
|
2024
|
+
return dir ? readV3Credits(dir) : void 0;
|
|
2025
|
+
}
|
|
1816
2026
|
async function promptCount(sessionId) {
|
|
1817
|
-
return (await
|
|
2027
|
+
return (await promptStamps(sessionId)).length;
|
|
2028
|
+
}
|
|
2029
|
+
async function promptStamps(sessionId) {
|
|
2030
|
+
return (await readEntries(sessionId)).flatMap((e) => e.kind === "prompt" ? [{ id: e.id, atMs: e.atMs }] : []);
|
|
1818
2031
|
}
|
|
1819
2032
|
function endingItems(e) {
|
|
1820
2033
|
const prefix = `te${e.id}-`;
|
|
@@ -1856,7 +2069,7 @@ function mergeTranscript(entries, attachments, endings) {
|
|
|
1856
2069
|
const attached = attachments?.get(userOrdinal);
|
|
1857
2070
|
userOrdinal++;
|
|
1858
2071
|
if (text2.trim() || attached?.length) {
|
|
1859
|
-
items.push({ type: "message", message: { id: `u-${entry.id}`, role: "user", text: text2, timestamp: entry.timestamp, attachments: attached } });
|
|
2072
|
+
items.push({ type: "message", message: { id: `u-${entry.id}`, role: "user", text: text2, timestamp: entry.timestamp, attachments: attached, messageId: entry.id } });
|
|
1860
2073
|
}
|
|
1861
2074
|
if (matched) {
|
|
1862
2075
|
items.push(...endingItems(matched));
|
|
@@ -1896,18 +2109,26 @@ var init_kiroFiles = __esm({
|
|
|
1896
2109
|
});
|
|
1897
2110
|
|
|
1898
2111
|
// server/src/session/chatStore.ts
|
|
1899
|
-
var ChatStore;
|
|
2112
|
+
var COLUMNS, rowOf, ChatStore;
|
|
1900
2113
|
var init_chatStore = __esm({
|
|
1901
2114
|
"server/src/session/chatStore.ts"() {
|
|
1902
2115
|
"use strict";
|
|
1903
2116
|
init_db();
|
|
2117
|
+
COLUMNS = "chat_id, session_id, title, cwd, pinned";
|
|
2118
|
+
rowOf = (r) => ({
|
|
2119
|
+
chatId: r.chat_id,
|
|
2120
|
+
sessionId: r.session_id,
|
|
2121
|
+
title: r.title,
|
|
2122
|
+
cwd: r.cwd,
|
|
2123
|
+
pinned: r.pinned === 1
|
|
2124
|
+
});
|
|
1904
2125
|
ChatStore = class {
|
|
1905
2126
|
all() {
|
|
1906
|
-
return db().prepare(
|
|
2127
|
+
return db().prepare(`SELECT ${COLUMNS} FROM chats`).all().map(rowOf);
|
|
1907
2128
|
}
|
|
1908
2129
|
get(chatId) {
|
|
1909
|
-
const r = db().prepare(
|
|
1910
|
-
return r ?
|
|
2130
|
+
const r = db().prepare(`SELECT ${COLUMNS} FROM chats WHERE chat_id = ?`).get(chatId);
|
|
2131
|
+
return r ? rowOf(r) : void 0;
|
|
1911
2132
|
}
|
|
1912
2133
|
create(chatId) {
|
|
1913
2134
|
db().prepare("INSERT INTO chats (chat_id) VALUES (?) ON CONFLICT(chat_id) DO NOTHING").run(chatId);
|
|
@@ -1934,6 +2155,11 @@ var init_chatStore = __esm({
|
|
|
1934
2155
|
setCwd(chatId, cwd) {
|
|
1935
2156
|
this.write(chatId, "cwd", cwd);
|
|
1936
2157
|
}
|
|
2158
|
+
/** Returns false when there is no such chat. */
|
|
2159
|
+
setPinned(chatId, pinned) {
|
|
2160
|
+
const r = db().prepare("UPDATE chats SET pinned = ? WHERE chat_id = ?").run(pinned ? 1 : 0, chatId);
|
|
2161
|
+
return Number(r.changes) > 0;
|
|
2162
|
+
}
|
|
1937
2163
|
/** What was attached to one prompt, keyed by ordinal (see db.ts). */
|
|
1938
2164
|
setAttachments(chatId, ordinal, files) {
|
|
1939
2165
|
if (files.length === 0) return;
|
|
@@ -1993,6 +2219,27 @@ var init_chatStore = __esm({
|
|
|
1993
2219
|
}
|
|
1994
2220
|
return out;
|
|
1995
2221
|
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Copies what a branch keeps of another chat: the attachments of the first `prompts`
|
|
2224
|
+
* prompts, and the turn endings that started before `before` (all of them if unset).
|
|
2225
|
+
* `movePath` gives an attachment's path in the branch.
|
|
2226
|
+
*/
|
|
2227
|
+
copyHistory(from, to, keep, movePath) {
|
|
2228
|
+
for (const [ordinal, files] of this.attachmentsByChat(from)) {
|
|
2229
|
+
if (ordinal < keep.prompts) {
|
|
2230
|
+
this.setAttachments(to, ordinal, files.map((f) => ({ ...f, path: movePath(f.path) })));
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
for (const e of this.turnEndingsByChat(from)) {
|
|
2234
|
+
if (keep.before === void 0 || e.startedAt < keep.before) this.addTurnEnding(to, e);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
/** Drops what a rewind removes: the attachments from prompt `prompts` on, and the turn
|
|
2238
|
+
* endings that started at or after `from`. */
|
|
2239
|
+
dropHistory(chatId, drop) {
|
|
2240
|
+
db().prepare("DELETE FROM message_attachments WHERE chat_id = ? AND ordinal >= ?").run(chatId, drop.prompts);
|
|
2241
|
+
db().prepare("DELETE FROM turn_endings WHERE chat_id = ? AND started_at >= ?").run(chatId, drop.from);
|
|
2242
|
+
}
|
|
1996
2243
|
remove(chatId) {
|
|
1997
2244
|
db().prepare("DELETE FROM chats WHERE chat_id = ?").run(chatId);
|
|
1998
2245
|
db().prepare("DELETE FROM message_attachments WHERE chat_id = ?").run(chatId);
|
|
@@ -2041,8 +2288,24 @@ function kiroMeta(update) {
|
|
|
2041
2288
|
function sessionInfoEvent(sessionId, update) {
|
|
2042
2289
|
const info = update._meta?.kiro;
|
|
2043
2290
|
switch (info?.kind) {
|
|
2044
|
-
case "context_usage":
|
|
2045
|
-
|
|
2291
|
+
case "context_usage": {
|
|
2292
|
+
if (typeof info.usagePercentage !== "number") return null;
|
|
2293
|
+
const contextBreakdown = contextBreakdownOf(info.breakdown);
|
|
2294
|
+
return {
|
|
2295
|
+
kind: "metadata",
|
|
2296
|
+
params: { sessionId, contextUsagePercentage: info.usagePercentage, ...contextBreakdown ? { contextBreakdown } : {} }
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
case "focus_update": {
|
|
2300
|
+
const title = sanitizeTitle(info.title ?? "");
|
|
2301
|
+
return title ? { kind: "title_changed", title } : null;
|
|
2302
|
+
}
|
|
2303
|
+
case "session_usage": {
|
|
2304
|
+
const total = (info.credits ?? []).reduce((sum, c) => sum + (typeof c?.usage === "number" ? c.usage : 0), 0);
|
|
2305
|
+
return info.credits?.length ? { kind: "metadata", params: { sessionId, sessionCredits: total } } : null;
|
|
2306
|
+
}
|
|
2307
|
+
case "user_message_id_assigned":
|
|
2308
|
+
return typeof info.userMessageId === "string" && info.userMessageId ? { kind: "prompt_id", messageId: info.userMessageId } : null;
|
|
2046
2309
|
case "summarization_started":
|
|
2047
2310
|
return { kind: "compaction", params: { sessionId, status: { type: "started" }, summary: null } };
|
|
2048
2311
|
case "summarization_completed":
|
|
@@ -2056,8 +2319,38 @@ function sessionInfoEvent(sessionId, update) {
|
|
|
2056
2319
|
return null;
|
|
2057
2320
|
}
|
|
2058
2321
|
}
|
|
2059
|
-
function
|
|
2060
|
-
|
|
2322
|
+
function sessionSettings(options) {
|
|
2323
|
+
if (!options) return void 0;
|
|
2324
|
+
return {
|
|
2325
|
+
modelId: options.find((o) => o.id === "model")?.currentValue,
|
|
2326
|
+
effortLevel: options.find((o) => o.id === "effortLevel")?.currentValue ?? null
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
function turnStatsOf(info) {
|
|
2330
|
+
const credits = (info.promptTurnSummaries ?? []).reduce(
|
|
2331
|
+
(sum, t) => sum + (typeof t?.usage === "number" && Number.isFinite(t.usage) ? t.usage : 0),
|
|
2332
|
+
0
|
|
2333
|
+
);
|
|
2334
|
+
const elapsedMs = typeof info.elapsedTime === "number" && info.elapsedTime > 0 ? info.elapsedTime : void 0;
|
|
2335
|
+
if (!credits && !elapsedMs) return void 0;
|
|
2336
|
+
return { ...elapsedMs ? { elapsedMs } : {}, ...credits > 0 ? { credits } : {} };
|
|
2337
|
+
}
|
|
2338
|
+
function accountUsageOf(raw) {
|
|
2339
|
+
const data = raw?.data;
|
|
2340
|
+
if (!data || typeof data.planName !== "string") return void 0;
|
|
2341
|
+
const credits = (Array.isArray(data.usageBreakdowns) ? data.usageBreakdowns : []).find(
|
|
2342
|
+
(b) => b?.resourceType === "CREDIT"
|
|
2343
|
+
);
|
|
2344
|
+
if (!credits || typeof credits.used !== "number") return void 0;
|
|
2345
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
2346
|
+
return {
|
|
2347
|
+
planName: data.planName,
|
|
2348
|
+
used: credits.used,
|
|
2349
|
+
...credits.hasLimit !== false && num(credits.limit) !== void 0 ? { limit: num(credits.limit) } : {},
|
|
2350
|
+
...typeof data.billingCycleReset === "string" ? { resetsOn: data.billingCycleReset } : {},
|
|
2351
|
+
...num(credits.overageCharges) ? { overageCharges: num(credits.overageCharges) } : {},
|
|
2352
|
+
...typeof credits.currency === "string" ? { currency: credits.currency } : {}
|
|
2353
|
+
};
|
|
2061
2354
|
}
|
|
2062
2355
|
function subExecutionSummary(sub) {
|
|
2063
2356
|
return {
|
|
@@ -2074,7 +2367,7 @@ function newerOf(a, b) {
|
|
|
2074
2367
|
return a && a.localeCompare(b) > 0 ? a : b;
|
|
2075
2368
|
}
|
|
2076
2369
|
function firstPromptText(transcript) {
|
|
2077
|
-
const first = transcript.find((it) => it.type === "message" && it.message.role === "user");
|
|
2370
|
+
const first = transcript.find((it) => it.type === "message" && it.message.role === "user" && !it.message.steer);
|
|
2078
2371
|
return first?.type === "message" ? first.message.text : void 0;
|
|
2079
2372
|
}
|
|
2080
2373
|
function isGhost(s, hasFile) {
|
|
@@ -2088,6 +2381,7 @@ var init_SessionManager = __esm({
|
|
|
2088
2381
|
init_config();
|
|
2089
2382
|
init_agents();
|
|
2090
2383
|
init_chats();
|
|
2384
|
+
init_kiroModels();
|
|
2091
2385
|
init_paths2();
|
|
2092
2386
|
init_KiroProcess();
|
|
2093
2387
|
init_EventStore();
|
|
@@ -2106,8 +2400,21 @@ var init_SessionManager = __esm({
|
|
|
2106
2400
|
cwd;
|
|
2107
2401
|
agentId;
|
|
2108
2402
|
modelId;
|
|
2403
|
+
effortLevel;
|
|
2109
2404
|
currentModeId;
|
|
2110
2405
|
availableModes = [];
|
|
2406
|
+
// Set on a session that starts as a copy of another's conversation, cut after the
|
|
2407
|
+
// turn of the prompt `messageId`.
|
|
2408
|
+
forkOf;
|
|
2409
|
+
// Credits used, as kiro last reported them.
|
|
2410
|
+
credits;
|
|
2411
|
+
// The latest turn: its prompt's id once kiro names it, whether anything came of it, and
|
|
2412
|
+
// whether it failed. A retry rewinds a failed turn that left nothing.
|
|
2413
|
+
lastTurn;
|
|
2414
|
+
// Messages sent into the running turn that the model has not read yet, by kiro's id.
|
|
2415
|
+
steers = /* @__PURE__ */ new Map();
|
|
2416
|
+
// What the running turn took, from kiro's turn_completion.
|
|
2417
|
+
turnStats;
|
|
2111
2418
|
title = "";
|
|
2112
2419
|
createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2113
2420
|
updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2153,11 +2460,15 @@ var init_SessionManager = __esm({
|
|
|
2153
2460
|
spawnProcess;
|
|
2154
2461
|
cancelGraceMs;
|
|
2155
2462
|
defaultModel;
|
|
2463
|
+
defaultEffort;
|
|
2464
|
+
defaultAgent;
|
|
2156
2465
|
constructor(log, opts = {}) {
|
|
2157
2466
|
this.log = log;
|
|
2158
2467
|
this.spawnProcess = opts.spawn ?? ((o, l) => new KiroProcess(o, l));
|
|
2159
2468
|
this.cancelGraceMs = opts.cancelGraceMs ?? CANCEL_GRACE_MS;
|
|
2160
2469
|
this.defaultModel = opts.defaultModel ?? (async () => void 0);
|
|
2470
|
+
this.defaultEffort = opts.defaultEffort ?? (async () => void 0);
|
|
2471
|
+
this.defaultAgent = opts.defaultAgent ?? (async () => void 0);
|
|
2161
2472
|
}
|
|
2162
2473
|
/** What a session is called, from every read path. */
|
|
2163
2474
|
titleOf(chat, parts) {
|
|
@@ -2174,6 +2485,9 @@ var init_SessionManager = __esm({
|
|
|
2174
2485
|
const s = this.sessions.get(this.store.sessionIdForChat(chatId) ?? "");
|
|
2175
2486
|
if (s) s.title = clean || s.title;
|
|
2176
2487
|
}
|
|
2488
|
+
pinChat(chatId, pinned) {
|
|
2489
|
+
if (!this.store.setPinned(chatId, pinned)) throw new Error(`Unknown chat: ${chatId}`);
|
|
2490
|
+
}
|
|
2177
2491
|
// A live process was spawned with the old cwd, so it's disposed and the next
|
|
2178
2492
|
// turn respawns.
|
|
2179
2493
|
async setChatCwd(chatId, input) {
|
|
@@ -2284,6 +2598,7 @@ var init_SessionManager = __esm({
|
|
|
2284
2598
|
s.agentId = persisted.agentId;
|
|
2285
2599
|
s.currentModeId = persisted.agentId;
|
|
2286
2600
|
s.modelId = persisted.modelId;
|
|
2601
|
+
s.effortLevel = persisted.effortLevel;
|
|
2287
2602
|
s.createdAt = persisted.createdAt;
|
|
2288
2603
|
s.updatedAt = persisted.updatedAt;
|
|
2289
2604
|
s.markLive();
|
|
@@ -2293,7 +2608,15 @@ var init_SessionManager = __esm({
|
|
|
2293
2608
|
}
|
|
2294
2609
|
wire(s, proc) {
|
|
2295
2610
|
proc.on("notification", (n) => {
|
|
2296
|
-
|
|
2611
|
+
const replayed = n.params?.update;
|
|
2612
|
+
if (s.replaying || kiroMeta(replayed ?? { sessionUpdate: "" })?.replay) {
|
|
2613
|
+
if (replayed?.sessionUpdate === "session_info_update" && replayed._meta?.kiro?.kind === "session_usage") {
|
|
2614
|
+
const payload = sessionInfoEvent(s.sessionId, replayed);
|
|
2615
|
+
if (payload?.kind === "metadata") s.credits = payload.params.sessionCredits;
|
|
2616
|
+
if (payload) s.record(payload);
|
|
2617
|
+
}
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2297
2620
|
if (n.method === KIRO_AGENT_NOT_FOUND) {
|
|
2298
2621
|
this.log.warn({ sessionId: s.sessionId, detail: n.params }, "kiro could not use the requested agent");
|
|
2299
2622
|
return;
|
|
@@ -2305,10 +2628,40 @@ var init_SessionManager = __esm({
|
|
|
2305
2628
|
const meta = kiroMeta(update);
|
|
2306
2629
|
if (meta?.replay) return;
|
|
2307
2630
|
if (update.sessionUpdate === "session_info_update") {
|
|
2631
|
+
const info = update._meta?.kiro;
|
|
2632
|
+
if (info?.kind === "steering_injected" || info?.kind === "steering_cleared") {
|
|
2633
|
+
this.steerSettled(s, info.kind === "steering_injected", info.kind === "steering_injected" ? [info.messageId] : info.messageIds ?? []);
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
if (info?.kind === "turn_completion") {
|
|
2637
|
+
s.turnStats = turnStatsOf(info);
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2308
2640
|
const payload = sessionInfoEvent(s.sessionId, update);
|
|
2641
|
+
if (payload?.kind === "prompt_id" && s.lastTurn) s.lastTurn.messageId = payload.messageId;
|
|
2642
|
+
if (payload?.kind === "metadata" && payload.params.sessionCredits !== void 0) {
|
|
2643
|
+
s.credits = payload.params.sessionCredits;
|
|
2644
|
+
}
|
|
2645
|
+
if (payload?.kind === "title_changed") {
|
|
2646
|
+
if (payload.title === s.title) return;
|
|
2647
|
+
s.title = payload.title;
|
|
2648
|
+
}
|
|
2309
2649
|
if (payload) s.record(payload);
|
|
2310
2650
|
return;
|
|
2311
2651
|
}
|
|
2652
|
+
if (update.sessionUpdate === "config_option_update") {
|
|
2653
|
+
const options = update.configOptions;
|
|
2654
|
+
rememberKiroModels(options);
|
|
2655
|
+
const settings = sessionSettings(options);
|
|
2656
|
+
if (!settings) return;
|
|
2657
|
+
const modelId = settings.modelId ?? s.modelId;
|
|
2658
|
+
const effortLevel = settings.effortLevel ?? void 0;
|
|
2659
|
+
if (modelId === s.modelId && effortLevel === s.effortLevel) return;
|
|
2660
|
+
s.modelId = modelId;
|
|
2661
|
+
s.effortLevel = effortLevel;
|
|
2662
|
+
s.record({ kind: "settings_changed", modelId, effortLevel: settings.effortLevel });
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2312
2665
|
if (!TRANSCRIPT_UPDATES.has(update.sessionUpdate)) return;
|
|
2313
2666
|
const subagentId = meta?.agentSubtaskId;
|
|
2314
2667
|
if (subagentId && meta.kind !== "agent-subtask") {
|
|
@@ -2317,6 +2670,7 @@ var init_SessionManager = __esm({
|
|
|
2317
2670
|
return;
|
|
2318
2671
|
}
|
|
2319
2672
|
if (subagentId) this.trackSubagentCall(s, subagentId, update);
|
|
2673
|
+
if (s.lastTurn) s.lastTurn.output = true;
|
|
2320
2674
|
s.record({ kind: "session_update", update });
|
|
2321
2675
|
});
|
|
2322
2676
|
proc.on("exit", (code, signal) => {
|
|
@@ -2364,11 +2718,25 @@ var init_SessionManager = __esm({
|
|
|
2364
2718
|
} finally {
|
|
2365
2719
|
s.replaying = false;
|
|
2366
2720
|
}
|
|
2721
|
+
} else if (s.forkOf) {
|
|
2722
|
+
const { sessionId } = await proc.forkSession({
|
|
2723
|
+
sessionId: s.forkOf.sessionId,
|
|
2724
|
+
cwd: s.cwd,
|
|
2725
|
+
mcpServers: [],
|
|
2726
|
+
_meta: { kiro: { messageId: s.forkOf.messageId } }
|
|
2727
|
+
});
|
|
2728
|
+
s.replaying = true;
|
|
2729
|
+
try {
|
|
2730
|
+
res = { ...await proc.loadSession({ sessionId, cwd: s.cwd, mcpServers: [] }), sessionId };
|
|
2731
|
+
} finally {
|
|
2732
|
+
s.replaying = false;
|
|
2733
|
+
}
|
|
2734
|
+
res = await this.applyForkSettings(proc, s, res);
|
|
2367
2735
|
} else {
|
|
2368
2736
|
res = await proc.newSession({
|
|
2369
2737
|
cwd: s.cwd,
|
|
2370
2738
|
mcpServers: [],
|
|
2371
|
-
_meta: { kiro: { modeId: s.agentId, modelId: s.modelId } }
|
|
2739
|
+
_meta: { kiro: { modeId: s.agentId, modelId: s.modelId, effortLevel: s.effortLevel } }
|
|
2372
2740
|
});
|
|
2373
2741
|
}
|
|
2374
2742
|
if (!s.hasBeenLive && res.sessionId !== s.sessionId) {
|
|
@@ -2379,8 +2747,13 @@ var init_SessionManager = __esm({
|
|
|
2379
2747
|
s.availableModes = res.modes.availableModes;
|
|
2380
2748
|
s.currentModeId = res.modes.currentModeId;
|
|
2381
2749
|
s.agentId = res.modes.currentModeId ?? s.agentId;
|
|
2382
|
-
|
|
2750
|
+
const settings = sessionSettings(res.configOptions);
|
|
2751
|
+
if (settings) {
|
|
2752
|
+
s.modelId = settings.modelId ?? s.modelId;
|
|
2753
|
+
s.effortLevel = settings.effortLevel ?? void 0;
|
|
2754
|
+
}
|
|
2383
2755
|
rememberModes(res.modes.availableModes);
|
|
2756
|
+
rememberKiroModels(res.configOptions);
|
|
2384
2757
|
s.markLive();
|
|
2385
2758
|
s.proc = proc;
|
|
2386
2759
|
s.spawning = void 0;
|
|
@@ -2393,6 +2766,25 @@ var init_SessionManager = __esm({
|
|
|
2393
2766
|
throw err;
|
|
2394
2767
|
}
|
|
2395
2768
|
}
|
|
2769
|
+
/** A fork starts on kiro's default mode and model, so the ones the chat had are set
|
|
2770
|
+
* again. Returns the session as it now is. */
|
|
2771
|
+
async applyForkSettings(proc, s, res) {
|
|
2772
|
+
const got = sessionSettings(res.configOptions);
|
|
2773
|
+
let changed = false;
|
|
2774
|
+
if (s.agentId && res.modes.currentModeId !== s.agentId) {
|
|
2775
|
+
await proc.setMode(res.sessionId, s.agentId);
|
|
2776
|
+
res = { ...res, modes: { ...res.modes, currentModeId: s.agentId } };
|
|
2777
|
+
}
|
|
2778
|
+
if (s.modelId && got?.modelId !== s.modelId) {
|
|
2779
|
+
await proc.setModel(res.sessionId, s.modelId);
|
|
2780
|
+
changed = true;
|
|
2781
|
+
}
|
|
2782
|
+
if (s.effortLevel && (changed || got?.effortLevel !== s.effortLevel)) {
|
|
2783
|
+
await proc.setEffort(res.sessionId, s.effortLevel);
|
|
2784
|
+
changed = true;
|
|
2785
|
+
}
|
|
2786
|
+
return changed ? { ...res, configOptions: void 0 } : res;
|
|
2787
|
+
}
|
|
2396
2788
|
async ensureCapacity() {
|
|
2397
2789
|
const liveIds = [...this.sessions.values()].filter((s) => s.proc);
|
|
2398
2790
|
if (liveIds.length < config.maxLiveSessions) return;
|
|
@@ -2412,33 +2804,116 @@ var init_SessionManager = __esm({
|
|
|
2412
2804
|
this.log.warn("at capacity but all processes are busy");
|
|
2413
2805
|
}
|
|
2414
2806
|
}
|
|
2415
|
-
/** Creates a new session. Spawns immediately to get a real kiro sessionId.
|
|
2807
|
+
/** Creates a new session. Spawns immediately to get a real kiro sessionId. It has no
|
|
2808
|
+
* Casper title, so kiro's own title names it once kiro has made one. */
|
|
2416
2809
|
async createChat(opts) {
|
|
2417
2810
|
const chatId = isValidChatId(opts.chatId) ? opts.chatId : crypto.randomUUID();
|
|
2418
2811
|
const cwd = opts.freshWorkspace ? createChatWorkspace(chatId) : resolveCwd(opts.cwd);
|
|
2419
|
-
const
|
|
2420
|
-
const
|
|
2812
|
+
const modelId = opts.modelId ?? await this.defaultModel();
|
|
2813
|
+
const s = await this.startSession(new EventStore(chatId), cwd, {
|
|
2814
|
+
agentId: opts.agentId ?? await this.defaultAgent() ?? config.defaultAgent,
|
|
2815
|
+
modelId,
|
|
2816
|
+
effortLevel: opts.effortLevel ?? await this.defaultEffort(modelId)
|
|
2817
|
+
});
|
|
2818
|
+
this.store.create(chatId);
|
|
2819
|
+
this.store.bindSession(chatId, s.sessionId);
|
|
2820
|
+
return this.buildDetail(this.store.get(chatId), s, []);
|
|
2821
|
+
}
|
|
2822
|
+
/** Starts a kiro session, new or forked, and holds it in memory under kiro's id. */
|
|
2823
|
+
async startSession(store, cwd, init) {
|
|
2824
|
+
const tempId = `pending-${crypto.randomUUID()}`;
|
|
2421
2825
|
const s = new Session(tempId, store, cwd);
|
|
2422
|
-
s.agentId =
|
|
2423
|
-
s.currentModeId =
|
|
2424
|
-
s.modelId =
|
|
2826
|
+
s.agentId = init.agentId;
|
|
2827
|
+
s.currentModeId = init.agentId;
|
|
2828
|
+
s.modelId = init.modelId;
|
|
2829
|
+
s.effortLevel = init.effortLevel;
|
|
2830
|
+
s.forkOf = init.forkOf;
|
|
2425
2831
|
this.sessions.set(tempId, s);
|
|
2426
2832
|
try {
|
|
2427
2833
|
await this.ensureProc(s);
|
|
2428
2834
|
} catch (err) {
|
|
2429
|
-
this.
|
|
2835
|
+
this.sessions.delete(s.sessionId);
|
|
2836
|
+
s.proc?.dispose();
|
|
2430
2837
|
throw err;
|
|
2431
2838
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2839
|
+
return s;
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* A new chat holding this chat's conversation up to and including the turn of the
|
|
2843
|
+
* prompt `messageId`. A workspace Casper made is copied with it, so either chat can be
|
|
2844
|
+
* deleted without taking the other's files.
|
|
2845
|
+
*/
|
|
2846
|
+
async branchChat(chatId, messageId) {
|
|
2847
|
+
const parent = await this.ensureOpen(chatId);
|
|
2848
|
+
await this.settleReload(parent);
|
|
2849
|
+
const prompts = await promptStamps(parent.sessionId);
|
|
2850
|
+
const at = prompts.findIndex((p) => p.id === messageId);
|
|
2851
|
+
if (at < 0) throw new Error(`Unknown message: ${messageId}`);
|
|
2852
|
+
const branchId = crypto.randomUUID();
|
|
2853
|
+
const fromDir = chatDir(chatId);
|
|
2854
|
+
const toDir = chatDir(branchId);
|
|
2855
|
+
const ownWorkspace = parent.cwd === chatWorkspaceDir(chatId);
|
|
2856
|
+
await fs11.promises.cp(fromDir, toDir, { recursive: true }).catch((err) => {
|
|
2857
|
+
if (err.code !== "ENOENT") throw err;
|
|
2858
|
+
});
|
|
2859
|
+
let s;
|
|
2860
|
+
try {
|
|
2861
|
+
s = await this.startSession(new EventStore(branchId), ownWorkspace ? createChatWorkspace(branchId) : parent.cwd, {
|
|
2862
|
+
agentId: parent.agentId,
|
|
2863
|
+
modelId: parent.modelId,
|
|
2864
|
+
effortLevel: parent.effortLevel,
|
|
2865
|
+
forkOf: { sessionId: parent.sessionId, messageId }
|
|
2866
|
+
});
|
|
2867
|
+
} catch (err) {
|
|
2868
|
+
await removeChatDir(branchId);
|
|
2869
|
+
throw err;
|
|
2438
2870
|
}
|
|
2439
|
-
|
|
2871
|
+
this.store.create(branchId);
|
|
2872
|
+
this.store.bindSession(branchId, s.sessionId);
|
|
2873
|
+
const parentRow = this.store.get(chatId);
|
|
2874
|
+
if (parentRow?.title) this.store.setTitle(branchId, parentRow.title);
|
|
2875
|
+
if (parentRow?.cwd && !ownWorkspace) this.store.setCwd(branchId, parentRow.cwd);
|
|
2876
|
+
const cutAt = prompts[at + 1]?.atMs;
|
|
2877
|
+
this.store.copyHistory(
|
|
2878
|
+
chatId,
|
|
2879
|
+
branchId,
|
|
2880
|
+
{ prompts: at + 1, before: cutAt === void 0 ? void 0 : cutAt - STAMP_PAD_MS },
|
|
2881
|
+
(p) => p.startsWith(fromDir + path12.sep) ? toDir + p.slice(fromDir.length) : p
|
|
2882
|
+
);
|
|
2883
|
+
return this.getDetail(branchId);
|
|
2440
2884
|
}
|
|
2441
|
-
|
|
2885
|
+
/**
|
|
2886
|
+
* Cuts this chat back to before the prompt `messageId`, so it can be sent again. kiro
|
|
2887
|
+
* cannot shorten a session, so the chat moves to a new kiro session holding the turns
|
|
2888
|
+
* before it, and the old session stays in kiro's store.
|
|
2889
|
+
*/
|
|
2890
|
+
async rewindChat(chatId, messageId) {
|
|
2891
|
+
const s = await this.ensureOpen(chatId);
|
|
2892
|
+
await this.settleReload(s);
|
|
2893
|
+
if (s.running) throw new Error("Cannot rewind while a turn is running");
|
|
2894
|
+
if (s.turnState.get().compacting) throw new Error("Cannot rewind while the conversation is being compacted");
|
|
2895
|
+
const prompts = await promptStamps(s.sessionId);
|
|
2896
|
+
const at = prompts.findIndex((p) => p.id === messageId);
|
|
2897
|
+
if (at < 0) throw new Error(`Unknown message: ${messageId}`);
|
|
2898
|
+
const next = await this.startSession(s.store, s.cwd, {
|
|
2899
|
+
agentId: s.agentId,
|
|
2900
|
+
modelId: s.modelId,
|
|
2901
|
+
effortLevel: s.effortLevel,
|
|
2902
|
+
forkOf: at > 0 ? { sessionId: s.sessionId, messageId: prompts[at - 1].id } : void 0
|
|
2903
|
+
});
|
|
2904
|
+
const old = s.proc;
|
|
2905
|
+
s.proc = void 0;
|
|
2906
|
+
this.sessions.delete(s.sessionId);
|
|
2907
|
+
this.store.bindSession(chatId, next.sessionId);
|
|
2908
|
+
this.store.dropHistory(chatId, { prompts: at, from: (prompts[at].atMs ?? Date.now()) - STAMP_PAD_MS });
|
|
2909
|
+
next.record({ kind: "history_replaced" });
|
|
2910
|
+
this.log.info({ chatId, from: s.sessionId, to: next.sessionId }, "chat rewound");
|
|
2911
|
+
await old?.disposeAndWait().catch(() => {
|
|
2912
|
+
});
|
|
2913
|
+
await this.forgetSession(s.sessionId);
|
|
2914
|
+
return this.getDetail(chatId);
|
|
2915
|
+
}
|
|
2916
|
+
async runPrompt(chatId, content, attachments, opts = {}) {
|
|
2442
2917
|
const s = await this.ensureOpen(chatId);
|
|
2443
2918
|
await this.settleReload(s);
|
|
2444
2919
|
if (s.running) throw new Error("A turn is already running for this session");
|
|
@@ -2451,13 +2926,9 @@ var init_SessionManager = __esm({
|
|
|
2451
2926
|
s.running = false;
|
|
2452
2927
|
throw err;
|
|
2453
2928
|
}
|
|
2454
|
-
if (
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
this.store.setTitle(chatId, title);
|
|
2458
|
-
s.title = title;
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2929
|
+
if (opts.retry) await this.dropFailedTurn(chatId, s, proc);
|
|
2930
|
+
s.lastTurn = { output: false, failed: false };
|
|
2931
|
+
s.turnStats = void 0;
|
|
2461
2932
|
let recorded;
|
|
2462
2933
|
if (attachments?.length) {
|
|
2463
2934
|
const ordinal = await promptCount(s.sessionId);
|
|
@@ -2465,11 +2936,18 @@ var init_SessionManager = __esm({
|
|
|
2465
2936
|
recorded = attachments;
|
|
2466
2937
|
}
|
|
2467
2938
|
s.record({ kind: "turn_started", prompt: content, attachments: recorded });
|
|
2468
|
-
proc.prompt({ sessionId: s.sessionId, prompt: content }).then((res) => {
|
|
2939
|
+
proc.prompt({ sessionId: s.sessionId, prompt: content }).then(async (res) => {
|
|
2469
2940
|
if (res.stopReason === "cancelled") this.saveEnding(chatId, s.recorder.finish({ kind: "interrupted" }));
|
|
2470
2941
|
else s.recorder.discard();
|
|
2471
|
-
s.
|
|
2472
|
-
|
|
2942
|
+
if (s.lastTurn) s.lastTurn.failed = res.stopReason === "cancelled";
|
|
2943
|
+
await this.dropSteers(s, proc);
|
|
2944
|
+
const stats = s.turnStats;
|
|
2945
|
+
s.turnStats = void 0;
|
|
2946
|
+
s.record({ kind: "turn_ended", stopReason: res.stopReason, ...stats ? { stats } : {} });
|
|
2947
|
+
}).catch(async (err) => {
|
|
2948
|
+
if (s.lastTurn) s.lastTurn.failed = true;
|
|
2949
|
+
s.turnStats = void 0;
|
|
2950
|
+
await this.dropSteers(s, proc);
|
|
2473
2951
|
if (s.cancelRequested) {
|
|
2474
2952
|
this.saveEnding(chatId, s.recorder.finish({ kind: "interrupted" }));
|
|
2475
2953
|
s.record({ kind: "turn_ended", stopReason: "cancelled" });
|
|
@@ -2492,6 +2970,67 @@ ${tail}` : err.message;
|
|
|
2492
2970
|
if (s.subagents.turnEnded()) s.record({ kind: "subagents_changed", subagents: s.subagents.list() });
|
|
2493
2971
|
});
|
|
2494
2972
|
}
|
|
2973
|
+
/** Sends a message into the running turn. It shows in the chat once the model reads it,
|
|
2974
|
+
* as a `steer_injected` event; one the turn ends without reading comes back as
|
|
2975
|
+
* `steer_dropped`. */
|
|
2976
|
+
async steer(chatId, text2, clientId) {
|
|
2977
|
+
const s = this.sessions.get(this.store.sessionIdForChat(chatId) ?? "");
|
|
2978
|
+
const message = text2.trim();
|
|
2979
|
+
if (!message) throw new Error("The message is empty");
|
|
2980
|
+
const proc = s?.proc;
|
|
2981
|
+
if (!s || !proc || !s.running || s.cancelRequested) throw new Error("No turn is running");
|
|
2982
|
+
const id = crypto.randomUUID();
|
|
2983
|
+
const entry = { text: message, ...clientId ? { clientId } : {} };
|
|
2984
|
+
s.steers.set(id, entry);
|
|
2985
|
+
try {
|
|
2986
|
+
const kiroId = await proc.steer(s.sessionId, message, id);
|
|
2987
|
+
if (kiroId && kiroId !== id && s.steers.delete(id)) s.steers.set(kiroId, entry);
|
|
2988
|
+
} catch (err) {
|
|
2989
|
+
s.steers.delete(id);
|
|
2990
|
+
throw err;
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
/** kiro read the steers `ids` into the turn, or dropped them. kiro names a steer
|
|
2994
|
+
* `steer-<the id it was given>`. */
|
|
2995
|
+
steerSettled(s, injected, ids) {
|
|
2996
|
+
for (const raw of ids) {
|
|
2997
|
+
if (!raw) continue;
|
|
2998
|
+
const id = s.steers.has(raw) ? raw : raw.replace(/^steer-/, "");
|
|
2999
|
+
const entry = s.steers.get(id);
|
|
3000
|
+
if (!entry) continue;
|
|
3001
|
+
s.steers.delete(id);
|
|
3002
|
+
s.record({ kind: injected ? "steer_injected" : "steer_dropped", text: entry.text, ...entry.clientId ? { clientId: entry.clientId } : {} });
|
|
3003
|
+
if (injected && s.lastTurn) s.lastTurn.output = true;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
/** At a turn's end, drops the steers the model never read, so none waits in kiro for a
|
|
3007
|
+
* later turn the user did not mean it for. */
|
|
3008
|
+
async dropSteers(s, proc) {
|
|
3009
|
+
if (s.steers.size === 0) return;
|
|
3010
|
+
await proc.clearSteers(s.sessionId).catch(() => {
|
|
3011
|
+
});
|
|
3012
|
+
this.steerSettled(s, false, [...s.steers.keys()]);
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Before a retry, removes the failed turn it retries when kiro kept nothing of it but the
|
|
3016
|
+
* prompt, so the model does not read that prompt twice. A turn that did anything is left:
|
|
3017
|
+
* kiro's rewind would also undo the files it changed.
|
|
3018
|
+
*/
|
|
3019
|
+
async dropFailedTurn(chatId, s, proc) {
|
|
3020
|
+
const last = s.lastTurn;
|
|
3021
|
+
if (!last?.failed || last.output || !last.messageId) return;
|
|
3022
|
+
s.lastTurn = void 0;
|
|
3023
|
+
try {
|
|
3024
|
+
const stamps = await promptStamps(s.sessionId);
|
|
3025
|
+
const ordinal = stamps.findIndex((p) => p.id === last.messageId);
|
|
3026
|
+
if (ordinal < 0 || !await proc.revertTo(s.sessionId, last.messageId)) return;
|
|
3027
|
+
this.store.dropHistory(chatId, { prompts: ordinal, from: (stamps[ordinal].atMs ?? Date.now()) - STAMP_PAD_MS });
|
|
3028
|
+
s.record({ kind: "history_replaced" });
|
|
3029
|
+
this.log.info({ chatId, messageId: last.messageId }, "failed turn removed before its retry");
|
|
3030
|
+
} catch (err) {
|
|
3031
|
+
this.log.warn({ err, chatId }, "could not remove the failed turn before its retry");
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
2495
3034
|
saveEnding(chatId, ending) {
|
|
2496
3035
|
if (!ending) return;
|
|
2497
3036
|
try {
|
|
@@ -2541,6 +3080,42 @@ ${tail}` : err.message;
|
|
|
2541
3080
|
s.modelId = modelId;
|
|
2542
3081
|
s.lastActivity = Date.now();
|
|
2543
3082
|
}
|
|
3083
|
+
async setEffort(chatId, effortLevel) {
|
|
3084
|
+
const s = await this.ensureOpen(chatId);
|
|
3085
|
+
await this.settleReload(s);
|
|
3086
|
+
const proc = await this.ensureProc(s);
|
|
3087
|
+
await proc.setEffort(s.sessionId, effortLevel);
|
|
3088
|
+
s.effortLevel = effortLevel;
|
|
3089
|
+
s.lastActivity = Date.now();
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* What the chat has cost and the plan it counts against. Asks kiro for the context
|
|
3093
|
+
* breakdown too, which it reports only during a turn, and records it as metadata. Both
|
|
3094
|
+
* need a kiro process, so a dormant chat is started.
|
|
3095
|
+
*/
|
|
3096
|
+
async getUsage(chatId) {
|
|
3097
|
+
const s = await this.ensureOpen(chatId);
|
|
3098
|
+
await this.settleReload(s);
|
|
3099
|
+
const proc = await this.ensureProc(s);
|
|
3100
|
+
s.lastActivity = Date.now();
|
|
3101
|
+
const [raw, account, filed] = await Promise.all([
|
|
3102
|
+
proc.contextBreakdown(s.sessionId).catch(() => void 0),
|
|
3103
|
+
this.accountUsage(proc),
|
|
3104
|
+
sessionCredits(s.sessionId).catch(() => void 0)
|
|
3105
|
+
]);
|
|
3106
|
+
const contextBreakdown = contextBreakdownOf(raw);
|
|
3107
|
+
if (contextBreakdown) s.record({ kind: "metadata", params: { sessionId: s.sessionId, contextBreakdown } });
|
|
3108
|
+
const credits = Math.max(s.credits ?? 0, filed ?? 0);
|
|
3109
|
+
return { ...s.credits !== void 0 || filed !== void 0 ? { credits } : {}, ...account ? { account } : {} };
|
|
3110
|
+
}
|
|
3111
|
+
// The plan changes slowly and each read is a call to kiro's service, so it is kept a minute.
|
|
3112
|
+
account;
|
|
3113
|
+
async accountUsage(proc) {
|
|
3114
|
+
if (this.account && Date.now() - this.account.at < 6e4) return this.account.value;
|
|
3115
|
+
const value = accountUsageOf(await proc.accountUsage().catch(() => void 0));
|
|
3116
|
+
this.account = { at: Date.now(), value };
|
|
3117
|
+
return value;
|
|
3118
|
+
}
|
|
2544
3119
|
/** Runs one of kiro's slash commands. Compaction is the only one Casper offers. It
|
|
2545
3120
|
* returns once kiro has started; its progress arrives as compaction events. */
|
|
2546
3121
|
async execCommand(chatId, command) {
|
|
@@ -2574,7 +3149,8 @@ ${tail}` : err.message;
|
|
|
2574
3149
|
chatId: chat.chatId,
|
|
2575
3150
|
sessionId: live?.sessionId ?? chat.sessionId ?? void 0,
|
|
2576
3151
|
title: this.titleOf(chat, {
|
|
2577
|
-
|
|
3152
|
+
// A live session hears kiro's new title before kiro writes its file.
|
|
3153
|
+
kiroTitle: live?.title || persisted?.title,
|
|
2578
3154
|
firstPrompt: transcript && firstPromptText(transcript),
|
|
2579
3155
|
cwd
|
|
2580
3156
|
}),
|
|
@@ -2584,7 +3160,9 @@ ${tail}` : err.message;
|
|
|
2584
3160
|
liveness: live?.proc ? "live" : "dormant",
|
|
2585
3161
|
agentId: live?.agentId ?? persisted?.agentId,
|
|
2586
3162
|
modelId: live?.modelId ?? persisted?.modelId,
|
|
3163
|
+
effortLevel: live ? live.effortLevel : persisted?.effortLevel,
|
|
2587
3164
|
running: live?.running ?? false,
|
|
3165
|
+
pinned: chat.pinned,
|
|
2588
3166
|
// A dormant session falls back to kiro's file.
|
|
2589
3167
|
contextUsagePercentage: snap?.contextUsagePercentage || persisted?.contextUsagePercentage
|
|
2590
3168
|
};
|
|
@@ -2706,7 +3284,7 @@ ${tail}` : err.message;
|
|
|
2706
3284
|
let cut = transcript.length;
|
|
2707
3285
|
for (let i = transcript.length - 1; i >= 0; i--) {
|
|
2708
3286
|
const it = transcript[i];
|
|
2709
|
-
if (it.type === "message" && it.message.role === "user") {
|
|
3287
|
+
if (it.type === "message" && it.message.role === "user" && !it.message.steer) {
|
|
2710
3288
|
if (promptText && it.message.text.trim() === promptText) cut = i;
|
|
2711
3289
|
break;
|
|
2712
3290
|
}
|
|
@@ -2734,7 +3312,13 @@ ${tail}` : err.message;
|
|
|
2734
3312
|
await removeChatDir(chatId);
|
|
2735
3313
|
if (!sessionId) return;
|
|
2736
3314
|
this.evict(sessionId);
|
|
2737
|
-
await
|
|
3315
|
+
await this.forgetSession(sessionId);
|
|
3316
|
+
}
|
|
3317
|
+
/** Deletes a session's files from kiro's store. */
|
|
3318
|
+
async forgetSession(sessionId) {
|
|
3319
|
+
await deletePersistedSession(sessionId).catch((err) => {
|
|
3320
|
+
this.log.warn({ err, sessionId }, "could not delete a kiro session");
|
|
3321
|
+
});
|
|
2738
3322
|
setTimeout(() => void deletePersistedSession(sessionId).catch(() => {
|
|
2739
3323
|
}), 2500).unref?.();
|
|
2740
3324
|
}
|
|
@@ -2762,15 +3346,54 @@ async function configuredDefault() {
|
|
|
2762
3346
|
return void 0;
|
|
2763
3347
|
}
|
|
2764
3348
|
}
|
|
3349
|
+
async function configuredEfforts() {
|
|
3350
|
+
const out = /* @__PURE__ */ new Map();
|
|
3351
|
+
try {
|
|
3352
|
+
const { stdout } = await execFileAsync2(config.kiroBin, ["settings", "chat.modelDefaults", "-f", "json"]);
|
|
3353
|
+
const value = JSON.parse(stdout);
|
|
3354
|
+
if (!value || typeof value !== "object") return out;
|
|
3355
|
+
for (const [modelId, settings] of Object.entries(value)) {
|
|
3356
|
+
if (!settings || typeof settings !== "object") continue;
|
|
3357
|
+
for (const section of Object.values(settings)) {
|
|
3358
|
+
const effort = section?.effort;
|
|
3359
|
+
if (typeof effort === "string" && effort) {
|
|
3360
|
+
out.set(modelId, effort);
|
|
3361
|
+
break;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
} catch {
|
|
3366
|
+
}
|
|
3367
|
+
return out;
|
|
3368
|
+
}
|
|
3369
|
+
function withKiroDetails(m, known) {
|
|
3370
|
+
const k = known.get(m.modelId);
|
|
3371
|
+
if (!k) return m;
|
|
3372
|
+
const named = k.name ? { ...m, modelName: k.name } : m;
|
|
3373
|
+
if (k.levels.length === 0) return named;
|
|
3374
|
+
const own = userEfforts.get(m.modelId);
|
|
3375
|
+
const fallback = k.recommended && k.levels.includes(k.recommended) ? k.recommended : void 0;
|
|
3376
|
+
return {
|
|
3377
|
+
...named,
|
|
3378
|
+
effortLevels: k.levels,
|
|
3379
|
+
recommendedEffort: fallback,
|
|
3380
|
+
defaultEffort: own && k.levels.includes(own) ? own : fallback
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
2765
3383
|
async function listModels(log) {
|
|
2766
|
-
if (cache2)
|
|
3384
|
+
if (!cache2) await loadModels(log);
|
|
3385
|
+
const known = kiroModels();
|
|
3386
|
+
return cache2.map((m) => withKiroDetails(m, known));
|
|
3387
|
+
}
|
|
3388
|
+
async function loadModels(log) {
|
|
2767
3389
|
const { stdout } = await execFileAsync2(
|
|
2768
3390
|
config.kiroBin,
|
|
2769
3391
|
["chat", "--list-models", "-f", "json"],
|
|
2770
3392
|
{ maxBuffer: 4 * 1024 * 1024 }
|
|
2771
3393
|
);
|
|
2772
3394
|
const parsed = JSON.parse(stdout);
|
|
2773
|
-
const configured = await configuredDefault();
|
|
3395
|
+
const [configured, efforts] = await Promise.all([configuredDefault(), configuredEfforts()]);
|
|
3396
|
+
userEfforts = efforts;
|
|
2774
3397
|
const preferred = parsed.models.some((m) => m.model_id === configured) ? configured : parsed.default_model;
|
|
2775
3398
|
cache2 = parsed.models.map((m, i) => ({
|
|
2776
3399
|
modelId: m.model_id,
|
|
@@ -2782,7 +3405,6 @@ async function listModels(log) {
|
|
|
2782
3405
|
isDefault: preferred ? m.model_id === preferred : m.default_model ?? i === 0
|
|
2783
3406
|
}));
|
|
2784
3407
|
log.info({ count: cache2.length, default: preferred }, "loaded model list");
|
|
2785
|
-
return cache2;
|
|
2786
3408
|
}
|
|
2787
3409
|
async function defaultModelId(log) {
|
|
2788
3410
|
try {
|
|
@@ -2791,6 +3413,41 @@ async function defaultModelId(log) {
|
|
|
2791
3413
|
return void 0;
|
|
2792
3414
|
}
|
|
2793
3415
|
}
|
|
3416
|
+
async function defaultEffortFor(modelId, log) {
|
|
3417
|
+
if (!modelId) return void 0;
|
|
3418
|
+
try {
|
|
3419
|
+
return (await listModels(log)).find((m) => m.modelId === modelId)?.defaultEffort;
|
|
3420
|
+
} catch {
|
|
3421
|
+
return void 0;
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
function invalidateModels() {
|
|
3425
|
+
cache2 = null;
|
|
3426
|
+
}
|
|
3427
|
+
async function kiroSetting(key, value) {
|
|
3428
|
+
await execFileAsync2(config.kiroBin, ["settings", key, value]);
|
|
3429
|
+
}
|
|
3430
|
+
async function setDefaultModel(modelId, log) {
|
|
3431
|
+
if (!(await listModels(log)).some((m) => m.modelId === modelId)) throw new Error(`Unknown model: ${modelId}`);
|
|
3432
|
+
await kiroSetting("chat.defaultModel", modelId);
|
|
3433
|
+
invalidateModels();
|
|
3434
|
+
}
|
|
3435
|
+
async function setDefaultEffort(modelId, level, log) {
|
|
3436
|
+
const model = (await listModels(log)).find((m) => m.modelId === modelId);
|
|
3437
|
+
if (!model?.effortLevels?.includes(level)) throw new Error(`${modelId} has no effort level ${level}`);
|
|
3438
|
+
let all = {};
|
|
3439
|
+
try {
|
|
3440
|
+
const { stdout } = await execFileAsync2(config.kiroBin, ["settings", "chat.modelDefaults", "-f", "json"]);
|
|
3441
|
+
const value = JSON.parse(stdout);
|
|
3442
|
+
if (value && typeof value === "object" && !Array.isArray(value)) all = value;
|
|
3443
|
+
} catch {
|
|
3444
|
+
}
|
|
3445
|
+
const entry = { ...all[modelId] ?? {} };
|
|
3446
|
+
const section = Object.keys(entry).find((k) => typeof entry[k]?.effort === "string") ?? kiroModels().get(modelId)?.effortKey ?? (modelId.startsWith("claude") ? "output_config" : "reasoning");
|
|
3447
|
+
entry[section] = { ...entry[section] ?? {}, effort: level };
|
|
3448
|
+
await kiroSetting("chat.modelDefaults", JSON.stringify({ ...all, [modelId]: entry }));
|
|
3449
|
+
invalidateModels();
|
|
3450
|
+
}
|
|
2794
3451
|
async function kiroVersion() {
|
|
2795
3452
|
try {
|
|
2796
3453
|
const { stdout } = await execFileAsync2(config.kiroBin, ["--version"]);
|
|
@@ -2799,13 +3456,15 @@ async function kiroVersion() {
|
|
|
2799
3456
|
return void 0;
|
|
2800
3457
|
}
|
|
2801
3458
|
}
|
|
2802
|
-
var execFileAsync2, cache2;
|
|
3459
|
+
var execFileAsync2, cache2, userEfforts;
|
|
2803
3460
|
var init_models = __esm({
|
|
2804
3461
|
"server/src/session/models.ts"() {
|
|
2805
3462
|
"use strict";
|
|
2806
3463
|
init_config();
|
|
3464
|
+
init_kiroModels();
|
|
2807
3465
|
execFileAsync2 = promisify2(execFile2);
|
|
2808
3466
|
cache2 = null;
|
|
3467
|
+
userEfforts = /* @__PURE__ */ new Map();
|
|
2809
3468
|
}
|
|
2810
3469
|
});
|
|
2811
3470
|
|
|
@@ -2969,10 +3628,143 @@ var init_models2 = __esm({
|
|
|
2969
3628
|
}
|
|
2970
3629
|
});
|
|
2971
3630
|
|
|
3631
|
+
// server/src/session/kiroTools.ts
|
|
3632
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
3633
|
+
import fs12 from "node:fs/promises";
|
|
3634
|
+
import path13 from "node:path";
|
|
3635
|
+
import { promisify as promisify3 } from "node:util";
|
|
3636
|
+
function kiroHome() {
|
|
3637
|
+
return path13.dirname(config.kiroSessionsDir);
|
|
3638
|
+
}
|
|
3639
|
+
async function readSetting(key) {
|
|
3640
|
+
try {
|
|
3641
|
+
const { stdout } = await execFileAsync3(config.kiroBin, ["settings", key, "-f", "json"]);
|
|
3642
|
+
return JSON.parse(stdout);
|
|
3643
|
+
} catch {
|
|
3644
|
+
return void 0;
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
async function writeSetting(key, value) {
|
|
3648
|
+
await execFileAsync3(config.kiroBin, ["settings", key, value]);
|
|
3649
|
+
}
|
|
3650
|
+
async function configuredDefaultAgent() {
|
|
3651
|
+
if (agentCache && Date.now() - agentCache.at < 3e4) return agentCache.value;
|
|
3652
|
+
const value = await readSetting("chat.defaultAgent");
|
|
3653
|
+
agentCache = { at: Date.now(), value: typeof value === "string" && value ? value : void 0 };
|
|
3654
|
+
return agentCache.value;
|
|
3655
|
+
}
|
|
3656
|
+
async function setDefaultAgent(agentId) {
|
|
3657
|
+
await writeSetting("chat.defaultAgent", agentId);
|
|
3658
|
+
agentCache = null;
|
|
3659
|
+
}
|
|
3660
|
+
async function readMcp() {
|
|
3661
|
+
try {
|
|
3662
|
+
const value = JSON.parse(await fs12.readFile(mcpFile(), "utf8"));
|
|
3663
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3664
|
+
} catch (err) {
|
|
3665
|
+
if (err.code === "ENOENT") return {};
|
|
3666
|
+
throw new Error(`Could not read ${mcpFile()}: ${err.message}`);
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
async function writeMcp(data) {
|
|
3670
|
+
const file = mcpFile();
|
|
3671
|
+
await fs12.mkdir(path13.dirname(file), { recursive: true });
|
|
3672
|
+
const mode = await fs12.stat(file).then((s) => s.mode & 511, () => 384);
|
|
3673
|
+
const tmp = `${file}.casper-${process.pid}`;
|
|
3674
|
+
await fs12.writeFile(tmp, `${JSON.stringify(data, null, 2)}
|
|
3675
|
+
`, { mode });
|
|
3676
|
+
await fs12.rename(tmp, file);
|
|
3677
|
+
}
|
|
3678
|
+
async function listMcpServers() {
|
|
3679
|
+
const servers = (await readMcp()).mcpServers ?? {};
|
|
3680
|
+
return Object.entries(servers).map(([name, s]) => ({
|
|
3681
|
+
name,
|
|
3682
|
+
...typeof s.command === "string" ? { command: s.command } : {},
|
|
3683
|
+
...typeof s.url === "string" ? { url: s.url } : {},
|
|
3684
|
+
args: strings(s.args),
|
|
3685
|
+
envKeys: s.env && typeof s.env === "object" ? Object.keys(s.env) : [],
|
|
3686
|
+
disabled: s.disabled === true
|
|
3687
|
+
}));
|
|
3688
|
+
}
|
|
3689
|
+
async function addMcpServer(input) {
|
|
3690
|
+
const name = String(input.name ?? "").trim();
|
|
3691
|
+
if (!SERVER_NAME.test(name)) throw new Error("A name is letters, digits, dots, dashes and underscores");
|
|
3692
|
+
const command = typeof input.command === "string" ? input.command.trim() : "";
|
|
3693
|
+
const url4 = typeof input.url === "string" ? input.url.trim() : "";
|
|
3694
|
+
if (!!command === !!url4) throw new Error("Give either a command or a URL");
|
|
3695
|
+
if (url4 && !/^https?:\/\//.test(url4)) throw new Error("The URL must start with http:// or https://");
|
|
3696
|
+
const args = strings(input.args);
|
|
3697
|
+
const env2 = {};
|
|
3698
|
+
for (const [k, v] of Object.entries(input.env ?? {})) {
|
|
3699
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k) || typeof v !== "string") throw new Error(`Bad environment variable: ${k}`);
|
|
3700
|
+
env2[k] = v;
|
|
3701
|
+
}
|
|
3702
|
+
const data = await readMcp();
|
|
3703
|
+
const servers = { ...data.mcpServers ?? {} };
|
|
3704
|
+
if (servers[name]) throw new Error(`A server named ${name} already exists`);
|
|
3705
|
+
servers[name] = { ...url4 ? { url: url4 } : { command, args }, ...Object.keys(env2).length ? { env: env2 } : {} };
|
|
3706
|
+
await writeMcp({ ...data, mcpServers: servers });
|
|
3707
|
+
}
|
|
3708
|
+
async function setMcpServerDisabled(name, disabled) {
|
|
3709
|
+
const data = await readMcp();
|
|
3710
|
+
const entry = data.mcpServers?.[name];
|
|
3711
|
+
if (!entry) throw new Error(`No server named ${name}`);
|
|
3712
|
+
const next = { ...entry };
|
|
3713
|
+
if (disabled) next.disabled = true;
|
|
3714
|
+
else delete next.disabled;
|
|
3715
|
+
await writeMcp({ ...data, mcpServers: { ...data.mcpServers, [name]: next } });
|
|
3716
|
+
}
|
|
3717
|
+
async function removeMcpServer(name) {
|
|
3718
|
+
const data = await readMcp();
|
|
3719
|
+
if (!data.mcpServers?.[name]) throw new Error(`No server named ${name}`);
|
|
3720
|
+
const { [name]: _removed, ...rest } = data.mcpServers;
|
|
3721
|
+
await writeMcp({ ...data, mcpServers: rest });
|
|
3722
|
+
}
|
|
3723
|
+
function frontMatter(text2) {
|
|
3724
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text2);
|
|
3725
|
+
const out = {};
|
|
3726
|
+
for (const line of m?.[1]?.split(/\r?\n/) ?? []) {
|
|
3727
|
+
const kv = /^([A-Za-z_-]+):\s*(.*)$/.exec(line);
|
|
3728
|
+
if (kv) out[kv[1]] = kv[2].replace(/^["']|["']$/g, "").trim();
|
|
3729
|
+
}
|
|
3730
|
+
return out;
|
|
3731
|
+
}
|
|
3732
|
+
async function listSkills() {
|
|
3733
|
+
const dir = path13.join(kiroHome(), "skills");
|
|
3734
|
+
let entries;
|
|
3735
|
+
try {
|
|
3736
|
+
entries = (await fs12.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);
|
|
3737
|
+
} catch {
|
|
3738
|
+
return [];
|
|
3739
|
+
}
|
|
3740
|
+
const out = [];
|
|
3741
|
+
for (const name of entries.sort()) {
|
|
3742
|
+
const file = path13.join(dir, name, "SKILL.md");
|
|
3743
|
+
try {
|
|
3744
|
+
const meta = frontMatter(await fs12.readFile(file, "utf8"));
|
|
3745
|
+
out.push({ name: meta.name || name, description: meta.description ?? "", path: file });
|
|
3746
|
+
} catch {
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
return out;
|
|
3750
|
+
}
|
|
3751
|
+
var execFileAsync3, mcpFile, agentCache, strings, SERVER_NAME;
|
|
3752
|
+
var init_kiroTools = __esm({
|
|
3753
|
+
"server/src/session/kiroTools.ts"() {
|
|
3754
|
+
"use strict";
|
|
3755
|
+
init_config();
|
|
3756
|
+
execFileAsync3 = promisify3(execFile3);
|
|
3757
|
+
mcpFile = () => path13.join(kiroHome(), "settings", "mcp.json");
|
|
3758
|
+
agentCache = null;
|
|
3759
|
+
strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
3760
|
+
SERVER_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
|
|
3761
|
+
}
|
|
3762
|
+
});
|
|
3763
|
+
|
|
2972
3764
|
// server/src/routes/agents.ts
|
|
2973
3765
|
function registerAgentRoutes(app) {
|
|
2974
3766
|
app.get("/api/agents", async () => {
|
|
2975
|
-
return { agents: await listAgents(), defaultAgentId: config.defaultAgent };
|
|
3767
|
+
return { agents: await listAgents(), defaultAgentId: await configuredDefaultAgent() ?? config.defaultAgent };
|
|
2976
3768
|
});
|
|
2977
3769
|
}
|
|
2978
3770
|
var init_agents2 = __esm({
|
|
@@ -2980,12 +3772,13 @@ var init_agents2 = __esm({
|
|
|
2980
3772
|
"use strict";
|
|
2981
3773
|
init_agents();
|
|
2982
3774
|
init_config();
|
|
3775
|
+
init_kiroTools();
|
|
2983
3776
|
}
|
|
2984
3777
|
});
|
|
2985
3778
|
|
|
2986
3779
|
// server/src/util/confinedFile.ts
|
|
2987
|
-
import
|
|
2988
|
-
import
|
|
3780
|
+
import fs13 from "node:fs/promises";
|
|
3781
|
+
import path14 from "node:path";
|
|
2989
3782
|
async function resolveConfined(input, spec) {
|
|
2990
3783
|
let target = null;
|
|
2991
3784
|
for (const root of spec.lexical) {
|
|
@@ -3001,7 +3794,7 @@ async function resolveConfined(input, spec) {
|
|
|
3001
3794
|
if (!real) return { ok: false, ...await spec.notFound() };
|
|
3002
3795
|
let stat;
|
|
3003
3796
|
try {
|
|
3004
|
-
stat = await
|
|
3797
|
+
stat = await fs13.stat(real);
|
|
3005
3798
|
} catch {
|
|
3006
3799
|
return { ok: false, ...await spec.notFound() };
|
|
3007
3800
|
}
|
|
@@ -3040,7 +3833,7 @@ async function resolveChatPath(sessions, sessionId, requestedPath, require2) {
|
|
|
3040
3833
|
async function workspaceNotFound(cwd) {
|
|
3041
3834
|
let missing;
|
|
3042
3835
|
try {
|
|
3043
|
-
missing = !(await
|
|
3836
|
+
missing = !(await fs13.stat(cwd)).isDirectory();
|
|
3044
3837
|
} catch {
|
|
3045
3838
|
missing = true;
|
|
3046
3839
|
}
|
|
@@ -3060,7 +3853,7 @@ async function resolveAbsolutePath(input, require2) {
|
|
|
3060
3853
|
});
|
|
3061
3854
|
}
|
|
3062
3855
|
async function classifyDirent(parentReal, entry, realRoots) {
|
|
3063
|
-
const absolute =
|
|
3856
|
+
const absolute = path14.join(parentReal, entry.name);
|
|
3064
3857
|
if (entry.isDirectory()) return { kind: "directory", real: absolute };
|
|
3065
3858
|
if (entry.isFile()) return { kind: "file", real: absolute };
|
|
3066
3859
|
if (!entry.isSymbolicLink()) return null;
|
|
@@ -3068,7 +3861,7 @@ async function classifyDirent(parentReal, entry, realRoots) {
|
|
|
3068
3861
|
const real = await realConfineToRoot(root, absolute);
|
|
3069
3862
|
if (!real) continue;
|
|
3070
3863
|
try {
|
|
3071
|
-
const stat = await
|
|
3864
|
+
const stat = await fs13.stat(real);
|
|
3072
3865
|
if (stat.isDirectory()) return { kind: "directory", real };
|
|
3073
3866
|
if (stat.isFile()) return { kind: "file", real };
|
|
3074
3867
|
} catch {
|
|
@@ -3086,7 +3879,7 @@ var init_confinedFile = __esm({
|
|
|
3086
3879
|
});
|
|
3087
3880
|
|
|
3088
3881
|
// server/src/util/filekind.ts
|
|
3089
|
-
import
|
|
3882
|
+
import path15 from "node:path";
|
|
3090
3883
|
function mimeForExt(ext) {
|
|
3091
3884
|
return MIME_MAP[ext.toLowerCase()] ?? "application/octet-stream";
|
|
3092
3885
|
}
|
|
@@ -3106,7 +3899,7 @@ function looksBinary(buf) {
|
|
|
3106
3899
|
return control / buf.length > 0.3;
|
|
3107
3900
|
}
|
|
3108
3901
|
function classifyKind(name) {
|
|
3109
|
-
const ext =
|
|
3902
|
+
const ext = path15.extname(name).toLowerCase();
|
|
3110
3903
|
if (ext in IMAGE_EXTS) return "image";
|
|
3111
3904
|
if (TEXT_EXTS.has(ext)) return "text";
|
|
3112
3905
|
return "binary";
|
|
@@ -3216,9 +4009,9 @@ var init_filekind = __esm({
|
|
|
3216
4009
|
});
|
|
3217
4010
|
|
|
3218
4011
|
// server/src/routes/filePreview.ts
|
|
3219
|
-
import
|
|
4012
|
+
import fs14 from "node:fs/promises";
|
|
3220
4013
|
import { createReadStream } from "node:fs";
|
|
3221
|
-
import
|
|
4014
|
+
import path16 from "node:path";
|
|
3222
4015
|
function tooLargeForPreview(size, maxBytes) {
|
|
3223
4016
|
return {
|
|
3224
4017
|
error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
|
|
@@ -3245,7 +4038,7 @@ function hexdump(buf) {
|
|
|
3245
4038
|
return lines.join("\n");
|
|
3246
4039
|
}
|
|
3247
4040
|
async function sendFilePreview(req, reply, realTarget, stat) {
|
|
3248
|
-
const ext =
|
|
4041
|
+
const ext = path16.extname(realTarget).toLowerCase();
|
|
3249
4042
|
const mime = mimeForExt(ext);
|
|
3250
4043
|
const isImage2 = mime.startsWith("image/");
|
|
3251
4044
|
const isPdf = mime === "application/pdf";
|
|
@@ -3290,7 +4083,7 @@ async function sendFilePreview(req, reply, realTarget, stat) {
|
|
|
3290
4083
|
if (kind === "binary") {
|
|
3291
4084
|
let fh;
|
|
3292
4085
|
try {
|
|
3293
|
-
fh = await
|
|
4086
|
+
fh = await fs14.open(realTarget, "r");
|
|
3294
4087
|
const buf = Buffer.alloc(Math.min(HEXDUMP_BYTES, stat.size));
|
|
3295
4088
|
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
3296
4089
|
const head = buf.subarray(0, bytesRead);
|
|
@@ -3334,9 +4127,9 @@ var init_filePreview = __esm({
|
|
|
3334
4127
|
});
|
|
3335
4128
|
|
|
3336
4129
|
// server/src/routes/fs.ts
|
|
3337
|
-
import
|
|
4130
|
+
import fs15 from "node:fs/promises";
|
|
3338
4131
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
3339
|
-
import
|
|
4132
|
+
import path17 from "node:path";
|
|
3340
4133
|
function registerFsRoutes(app) {
|
|
3341
4134
|
app.get(
|
|
3342
4135
|
"/api/fs/dirs",
|
|
@@ -3344,10 +4137,10 @@ function registerFsRoutes(app) {
|
|
|
3344
4137
|
const input = (req.query.path ?? "").trim();
|
|
3345
4138
|
const base = config.defaultCwd;
|
|
3346
4139
|
const endsWithSep = input.endsWith("/");
|
|
3347
|
-
const resolved = input ?
|
|
3348
|
-
const dir = endsWithSep || !input ? resolved :
|
|
3349
|
-
const prefix = endsWithSep || !input ? "" :
|
|
3350
|
-
const targetKind = await
|
|
4140
|
+
const resolved = input ? path17.resolve(base, input) : base;
|
|
4141
|
+
const dir = endsWithSep || !input ? resolved : path17.dirname(resolved);
|
|
4142
|
+
const prefix = endsWithSep || !input ? "" : path17.basename(resolved);
|
|
4143
|
+
const targetKind = await fs15.stat(resolved).then((s) => s.isDirectory() ? "directory" : "file").catch(() => "missing");
|
|
3351
4144
|
const listing = await resolveAbsolutePath(dir, "directory");
|
|
3352
4145
|
if (!listing.ok) {
|
|
3353
4146
|
if (listing.status === 403) return replyWith(reply, listing);
|
|
@@ -3356,7 +4149,7 @@ function registerFsRoutes(app) {
|
|
|
3356
4149
|
const realDir = listing.real;
|
|
3357
4150
|
let entries = [];
|
|
3358
4151
|
try {
|
|
3359
|
-
const dirents = await
|
|
4152
|
+
const dirents = await fs15.readdir(realDir, { withFileTypes: true });
|
|
3360
4153
|
const checks2 = await Promise.all(
|
|
3361
4154
|
dirents.map(async (d) => {
|
|
3362
4155
|
const target = await classifyDirent(realDir, d, absoluteRoots());
|
|
@@ -3367,7 +4160,7 @@ function registerFsRoutes(app) {
|
|
|
3367
4160
|
entries = checks2.filter((name) => name !== null).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase())).sort((a, b) => {
|
|
3368
4161
|
if (isDot(a) !== isDot(b)) return isDot(a) ? 1 : -1;
|
|
3369
4162
|
return a.localeCompare(b);
|
|
3370
|
-
}).slice(0, 500).map((name) =>
|
|
4163
|
+
}).slice(0, 500).map((name) => path17.join(dir, name));
|
|
3371
4164
|
} catch {
|
|
3372
4165
|
entries = [];
|
|
3373
4166
|
}
|
|
@@ -3382,7 +4175,7 @@ function registerFsRoutes(app) {
|
|
|
3382
4175
|
reply.code(400);
|
|
3383
4176
|
return { error: "path parameter is required" };
|
|
3384
4177
|
}
|
|
3385
|
-
if (!
|
|
4178
|
+
if (!path17.isAbsolute(filePath)) {
|
|
3386
4179
|
reply.code(400);
|
|
3387
4180
|
return { error: "path must be absolute" };
|
|
3388
4181
|
}
|
|
@@ -3392,7 +4185,7 @@ function registerFsRoutes(app) {
|
|
|
3392
4185
|
reply.header("Content-Type", "application/octet-stream");
|
|
3393
4186
|
reply.header(
|
|
3394
4187
|
"Content-Disposition",
|
|
3395
|
-
`attachment; filename="${
|
|
4188
|
+
`attachment; filename="${path17.basename(resolved.real).replace(/"/g, "")}"`
|
|
3396
4189
|
);
|
|
3397
4190
|
reply.header("Content-Length", resolved.stat.size);
|
|
3398
4191
|
return reply.send(createReadStream2(resolved.real));
|
|
@@ -3422,8 +4215,8 @@ function registerChatRoutes(app, manager) {
|
|
|
3422
4215
|
cwd: body.cwd,
|
|
3423
4216
|
agentId: body.agentId,
|
|
3424
4217
|
modelId: body.modelId,
|
|
4218
|
+
effortLevel: body.effortLevel,
|
|
3425
4219
|
freshWorkspace: body.freshWorkspace,
|
|
3426
|
-
title: body.title,
|
|
3427
4220
|
// Not optional in practice: the client's chat already owns any file uploaded before
|
|
3428
4221
|
// the session existed, so dropping it here strands those files under an id nothing
|
|
3429
4222
|
// refers to again.
|
|
@@ -3482,6 +4275,47 @@ function registerChatRoutes(app, manager) {
|
|
|
3482
4275
|
return { ok: true };
|
|
3483
4276
|
}
|
|
3484
4277
|
);
|
|
4278
|
+
app.post(
|
|
4279
|
+
"/api/chats/:id/effort",
|
|
4280
|
+
async (req) => {
|
|
4281
|
+
await manager.setEffort(req.params.id, req.body.effortLevel);
|
|
4282
|
+
return { ok: true };
|
|
4283
|
+
}
|
|
4284
|
+
);
|
|
4285
|
+
app.get("/api/chats/:id/usage", async (req, reply) => {
|
|
4286
|
+
try {
|
|
4287
|
+
return await manager.getUsage(req.params.id);
|
|
4288
|
+
} catch (err) {
|
|
4289
|
+
reply.code(400);
|
|
4290
|
+
return { error: err.message };
|
|
4291
|
+
}
|
|
4292
|
+
});
|
|
4293
|
+
app.post("/api/chats/:id/branch", async (req, reply) => {
|
|
4294
|
+
const messageId = req.body?.messageId;
|
|
4295
|
+
if (typeof messageId !== "string" || !messageId) {
|
|
4296
|
+
reply.code(400);
|
|
4297
|
+
return { error: "messageId is required" };
|
|
4298
|
+
}
|
|
4299
|
+
try {
|
|
4300
|
+
return await manager.branchChat(req.params.id, messageId);
|
|
4301
|
+
} catch (err) {
|
|
4302
|
+
reply.code(400);
|
|
4303
|
+
return { error: err.message };
|
|
4304
|
+
}
|
|
4305
|
+
});
|
|
4306
|
+
app.post("/api/chats/:id/rewind", async (req, reply) => {
|
|
4307
|
+
const messageId = req.body?.messageId;
|
|
4308
|
+
if (typeof messageId !== "string" || !messageId) {
|
|
4309
|
+
reply.code(400);
|
|
4310
|
+
return { error: "messageId is required" };
|
|
4311
|
+
}
|
|
4312
|
+
try {
|
|
4313
|
+
return await manager.rewindChat(req.params.id, messageId);
|
|
4314
|
+
} catch (err) {
|
|
4315
|
+
reply.code(400);
|
|
4316
|
+
return { error: err.message };
|
|
4317
|
+
}
|
|
4318
|
+
});
|
|
3485
4319
|
app.post(
|
|
3486
4320
|
"/api/chats/:id/mode",
|
|
3487
4321
|
async (req) => {
|
|
@@ -3496,6 +4330,22 @@ function registerChatRoutes(app, manager) {
|
|
|
3496
4330
|
return { ok: true };
|
|
3497
4331
|
}
|
|
3498
4332
|
);
|
|
4333
|
+
app.post(
|
|
4334
|
+
"/api/chats/:id/pin",
|
|
4335
|
+
async (req, reply) => {
|
|
4336
|
+
if (typeof req.body?.pinned !== "boolean") {
|
|
4337
|
+
reply.code(400);
|
|
4338
|
+
return { error: "pinned must be true or false" };
|
|
4339
|
+
}
|
|
4340
|
+
try {
|
|
4341
|
+
manager.pinChat(req.params.id, req.body.pinned);
|
|
4342
|
+
return { ok: true };
|
|
4343
|
+
} catch (err) {
|
|
4344
|
+
reply.code(404);
|
|
4345
|
+
return { error: err.message };
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
);
|
|
3499
4349
|
app.post(
|
|
3500
4350
|
"/api/chats/:id/cwd",
|
|
3501
4351
|
async (req, reply) => {
|
|
@@ -3563,9 +4413,9 @@ var init_subagents = __esm({
|
|
|
3563
4413
|
});
|
|
3564
4414
|
|
|
3565
4415
|
// server/src/routes/workspace.ts
|
|
3566
|
-
import
|
|
4416
|
+
import fs16 from "node:fs/promises";
|
|
3567
4417
|
import { createReadStream as createReadStream3 } from "node:fs";
|
|
3568
|
-
import
|
|
4418
|
+
import path18 from "node:path";
|
|
3569
4419
|
function registerWorkspaceRoutes(app, manager) {
|
|
3570
4420
|
app.get(
|
|
3571
4421
|
"/api/chats/:id/tree",
|
|
@@ -3580,7 +4430,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
3580
4430
|
const { cwd, relative, real: realTarget } = resolved;
|
|
3581
4431
|
let dirents;
|
|
3582
4432
|
try {
|
|
3583
|
-
dirents = await
|
|
4433
|
+
dirents = await fs16.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
|
|
3584
4434
|
} catch {
|
|
3585
4435
|
return replyWith(reply, await workspaceNotFound(cwd));
|
|
3586
4436
|
}
|
|
@@ -3594,7 +4444,7 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
3594
4444
|
continue;
|
|
3595
4445
|
}
|
|
3596
4446
|
try {
|
|
3597
|
-
const stat = await
|
|
4447
|
+
const stat = await fs16.stat(target.real);
|
|
3598
4448
|
entries.push({
|
|
3599
4449
|
name: d.name,
|
|
3600
4450
|
path: entryRelative,
|
|
@@ -3623,8 +4473,8 @@ function registerWorkspaceRoutes(app, manager) {
|
|
|
3623
4473
|
reply.code(413);
|
|
3624
4474
|
return { error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 100 MB)` };
|
|
3625
4475
|
}
|
|
3626
|
-
const ext =
|
|
3627
|
-
const filename =
|
|
4476
|
+
const ext = path18.extname(realTarget);
|
|
4477
|
+
const filename = path18.basename(realTarget);
|
|
3628
4478
|
const asciiName = filename.replace(/[^\w.\-]/g, "_");
|
|
3629
4479
|
reply.header("Content-Type", mimeForExt(ext));
|
|
3630
4480
|
reply.header(
|
|
@@ -3659,27 +4509,27 @@ var init_workspace = __esm({
|
|
|
3659
4509
|
|
|
3660
4510
|
// server/src/routes/uploads.ts
|
|
3661
4511
|
import { createReadStream as createReadStream4, createWriteStream } from "node:fs";
|
|
3662
|
-
import
|
|
3663
|
-
import
|
|
4512
|
+
import fs17 from "node:fs/promises";
|
|
4513
|
+
import path19 from "node:path";
|
|
3664
4514
|
import crypto4 from "node:crypto";
|
|
3665
|
-
import { execFile as
|
|
4515
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
3666
4516
|
import { pipeline } from "node:stream/promises";
|
|
3667
4517
|
function sanitizeName(raw) {
|
|
3668
|
-
const base =
|
|
4518
|
+
const base = path19.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
|
|
3669
4519
|
if (!base || base === "." || base === "..") return `upload-${Date.now()}`;
|
|
3670
4520
|
return base.replace(/^\.+/, "") || `upload-${Date.now()}`;
|
|
3671
4521
|
}
|
|
3672
4522
|
async function uniquePath(dir, name) {
|
|
3673
|
-
const ext =
|
|
4523
|
+
const ext = path19.extname(name);
|
|
3674
4524
|
const stem = name.slice(0, name.length - ext.length);
|
|
3675
4525
|
let candidate = name;
|
|
3676
4526
|
let n = 1;
|
|
3677
4527
|
while (true) {
|
|
3678
4528
|
try {
|
|
3679
|
-
await
|
|
4529
|
+
await fs17.access(path19.join(dir, candidate));
|
|
3680
4530
|
candidate = `${stem} (${n++})${ext}`;
|
|
3681
4531
|
} catch {
|
|
3682
|
-
return
|
|
4532
|
+
return path19.join(dir, candidate);
|
|
3683
4533
|
}
|
|
3684
4534
|
}
|
|
3685
4535
|
}
|
|
@@ -3691,7 +4541,7 @@ function sha256File(absPath) {
|
|
|
3691
4541
|
}
|
|
3692
4542
|
function fileType(absPath) {
|
|
3693
4543
|
return new Promise((resolve) => {
|
|
3694
|
-
|
|
4544
|
+
execFile4("file", ["-b", absPath], { timeout: 5e3 }, (err, stdout) => {
|
|
3695
4545
|
resolve(err ? "" : stdout.trim());
|
|
3696
4546
|
});
|
|
3697
4547
|
});
|
|
@@ -3699,7 +4549,7 @@ function fileType(absPath) {
|
|
|
3699
4549
|
async function sampleStrings(absPath, limit = 40) {
|
|
3700
4550
|
let fh;
|
|
3701
4551
|
try {
|
|
3702
|
-
fh = await
|
|
4552
|
+
fh = await fs17.open(absPath, "r");
|
|
3703
4553
|
const buf = Buffer.alloc(STRINGS_SCAN_BYTES);
|
|
3704
4554
|
const { bytesRead } = await fh.read(buf, 0, STRINGS_SCAN_BYTES, 0);
|
|
3705
4555
|
const out = [];
|
|
@@ -3731,7 +4581,7 @@ function registerUploadRoutes(app) {
|
|
|
3731
4581
|
return { error: "Invalid chat id" };
|
|
3732
4582
|
}
|
|
3733
4583
|
const uploadDir = chatUploadsDir(req.params.chatId);
|
|
3734
|
-
await
|
|
4584
|
+
await fs17.mkdir(uploadDir, { recursive: true, mode: 448 });
|
|
3735
4585
|
const results = [];
|
|
3736
4586
|
const parts = req.files();
|
|
3737
4587
|
for await (const part of parts) {
|
|
@@ -3745,24 +4595,24 @@ function registerUploadRoutes(app) {
|
|
|
3745
4595
|
return { error: `Failed to store ${name}` };
|
|
3746
4596
|
}
|
|
3747
4597
|
if (part.file.truncated) {
|
|
3748
|
-
await
|
|
4598
|
+
await fs17.rm(dest, { force: true });
|
|
3749
4599
|
reply.code(413);
|
|
3750
4600
|
return {
|
|
3751
4601
|
error: `${name} exceeds the max upload size (${Math.round(config.maxUploadBytes / 1024 / 1024)} MB)`
|
|
3752
4602
|
};
|
|
3753
4603
|
}
|
|
3754
|
-
const stat = await
|
|
3755
|
-
const ext =
|
|
4604
|
+
const stat = await fs17.stat(dest);
|
|
4605
|
+
const ext = path19.extname(dest);
|
|
3756
4606
|
const kind = classifyKind(dest);
|
|
3757
4607
|
const uploaded = {
|
|
3758
|
-
name:
|
|
4608
|
+
name: path19.basename(dest),
|
|
3759
4609
|
path: dest,
|
|
3760
4610
|
size: stat.size,
|
|
3761
4611
|
mimeType: mimeForExt(ext),
|
|
3762
4612
|
kind
|
|
3763
4613
|
};
|
|
3764
4614
|
if (kind === "binary") {
|
|
3765
|
-
const [type, sha2562,
|
|
4615
|
+
const [type, sha2562, strings2] = await Promise.all([
|
|
3766
4616
|
fileType(dest),
|
|
3767
4617
|
sha256File(dest),
|
|
3768
4618
|
sampleStrings(dest)
|
|
@@ -3770,7 +4620,7 @@ function registerUploadRoutes(app) {
|
|
|
3770
4620
|
uploaded.triage = {
|
|
3771
4621
|
fileType: type || void 0,
|
|
3772
4622
|
sha256: sha2562,
|
|
3773
|
-
strings:
|
|
4623
|
+
strings: strings2.length ? strings2 : void 0
|
|
3774
4624
|
};
|
|
3775
4625
|
}
|
|
3776
4626
|
results.push(uploaded);
|
|
@@ -3792,6 +4642,78 @@ var init_uploads = __esm({
|
|
|
3792
4642
|
}
|
|
3793
4643
|
});
|
|
3794
4644
|
|
|
4645
|
+
// server/src/routes/settings.ts
|
|
4646
|
+
function registerSettingsRoutes(app, manager, startedAt) {
|
|
4647
|
+
app.get("/api/settings", async () => ({
|
|
4648
|
+
defaultAgentId: await configuredDefaultAgent() ?? config.defaultAgent,
|
|
4649
|
+
// The token is left out: it is a secret, and the page has no use for it.
|
|
4650
|
+
server: {
|
|
4651
|
+
version: VERSION,
|
|
4652
|
+
kiroVersion: await kiroVersion(),
|
|
4653
|
+
kiroBin: config.kiroBin,
|
|
4654
|
+
host: config.host,
|
|
4655
|
+
port: config.port,
|
|
4656
|
+
authEnabled: Boolean(config.token),
|
|
4657
|
+
fileRoot: config.fileRoot,
|
|
4658
|
+
defaultCwd: config.defaultCwd,
|
|
4659
|
+
dataDir: config.casperDataDir,
|
|
4660
|
+
configFile: config.configFile,
|
|
4661
|
+
maxLiveSessions: config.maxLiveSessions,
|
|
4662
|
+
liveSessions: manager.liveCount,
|
|
4663
|
+
uptimeMs: Date.now() - startedAt
|
|
4664
|
+
}
|
|
4665
|
+
}));
|
|
4666
|
+
app.put(
|
|
4667
|
+
"/api/settings/defaults",
|
|
4668
|
+
(req, reply) => answer(reply, async () => {
|
|
4669
|
+
const body = req.body ?? {};
|
|
4670
|
+
if (body.agentId !== void 0) {
|
|
4671
|
+
const agentId = String(body.agentId);
|
|
4672
|
+
if (!(await listAgents()).some((a) => a.id === agentId)) throw new Error(`Unknown agent: ${agentId}`);
|
|
4673
|
+
await setDefaultAgent(agentId);
|
|
4674
|
+
}
|
|
4675
|
+
if (body.modelId !== void 0) await setDefaultModel(String(body.modelId), app.log);
|
|
4676
|
+
if (body.effort) await setDefaultEffort(String(body.effort.modelId), String(body.effort.level), app.log);
|
|
4677
|
+
})
|
|
4678
|
+
);
|
|
4679
|
+
app.get("/api/tools", async () => {
|
|
4680
|
+
const [mcpServers, skills] = await Promise.all([listMcpServers(), listSkills()]);
|
|
4681
|
+
return { mcpServers, skills };
|
|
4682
|
+
});
|
|
4683
|
+
app.post(
|
|
4684
|
+
"/api/tools/mcp",
|
|
4685
|
+
(req, reply) => answer(reply, () => addMcpServer(req.body ?? { name: "" }))
|
|
4686
|
+
);
|
|
4687
|
+
app.put(
|
|
4688
|
+
"/api/tools/mcp/:name",
|
|
4689
|
+
(req, reply) => answer(reply, () => setMcpServerDisabled(req.params.name, req.body?.disabled === true))
|
|
4690
|
+
);
|
|
4691
|
+
app.delete(
|
|
4692
|
+
"/api/tools/mcp/:name",
|
|
4693
|
+
(req, reply) => answer(reply, () => removeMcpServer(req.params.name))
|
|
4694
|
+
);
|
|
4695
|
+
}
|
|
4696
|
+
async function answer(reply, fn) {
|
|
4697
|
+
try {
|
|
4698
|
+
await fn();
|
|
4699
|
+
return { ok: true };
|
|
4700
|
+
} catch (err) {
|
|
4701
|
+
reply.code(400);
|
|
4702
|
+
return { error: err.message };
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
var VERSION;
|
|
4706
|
+
var init_settings2 = __esm({
|
|
4707
|
+
"server/src/routes/settings.ts"() {
|
|
4708
|
+
"use strict";
|
|
4709
|
+
init_config();
|
|
4710
|
+
init_agents();
|
|
4711
|
+
init_models();
|
|
4712
|
+
init_kiroTools();
|
|
4713
|
+
VERSION = true ? "0.17.1" : "dev";
|
|
4714
|
+
}
|
|
4715
|
+
});
|
|
4716
|
+
|
|
3795
4717
|
// server/src/routes/health.ts
|
|
3796
4718
|
function registerHealthRoute(app, manager, startedAt) {
|
|
3797
4719
|
app.get("/api/health", async () => {
|
|
@@ -3811,7 +4733,7 @@ var init_health = __esm({
|
|
|
3811
4733
|
});
|
|
3812
4734
|
|
|
3813
4735
|
// server/src/ws/dirWatchers.ts
|
|
3814
|
-
import
|
|
4736
|
+
import fs18 from "node:fs";
|
|
3815
4737
|
function diffWatchSet(current, next) {
|
|
3816
4738
|
const have = new Set(current);
|
|
3817
4739
|
const want = new Set(next.slice(0, MAX_WATCHES));
|
|
@@ -3835,7 +4757,7 @@ function createDirWatchers(opts) {
|
|
|
3835
4757
|
const target = await opts.resolve(relative);
|
|
3836
4758
|
if (!target || closed) return;
|
|
3837
4759
|
try {
|
|
3838
|
-
const watcher =
|
|
4760
|
+
const watcher = fs18.watch(target, { persistent: false }, () => {
|
|
3839
4761
|
const existing = timers.get(relative);
|
|
3840
4762
|
if (existing) clearTimeout(existing);
|
|
3841
4763
|
timers.set(
|
|
@@ -3893,7 +4815,7 @@ function handleConnection(socket, manager, chatId, startCursor) {
|
|
|
3893
4815
|
return null;
|
|
3894
4816
|
}
|
|
3895
4817
|
},
|
|
3896
|
-
onChange: (
|
|
4818
|
+
onChange: (path22) => send(socket, { type: "fs_changed", path: path22 })
|
|
3897
4819
|
});
|
|
3898
4820
|
const forward = (event) => {
|
|
3899
4821
|
if (event.seq <= cursor) return;
|
|
@@ -3944,13 +4866,20 @@ function handleConnection(socket, manager, chatId, startCursor) {
|
|
|
3944
4866
|
case "watch_paths":
|
|
3945
4867
|
return watchers.sync(msg.paths);
|
|
3946
4868
|
case "prompt":
|
|
3947
|
-
return ack("prompt", () => manager.runPrompt(chatId, msg.content, msg.attachments));
|
|
4869
|
+
return ack("prompt", () => manager.runPrompt(chatId, msg.content, msg.attachments, { retry: msg.retry === true }));
|
|
4870
|
+
case "steer":
|
|
4871
|
+
return ack(
|
|
4872
|
+
"steer",
|
|
4873
|
+
() => manager.steer(chatId, String(msg.text ?? ""), typeof msg.clientId === "string" ? msg.clientId : void 0)
|
|
4874
|
+
);
|
|
3948
4875
|
case "cancel":
|
|
3949
4876
|
return ack("cancel", () => manager.cancel(chatId));
|
|
3950
4877
|
case "set_mode":
|
|
3951
4878
|
return ack("set_mode", () => manager.setMode(chatId, msg.modeId));
|
|
3952
4879
|
case "set_model":
|
|
3953
4880
|
return ack("set_model", () => manager.setModel(chatId, msg.modelId));
|
|
4881
|
+
case "set_effort":
|
|
4882
|
+
return ack("set_effort", () => manager.setEffort(chatId, msg.effortLevel));
|
|
3954
4883
|
case "exec_command":
|
|
3955
4884
|
return ack(
|
|
3956
4885
|
"exec_command",
|
|
@@ -4026,7 +4955,7 @@ var init_gateway = __esm({
|
|
|
4026
4955
|
});
|
|
4027
4956
|
|
|
4028
4957
|
// server/src/app.ts
|
|
4029
|
-
import
|
|
4958
|
+
import fs19 from "node:fs";
|
|
4030
4959
|
import Fastify from "fastify";
|
|
4031
4960
|
import websocket from "@fastify/websocket";
|
|
4032
4961
|
import fastifyStatic from "@fastify/static";
|
|
@@ -4045,7 +4974,11 @@ async function buildApp() {
|
|
|
4045
4974
|
transport: isProd ? void 0 : { target: "pino/file", options: { destination: 2 } }
|
|
4046
4975
|
}
|
|
4047
4976
|
});
|
|
4048
|
-
const manager = new SessionManager(logger, {
|
|
4977
|
+
const manager = new SessionManager(logger, {
|
|
4978
|
+
defaultModel: () => defaultModelId(logger),
|
|
4979
|
+
defaultEffort: (modelId) => defaultEffortFor(modelId, logger),
|
|
4980
|
+
defaultAgent: configuredDefaultAgent
|
|
4981
|
+
});
|
|
4049
4982
|
const startedAt = Date.now();
|
|
4050
4983
|
await app.register(websocket, {
|
|
4051
4984
|
options: { maxPayload: 16 * 1024 * 1024 }
|
|
@@ -4056,6 +4989,7 @@ async function buildApp() {
|
|
|
4056
4989
|
await registerAuth(app);
|
|
4057
4990
|
registerHealthRoute(app, manager, startedAt);
|
|
4058
4991
|
registerModelRoutes(app);
|
|
4992
|
+
registerSettingsRoutes(app, manager, startedAt);
|
|
4059
4993
|
registerAgentRoutes(app);
|
|
4060
4994
|
registerFsRoutes(app);
|
|
4061
4995
|
registerChatRoutes(app, manager);
|
|
@@ -4063,20 +4997,20 @@ async function buildApp() {
|
|
|
4063
4997
|
registerWorkspaceRoutes(app, manager);
|
|
4064
4998
|
registerUploadRoutes(app);
|
|
4065
4999
|
registerWsGateway(app, manager);
|
|
4066
|
-
if (
|
|
5000
|
+
if (fs19.existsSync(config.webDist)) {
|
|
4067
5001
|
await app.register(fastifyStatic, {
|
|
4068
5002
|
root: config.webDist
|
|
4069
5003
|
});
|
|
4070
5004
|
app.addHook("onSend", async (req, reply) => {
|
|
4071
|
-
const
|
|
4072
|
-
if (
|
|
5005
|
+
const path22 = req.url.split("?")[0] ?? "";
|
|
5006
|
+
if (path22 === "/manifest.json" || path22.startsWith("/icons/")) {
|
|
4073
5007
|
reply.header("cache-control", "public, max-age=300");
|
|
4074
5008
|
}
|
|
4075
5009
|
});
|
|
4076
5010
|
app.setNotFoundHandler((req, reply) => {
|
|
4077
|
-
const
|
|
4078
|
-
const looksLikeAsset =
|
|
4079
|
-
if (
|
|
5011
|
+
const path22 = req.url.split("?")[0] ?? "";
|
|
5012
|
+
const looksLikeAsset = path22.startsWith("/assets/") || /\.[a-zA-Z0-9]+$/.test(path22);
|
|
5013
|
+
if (path22.startsWith("/api/") || path22.startsWith("/ws") || looksLikeAsset) {
|
|
4080
5014
|
reply.code(404).send({ error: "Not found" });
|
|
4081
5015
|
return;
|
|
4082
5016
|
}
|
|
@@ -4100,6 +5034,8 @@ var init_app = __esm({
|
|
|
4100
5034
|
init_subagents();
|
|
4101
5035
|
init_workspace();
|
|
4102
5036
|
init_uploads();
|
|
5037
|
+
init_settings2();
|
|
5038
|
+
init_kiroTools();
|
|
4103
5039
|
init_health();
|
|
4104
5040
|
init_gateway();
|
|
4105
5041
|
}
|
|
@@ -4154,16 +5090,16 @@ __export(doctor_exports, {
|
|
|
4154
5090
|
mcpWiring: () => mcpWiring
|
|
4155
5091
|
});
|
|
4156
5092
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
4157
|
-
import
|
|
5093
|
+
import fs20 from "node:fs";
|
|
4158
5094
|
import os4 from "node:os";
|
|
4159
|
-
import
|
|
5095
|
+
import path20 from "node:path";
|
|
4160
5096
|
function which(bin) {
|
|
4161
|
-
if (bin.includes("/")) return
|
|
4162
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
5097
|
+
if (bin.includes("/")) return fs20.existsSync(bin) ? bin : void 0;
|
|
5098
|
+
for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
|
|
4163
5099
|
if (dir === "") continue;
|
|
4164
|
-
const candidate =
|
|
5100
|
+
const candidate = path20.join(dir, bin);
|
|
4165
5101
|
try {
|
|
4166
|
-
|
|
5102
|
+
fs20.accessSync(candidate, fs20.constants.X_OK);
|
|
4167
5103
|
return candidate;
|
|
4168
5104
|
} catch {
|
|
4169
5105
|
}
|
|
@@ -4187,7 +5123,7 @@ function mcpWiring(agentJson, resolve) {
|
|
|
4187
5123
|
return { ok: false, detail: `${command || "(no command)"} not found - widgets are unavailable` };
|
|
4188
5124
|
}
|
|
4189
5125
|
const script = args.find((a) => a.endsWith(".js"));
|
|
4190
|
-
if (script && !
|
|
5126
|
+
if (script && !fs20.existsSync(script)) {
|
|
4191
5127
|
return { ok: false, detail: `${script} is gone - delete the agent file and restart to rewrite it` };
|
|
4192
5128
|
}
|
|
4193
5129
|
return { ok: true, detail: script ?? `${command} ${args.join(" ")}` };
|
|
@@ -4239,7 +5175,7 @@ function checks() {
|
|
|
4239
5175
|
const settings = readSettings(config.configFile);
|
|
4240
5176
|
const hasToken = typeof settings.token === "string" && settings.token !== "";
|
|
4241
5177
|
out.push(
|
|
4242
|
-
|
|
5178
|
+
fs20.existsSync(config.configFile) ? {
|
|
4243
5179
|
ok: hasToken,
|
|
4244
5180
|
name: "settings",
|
|
4245
5181
|
detail: hasToken ? `${config.configFile} (token set)` : `${config.configFile} exists but has no token - auth is DISABLED`
|
|
@@ -4249,8 +5185,8 @@ function checks() {
|
|
|
4249
5185
|
detail: `${config.configFile} not found; a token is generated on first run`
|
|
4250
5186
|
}
|
|
4251
5187
|
);
|
|
4252
|
-
if (
|
|
4253
|
-
const mode =
|
|
5188
|
+
if (fs20.existsSync(config.configFile)) {
|
|
5189
|
+
const mode = fs20.statSync(config.configFile).mode & 511;
|
|
4254
5190
|
out.push({
|
|
4255
5191
|
ok: mode === 384,
|
|
4256
5192
|
name: "settings mode",
|
|
@@ -4258,10 +5194,10 @@ function checks() {
|
|
|
4258
5194
|
});
|
|
4259
5195
|
}
|
|
4260
5196
|
try {
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
const db2 =
|
|
4264
|
-
const size =
|
|
5197
|
+
fs20.mkdirSync(config.casperDataDir, { recursive: true });
|
|
5198
|
+
fs20.accessSync(config.casperDataDir, fs20.constants.W_OK);
|
|
5199
|
+
const db2 = path20.join(config.casperDataDir, "casper.db");
|
|
5200
|
+
const size = fs20.existsSync(db2) ? `, casper.db ${(fs20.statSync(db2).size / 1024).toFixed(0)} KB` : "";
|
|
4265
5201
|
out.push({ ok: true, name: "data dir", detail: `${config.casperDataDir} writable${size}` });
|
|
4266
5202
|
} catch (err) {
|
|
4267
5203
|
out.push({
|
|
@@ -4272,22 +5208,22 @@ function checks() {
|
|
|
4272
5208
|
});
|
|
4273
5209
|
}
|
|
4274
5210
|
out.push(
|
|
4275
|
-
|
|
5211
|
+
fs20.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
|
|
4276
5212
|
ok: false,
|
|
4277
5213
|
fatal: true,
|
|
4278
5214
|
name: "web app",
|
|
4279
5215
|
detail: `${config.webDist} not found - the package is incomplete or CASPER_WEB_DIST is wrong`
|
|
4280
5216
|
}
|
|
4281
5217
|
);
|
|
4282
|
-
const agent =
|
|
4283
|
-
const hasAgent =
|
|
5218
|
+
const agent = path20.join(os4.homedir(), ".kiro", "agents", "casper.json");
|
|
5219
|
+
const hasAgent = fs20.existsSync(agent);
|
|
4284
5220
|
out.push({
|
|
4285
5221
|
ok: hasAgent,
|
|
4286
5222
|
name: "casper agent",
|
|
4287
5223
|
detail: hasAgent ? agent : `${agent} missing - it is installed on first run`
|
|
4288
5224
|
});
|
|
4289
5225
|
if (hasAgent) {
|
|
4290
|
-
const json =
|
|
5226
|
+
const json = fs20.readFileSync(agent, "utf8");
|
|
4291
5227
|
const format = agentFormat(json);
|
|
4292
5228
|
out.push({ ok: format.ok, name: "agent format", detail: format.detail });
|
|
4293
5229
|
const wiring = mcpWiring(json, which);
|
|
@@ -4603,7 +5539,7 @@ function handleMessage(req, version) {
|
|
|
4603
5539
|
return ok(id, {
|
|
4604
5540
|
protocolVersion: asked ?? DEFAULT_PROTOCOL_VERSION,
|
|
4605
5541
|
capabilities: { tools: { listChanged: false } },
|
|
4606
|
-
serverInfo: { name:
|
|
5542
|
+
serverInfo: { name: SERVER_NAME2, version }
|
|
4607
5543
|
});
|
|
4608
5544
|
}
|
|
4609
5545
|
case "ping":
|
|
@@ -4623,14 +5559,14 @@ function handleMessage(req, version) {
|
|
|
4623
5559
|
return { jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown method: ${method}` } };
|
|
4624
5560
|
}
|
|
4625
5561
|
}
|
|
4626
|
-
var DEFAULT_PROTOCOL_VERSION,
|
|
5562
|
+
var DEFAULT_PROTOCOL_VERSION, SERVER_NAME2, MAX_WIDGET_CHARS, TOOLS;
|
|
4627
5563
|
var init_protocol = __esm({
|
|
4628
5564
|
"server/src/mcp/protocol.ts"() {
|
|
4629
5565
|
"use strict";
|
|
4630
5566
|
init_guidelines();
|
|
4631
5567
|
init_choice();
|
|
4632
5568
|
DEFAULT_PROTOCOL_VERSION = "2025-06-18";
|
|
4633
|
-
|
|
5569
|
+
SERVER_NAME2 = "casper-generative-ui";
|
|
4634
5570
|
MAX_WIDGET_CHARS = 256 * 1024;
|
|
4635
5571
|
TOOLS = [
|
|
4636
5572
|
{
|
|
@@ -4731,7 +5667,7 @@ function runMcpServer() {
|
|
|
4731
5667
|
return;
|
|
4732
5668
|
}
|
|
4733
5669
|
try {
|
|
4734
|
-
const res = handleMessage(req,
|
|
5670
|
+
const res = handleMessage(req, VERSION2);
|
|
4735
5671
|
if (res) process.stdout.write(JSON.stringify(res) + "\n");
|
|
4736
5672
|
} catch (err) {
|
|
4737
5673
|
process.stderr.write(`casper-mcp: ${err.message}
|
|
@@ -4749,19 +5685,19 @@ function runMcpServer() {
|
|
|
4749
5685
|
});
|
|
4750
5686
|
rl.on("close", () => process.exit(0));
|
|
4751
5687
|
}
|
|
4752
|
-
var
|
|
5688
|
+
var VERSION2;
|
|
4753
5689
|
var init_server2 = __esm({
|
|
4754
5690
|
"server/src/mcp/server.ts"() {
|
|
4755
5691
|
"use strict";
|
|
4756
5692
|
init_protocol();
|
|
4757
|
-
|
|
5693
|
+
VERSION2 = true ? "0.17.1" : "dev";
|
|
4758
5694
|
}
|
|
4759
5695
|
});
|
|
4760
5696
|
|
|
4761
5697
|
// server/src/cli/program.ts
|
|
4762
|
-
import
|
|
5698
|
+
import fs21 from "node:fs";
|
|
4763
5699
|
import os5 from "node:os";
|
|
4764
|
-
import
|
|
5700
|
+
import path21 from "node:path";
|
|
4765
5701
|
|
|
4766
5702
|
// server/node_modules/commander/lib/error.js
|
|
4767
5703
|
var CommanderError = class extends Error {
|
|
@@ -7874,9 +8810,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
7874
8810
|
* @param {string} [path]
|
|
7875
8811
|
* @return {(string|null|Command)}
|
|
7876
8812
|
*/
|
|
7877
|
-
executableDir(
|
|
7878
|
-
if (
|
|
7879
|
-
this._executableDir =
|
|
8813
|
+
executableDir(path22) {
|
|
8814
|
+
if (path22 === void 0) return this._executableDir;
|
|
8815
|
+
this._executableDir = path22;
|
|
7880
8816
|
return this;
|
|
7881
8817
|
}
|
|
7882
8818
|
/**
|
|
@@ -8134,7 +9070,7 @@ var program = new Command();
|
|
|
8134
9070
|
// server/src/cli/program.ts
|
|
8135
9071
|
init_paths();
|
|
8136
9072
|
init_settings();
|
|
8137
|
-
var
|
|
9073
|
+
var VERSION3 = true ? "0.17.1" : "dev";
|
|
8138
9074
|
async function bootstrap() {
|
|
8139
9075
|
const file = configFilePath();
|
|
8140
9076
|
const settings = readSettings(file);
|
|
@@ -8180,8 +9116,8 @@ async function resetToken(value) {
|
|
|
8180
9116
|
updateSettings(file, { token });
|
|
8181
9117
|
process.stdout.write(`wrote a new token to ${file}
|
|
8182
9118
|
`);
|
|
8183
|
-
const dbFile =
|
|
8184
|
-
if (
|
|
9119
|
+
const dbFile = path21.join(dataDirPath(), "casper.db");
|
|
9120
|
+
if (fs21.existsSync(dbFile)) {
|
|
8185
9121
|
const { LoginStore: LoginStore2 } = await Promise.resolve().then(() => (init_logins(), logins_exports));
|
|
8186
9122
|
new LoginStore2().revokeAll();
|
|
8187
9123
|
process.stdout.write("revoked all device sessions\n");
|
|
@@ -8198,7 +9134,7 @@ async function resetToken(value) {
|
|
|
8198
9134
|
}
|
|
8199
9135
|
function buildProgram() {
|
|
8200
9136
|
const program2 = new Command();
|
|
8201
|
-
program2.name("casper").description("Web client for kiro-cli over the Agent Client Protocol").version(
|
|
9137
|
+
program2.name("casper").description("Web client for kiro-cli over the Agent Client Protocol").version(VERSION3, "-v, --version").showHelpAfterError().addHelpText("after", `
|
|
8202
9138
|
Settings live in ${configFilePath()}.
|
|
8203
9139
|
Update with: npm install -g @joeyshi12/casper`);
|
|
8204
9140
|
program2.command("start").description("run the server in the foreground").action(async () => {
|