@joeyshi12/casper 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +39 -108
  2. package/dist/mcp.js +1 -1
  3. package/dist/server.js +1092 -156
  4. package/dist/web/assets/{abnfDiagram-N423BO3Z-DQdd062N.js → abnfDiagram-N423BO3Z-Bo2zpD_r.js} +1 -1
  5. package/dist/web/assets/{arc-WhOJ_KR1.js → arc-eMfj-v5r.js} +1 -1
  6. package/dist/web/assets/{architectureDiagram-T3A2C74G-3zZgBQZT.js → architectureDiagram-T3A2C74G-SMny_PQB.js} +1 -1
  7. package/dist/web/assets/{blockDiagram-VBNYF7ZC-CW9mWWLl.js → blockDiagram-VBNYF7ZC-DSEqDPwv.js} +1 -1
  8. package/dist/web/assets/{c4Diagram-5PPSVZJV-xXmfR_aG.js → c4Diagram-5PPSVZJV-CpqtaVod.js} +1 -1
  9. package/dist/web/assets/channel-BsxsiUCc.js +1 -0
  10. package/dist/web/assets/{chunk-2GRJ4B5K-CuCN8EG_.js → chunk-2GRJ4B5K-AuFfmAbN.js} +1 -1
  11. package/dist/web/assets/{chunk-2Q5K7J3B-BC-h61jv.js → chunk-2Q5K7J3B-iOn5T_gD.js} +1 -1
  12. package/dist/web/assets/{chunk-5RXB4S5H-BqZ4ICSN.js → chunk-5RXB4S5H-4T8n_Vjj.js} +1 -1
  13. package/dist/web/assets/{chunk-5VM5RSS4-_EcX9Eg6.js → chunk-5VM5RSS4-BXZGGB8b.js} +1 -1
  14. package/dist/web/assets/{chunk-6Q2QTUOP-Rjg4xt7f.js → chunk-6Q2QTUOP-CaHKdpZm.js} +1 -1
  15. package/dist/web/assets/{chunk-GF5L2VYU-CllNzjfD.js → chunk-GF5L2VYU-JzGg4vgu.js} +1 -1
  16. package/dist/web/assets/{chunk-JWPE2WC7-Cc24s09e.js → chunk-JWPE2WC7-CsXwzKxD.js} +1 -1
  17. package/dist/web/assets/{chunk-KBJHAD2P-DYClY3b3.js → chunk-KBJHAD2P-4iRbBEYU.js} +1 -1
  18. package/dist/web/assets/{chunk-RYQCIY6F-C5PSe7M_.js → chunk-RYQCIY6F-D-VbfyMB.js} +1 -1
  19. package/dist/web/assets/{chunk-XXDRQBXY-DK8-XmXo.js → chunk-XXDRQBXY-CeFbm4g1.js} +1 -1
  20. package/dist/web/assets/classDiagram-JCYQIIEL-CRUWBeef.js +1 -0
  21. package/dist/web/assets/classDiagram-v2-OCEON4UE-CRUWBeef.js +1 -0
  22. package/dist/web/assets/{cose-bilkent-JH36ORCC-Dxs4GA2q.js → cose-bilkent-JH36ORCC-CYNfxa8A.js} +1 -1
  23. package/dist/web/assets/{cynefin-VYW2F7L2-kzjViLcF.js → cynefin-VYW2F7L2-rvO1KlAd.js} +1 -1
  24. package/dist/web/assets/{cynefinDiagram-MW4NZA55-B9zBzt3B.js → cynefinDiagram-MW4NZA55-DvzcG3NL.js} +1 -1
  25. package/dist/web/assets/{dagre-VZM6K2ZE-BNwMkCZi.js → dagre-VZM6K2ZE-BgutGA8k.js} +1 -1
  26. package/dist/web/assets/{diagram-7IWD3JNH-DpIoQsS7.js → diagram-7IWD3JNH-BoLPEgBs.js} +1 -1
  27. package/dist/web/assets/{diagram-B4RE2ZJO-BKWdzhKi.js → diagram-B4RE2ZJO-BaaHgQLy.js} +1 -1
  28. package/dist/web/assets/{diagram-LBJQPF4R-DBbKFigX.js → diagram-LBJQPF4R-Bc7jkgiW.js} +1 -1
  29. package/dist/web/assets/{diagram-Q27KOJAE-B7qLPRw9.js → diagram-Q27KOJAE-p311g83v.js} +1 -1
  30. package/dist/web/assets/{diagram-UB23O5K3-DVncy6q5.js → diagram-UB23O5K3-ALwvcWzK.js} +1 -1
  31. package/dist/web/assets/{ebnfDiagram-BXEA7PRR-B9rA3ylb.js → ebnfDiagram-BXEA7PRR-DveTtKRe.js} +1 -1
  32. package/dist/web/assets/{erDiagram-JOGREHBK-BExDcOc7.js → erDiagram-JOGREHBK-D3kM5uf0.js} +1 -1
  33. package/dist/web/assets/{flowDiagram-UKHOOZJN-B1xPLh9u.js → flowDiagram-UKHOOZJN-h_YgCdts.js} +1 -1
  34. package/dist/web/assets/{ganttDiagram-PKOTCBZU-BYS5NkM6.js → ganttDiagram-PKOTCBZU-DpMVjZz3.js} +1 -1
  35. package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-CN4An7lt.js → gitGraphDiagram-DS77QQ5N-OJ0-BsVV.js} +1 -1
  36. package/dist/web/assets/index-BYjBvJ2T.css +1 -0
  37. package/dist/web/assets/index-C0MiltSe.js +245 -0
  38. package/dist/web/assets/{index-BuQTY976.js → index-DHJZ0QFt.js} +1 -1
  39. package/dist/web/assets/{infoDiagram-6WML65LV-sk97M0G1.js → infoDiagram-6WML65LV-C3dDMfD2.js} +1 -1
  40. package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-BzW2rVh6.js → ishikawaDiagram-WSZJBQD7-QSccwWZc.js} +1 -1
  41. package/dist/web/assets/{journeyDiagram-NVQOT4AX-iaCo98OS.js → journeyDiagram-NVQOT4AX-BBse3ziF.js} +1 -1
  42. package/dist/web/assets/{kanban-definition-27J2QSJJ-Ce38xzYm.js → kanban-definition-27J2QSJJ-DrbZwvww.js} +1 -1
  43. package/dist/web/assets/{linear-BoUX9N6W.js → linear-DSPm6cs7.js} +1 -1
  44. package/dist/web/assets/{mermaid.core-CC54dZcH.js → mermaid.core-DXBmt4hx.js} +4 -4
  45. package/dist/web/assets/{mindmap-definition-FAOFIHXS-BxRXpNJ-.js → mindmap-definition-FAOFIHXS-DnF7VGZ2.js} +1 -1
  46. package/dist/web/assets/{pegDiagram-VL7TDLO6-Cg0-J9c8.js → pegDiagram-VL7TDLO6-DU79M4ax.js} +1 -1
  47. package/dist/web/assets/{pieDiagram-7S7Q4E2Y-DUjXAJq4.js → pieDiagram-7S7Q4E2Y-D_kGXiwY.js} +1 -1
  48. package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-q1HsW0pP.js → quadrantDiagram-CIZ2JOQS-Sy59mKJK.js} +1 -1
  49. package/dist/web/assets/{railroadDiagram-AXF67PYL-ats1_veK.js → railroadDiagram-AXF67PYL-i-7HN_g3.js} +1 -1
  50. package/dist/web/assets/{requirementDiagram-LRYGKXZP-CV2_uFpI.js → requirementDiagram-LRYGKXZP-MbXlWZ5N.js} +1 -1
  51. package/dist/web/assets/{sankeyDiagram-W5VNT64P-BwyC8qNn.js → sankeyDiagram-W5VNT64P-BhU2pRtd.js} +1 -1
  52. package/dist/web/assets/{sequenceDiagram-SI44F4Z6-72yj0uCA.js → sequenceDiagram-SI44F4Z6-ju70uzxa.js} +1 -1
  53. package/dist/web/assets/{sizeCapture-X5ZJPWSS-CV7PeUrN.js → sizeCapture-X5ZJPWSS-Oy3UrbXL.js} +1 -1
  54. package/dist/web/assets/{stateDiagram-OKZ733FA-B08_WgjO.js → stateDiagram-OKZ733FA-DzSL7BOW.js} +1 -1
  55. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-TGjHDp18.js +1 -0
  56. package/dist/web/assets/{swimlanes-SLNWSIFB-DnBKpTQH.js → swimlanes-SLNWSIFB-a_3Pai9c.js} +2 -2
  57. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-Dq3BU0bp.js +8 -0
  58. package/dist/web/assets/{timeline-definition-Z64GVDOM-BAoT5Fu8.js → timeline-definition-Z64GVDOM-CdYiXKKc.js} +1 -1
  59. package/dist/web/assets/{vennDiagram-T6HMQDX7-VLzfwvza.js → vennDiagram-T6HMQDX7-CNquHYyi.js} +1 -1
  60. package/dist/web/assets/{wardleyDiagram-T6FBY63Y-QaCRHB2e.js → wardleyDiagram-T6FBY63Y-D-dCSbF5.js} +1 -1
  61. package/dist/web/assets/{xychartDiagram-ELKLHX3M-DCUP6k1x.js → xychartDiagram-ELKLHX3M-BZCQNI1R.js} +1 -1
  62. package/dist/web/index.html +2 -2
  63. package/dist/web/sw.js +1 -1
  64. package/package.json +1 -1
  65. package/dist/web/assets/channel-QybFlytR.js +0 -1
  66. package/dist/web/assets/classDiagram-JCYQIIEL-CcJ6l3H0.js +0 -1
  67. package/dist/web/assets/classDiagram-v2-OCEON4UE-CcJ6l3H0.js +0 -1
  68. package/dist/web/assets/index-BwBYAAwi.js +0 -244
  69. package/dist/web/assets/index-CRxbcY2R.css +0 -1
  70. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-80B2GltS.js +0 -1
  71. 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 contextUsageOf(line) {
1554
- if (!line.includes('"contextUsage"')) return void 0;
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 p = JSON.parse(line).payload;
1704
+ const l = JSON.parse(line);
1705
+ const p = l.payload;
1557
1706
  const value = isRecord(p.value) ? p.value.usagePercentage : void 0;
1558
- return p.type === "session_metadata" && p.key === "contextUsage" && typeof value === "number" ? value : void 0;
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 lastContextUsage(file) {
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 void 0;
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
- return tail ? contextUsageOf(tail) : void 0;
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
- updatedAt: iso(j.lastModifiedAt) ?? createdAt,
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
- contextUsagePercentage: await lastContextUsage(path10.join(dir, "messages.jsonl"))
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 readEntries(sessionId)).filter((e) => e.kind === "prompt").length;
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("SELECT chat_id, session_id, title, cwd FROM chats").all().map((r) => ({ chatId: r.chat_id, sessionId: r.session_id, title: r.title, cwd: r.cwd }));
2127
+ return db().prepare(`SELECT ${COLUMNS} FROM chats`).all().map(rowOf);
1907
2128
  }
1908
2129
  get(chatId) {
1909
- const r = db().prepare("SELECT chat_id, session_id, title, cwd FROM chats WHERE chat_id = ?").get(chatId);
1910
- return r ? { chatId: r.chat_id, sessionId: r.session_id, title: r.title, cwd: r.cwd } : void 0;
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
- return typeof info.usagePercentage === "number" ? { kind: "metadata", params: { sessionId, contextUsagePercentage: info.usagePercentage } } : null;
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 sessionModel(res) {
2060
- return res.configOptions?.find((o) => o.id === "model")?.currentValue;
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
- if (s.replaying) return;
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
- s.modelId = sessionModel(res) ?? s.modelId;
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 tempId = `pending-${Date.now()}-${Math.floor(this.sessions.size)}`;
2420
- const store = new EventStore(chatId);
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 = opts.agentId ?? config.defaultAgent;
2423
- s.currentModeId = s.agentId;
2424
- s.modelId = opts.modelId ?? await this.defaultModel();
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.evict(s.sessionId);
2835
+ this.sessions.delete(s.sessionId);
2836
+ s.proc?.dispose();
2430
2837
  throw err;
2431
2838
  }
2432
- this.store.create(chatId);
2433
- this.store.bindSession(chatId, s.sessionId);
2434
- const name = sanitizeTitle(opts.title ?? "") || (opts.freshWorkspace ? "" : path12.basename(s.cwd));
2435
- if (name) {
2436
- this.store.setTitle(chatId, name);
2437
- s.title = name;
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
- return this.buildDetail(this.store.get(chatId), s, []);
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
- async runPrompt(chatId, content, attachments) {
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 (!this.store.getTitle(chatId)) {
2455
- const title = titleFromPrompt(content);
2456
- if (title) {
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.record({ kind: "turn_ended", stopReason: res.stopReason });
2472
- }).catch((err) => {
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
- kiroTitle: persisted?.title || live?.title,
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 deletePersistedSession(sessionId);
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) return 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 fs12 from "node:fs/promises";
2988
- import path13 from "node:path";
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 fs12.stat(real);
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 fs12.stat(cwd)).isDirectory();
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 = path13.join(parentReal, entry.name);
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 fs12.stat(real);
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 path14 from "node:path";
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 = path14.extname(name).toLowerCase();
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 fs13 from "node:fs/promises";
4012
+ import fs14 from "node:fs/promises";
3220
4013
  import { createReadStream } from "node:fs";
3221
- import path15 from "node:path";
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 = path15.extname(realTarget).toLowerCase();
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 fs13.open(realTarget, "r");
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 fs14 from "node:fs/promises";
4130
+ import fs15 from "node:fs/promises";
3338
4131
  import { createReadStream as createReadStream2 } from "node:fs";
3339
- import path16 from "node:path";
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 ? path16.resolve(base, input) : base;
3348
- const dir = endsWithSep || !input ? resolved : path16.dirname(resolved);
3349
- const prefix = endsWithSep || !input ? "" : path16.basename(resolved);
3350
- const targetKind = await fs14.stat(resolved).then((s) => s.isDirectory() ? "directory" : "file").catch(() => "missing");
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 fs14.readdir(realDir, { withFileTypes: true });
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) => path16.join(dir, 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 (!path16.isAbsolute(filePath)) {
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="${path16.basename(resolved.real).replace(/"/g, "")}"`
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 fs15 from "node:fs/promises";
4416
+ import fs16 from "node:fs/promises";
3567
4417
  import { createReadStream as createReadStream3 } from "node:fs";
3568
- import path17 from "node:path";
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 fs15.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
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 fs15.stat(target.real);
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 = path17.extname(realTarget);
3627
- const filename = path17.basename(realTarget);
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 fs16 from "node:fs/promises";
3663
- import path18 from "node:path";
4512
+ import fs17 from "node:fs/promises";
4513
+ import path19 from "node:path";
3664
4514
  import crypto4 from "node:crypto";
3665
- import { execFile as execFile3 } from "node:child_process";
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 = path18.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
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 = path18.extname(name);
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 fs16.access(path18.join(dir, candidate));
4529
+ await fs17.access(path19.join(dir, candidate));
3680
4530
  candidate = `${stem} (${n++})${ext}`;
3681
4531
  } catch {
3682
- return path18.join(dir, candidate);
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
- execFile3("file", ["-b", absPath], { timeout: 5e3 }, (err, stdout) => {
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 fs16.open(absPath, "r");
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 fs16.mkdir(uploadDir, { recursive: true, mode: 448 });
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 fs16.rm(dest, { force: true });
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 fs16.stat(dest);
3755
- const ext = path18.extname(dest);
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: path18.basename(dest),
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, strings] = await Promise.all([
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: strings.length ? strings : void 0
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.0" : "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 fs17 from "node:fs";
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 = fs17.watch(target, { persistent: false }, () => {
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: (path21) => send(socket, { type: "fs_changed", path: path21 })
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 fs18 from "node:fs";
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, { defaultModel: () => defaultModelId(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 (fs18.existsSync(config.webDist)) {
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 path21 = req.url.split("?")[0] ?? "";
4072
- if (path21 === "/manifest.json" || path21.startsWith("/icons/")) {
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 path21 = req.url.split("?")[0] ?? "";
4078
- const looksLikeAsset = path21.startsWith("/assets/") || /\.[a-zA-Z0-9]+$/.test(path21);
4079
- if (path21.startsWith("/api/") || path21.startsWith("/ws") || looksLikeAsset) {
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 fs19 from "node:fs";
5093
+ import fs20 from "node:fs";
4158
5094
  import os4 from "node:os";
4159
- import path19 from "node:path";
5095
+ import path20 from "node:path";
4160
5096
  function which(bin) {
4161
- if (bin.includes("/")) return fs19.existsSync(bin) ? bin : void 0;
4162
- for (const dir of (process.env.PATH ?? "").split(path19.delimiter)) {
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 = path19.join(dir, bin);
5100
+ const candidate = path20.join(dir, bin);
4165
5101
  try {
4166
- fs19.accessSync(candidate, fs19.constants.X_OK);
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 && !fs19.existsSync(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
- fs19.existsSync(config.configFile) ? {
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 (fs19.existsSync(config.configFile)) {
4253
- const mode = fs19.statSync(config.configFile).mode & 511;
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
- fs19.mkdirSync(config.casperDataDir, { recursive: true });
4262
- fs19.accessSync(config.casperDataDir, fs19.constants.W_OK);
4263
- const db2 = path19.join(config.casperDataDir, "casper.db");
4264
- const size = fs19.existsSync(db2) ? `, casper.db ${(fs19.statSync(db2).size / 1024).toFixed(0)} KB` : "";
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
- fs19.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
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 = path19.join(os4.homedir(), ".kiro", "agents", "casper.json");
4283
- const hasAgent = fs19.existsSync(agent);
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 = fs19.readFileSync(agent, "utf8");
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: SERVER_NAME, version }
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, SERVER_NAME, MAX_WIDGET_CHARS, TOOLS;
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
- SERVER_NAME = "casper-generative-ui";
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, VERSION);
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 VERSION;
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
- VERSION = true ? "0.16.0" : "dev";
5693
+ VERSION2 = true ? "0.17.0" : "dev";
4758
5694
  }
4759
5695
  });
4760
5696
 
4761
5697
  // server/src/cli/program.ts
4762
- import fs20 from "node:fs";
5698
+ import fs21 from "node:fs";
4763
5699
  import os5 from "node:os";
4764
- import path20 from "node:path";
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(path21) {
7878
- if (path21 === void 0) return this._executableDir;
7879
- this._executableDir = path21;
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 VERSION2 = true ? "0.16.0" : "dev";
9073
+ var VERSION3 = true ? "0.17.0" : "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 = path20.join(dataDirPath(), "casper.db");
8184
- if (fs20.existsSync(dbFile)) {
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(VERSION2, "-v, --version").showHelpAfterError().addHelpText("after", `
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 () => {