@joeyshi12/casper 0.15.7 → 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 -104
  2. package/dist/mcp.js +1 -1
  3. package/dist/server.js +1807 -774
  4. package/dist/web/assets/{abnfDiagram-N423BO3Z-jvSYyvNw.js → abnfDiagram-N423BO3Z-Bo2zpD_r.js} +1 -1
  5. package/dist/web/assets/{arc-BiHYHdUQ.js → arc-eMfj-v5r.js} +1 -1
  6. package/dist/web/assets/{architectureDiagram-T3A2C74G-4Eqc-u3C.js → architectureDiagram-T3A2C74G-SMny_PQB.js} +1 -1
  7. package/dist/web/assets/{blockDiagram-VBNYF7ZC-BcOcmlBN.js → blockDiagram-VBNYF7ZC-DSEqDPwv.js} +1 -1
  8. package/dist/web/assets/{c4Diagram-5PPSVZJV-BUudyYqU.js → c4Diagram-5PPSVZJV-CpqtaVod.js} +1 -1
  9. package/dist/web/assets/channel-BsxsiUCc.js +1 -0
  10. package/dist/web/assets/{chunk-2GRJ4B5K-bTMhoJSQ.js → chunk-2GRJ4B5K-AuFfmAbN.js} +1 -1
  11. package/dist/web/assets/{chunk-2Q5K7J3B-BYp6dfdp.js → chunk-2Q5K7J3B-iOn5T_gD.js} +1 -1
  12. package/dist/web/assets/{chunk-5RXB4S5H-Dg3ASlI5.js → chunk-5RXB4S5H-4T8n_Vjj.js} +1 -1
  13. package/dist/web/assets/{chunk-5VM5RSS4-B7uwpCHf.js → chunk-5VM5RSS4-BXZGGB8b.js} +1 -1
  14. package/dist/web/assets/{chunk-6Q2QTUOP-DVTiWdGW.js → chunk-6Q2QTUOP-CaHKdpZm.js} +1 -1
  15. package/dist/web/assets/{chunk-GF5L2VYU-C2LdpbyU.js → chunk-GF5L2VYU-JzGg4vgu.js} +1 -1
  16. package/dist/web/assets/{chunk-JWPE2WC7-CMX4-S8P.js → chunk-JWPE2WC7-CsXwzKxD.js} +1 -1
  17. package/dist/web/assets/{chunk-KBJHAD2P-D92STFuF.js → chunk-KBJHAD2P-4iRbBEYU.js} +1 -1
  18. package/dist/web/assets/{chunk-RYQCIY6F-B2PLxj_Q.js → chunk-RYQCIY6F-D-VbfyMB.js} +1 -1
  19. package/dist/web/assets/{chunk-XXDRQBXY-DCpI_0Ix.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-KtY-8vxF.js → cose-bilkent-JH36ORCC-CYNfxa8A.js} +1 -1
  23. package/dist/web/assets/{cynefin-VYW2F7L2-DkTsGV_4.js → cynefin-VYW2F7L2-rvO1KlAd.js} +1 -1
  24. package/dist/web/assets/{cynefinDiagram-MW4NZA55-BVPMHgGu.js → cynefinDiagram-MW4NZA55-DvzcG3NL.js} +1 -1
  25. package/dist/web/assets/{dagre-VZM6K2ZE-DT-S1NOU.js → dagre-VZM6K2ZE-BgutGA8k.js} +1 -1
  26. package/dist/web/assets/{diagram-7IWD3JNH-DUJvV7wV.js → diagram-7IWD3JNH-BoLPEgBs.js} +1 -1
  27. package/dist/web/assets/{diagram-B4RE2ZJO-DmpnY7ob.js → diagram-B4RE2ZJO-BaaHgQLy.js} +1 -1
  28. package/dist/web/assets/{diagram-LBJQPF4R-nEMZNwen.js → diagram-LBJQPF4R-Bc7jkgiW.js} +1 -1
  29. package/dist/web/assets/{diagram-Q27KOJAE-Dr-59ElI.js → diagram-Q27KOJAE-p311g83v.js} +1 -1
  30. package/dist/web/assets/{diagram-UB23O5K3-4wvmRauY.js → diagram-UB23O5K3-ALwvcWzK.js} +1 -1
  31. package/dist/web/assets/{ebnfDiagram-BXEA7PRR-055xAlHi.js → ebnfDiagram-BXEA7PRR-DveTtKRe.js} +1 -1
  32. package/dist/web/assets/{erDiagram-JOGREHBK-DqWTDG8u.js → erDiagram-JOGREHBK-D3kM5uf0.js} +1 -1
  33. package/dist/web/assets/{flowDiagram-UKHOOZJN-D3lUh0yb.js → flowDiagram-UKHOOZJN-h_YgCdts.js} +1 -1
  34. package/dist/web/assets/{ganttDiagram-PKOTCBZU-aV5l2zrT.js → ganttDiagram-PKOTCBZU-DpMVjZz3.js} +1 -1
  35. package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-BK2KCI35.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-Dv1My9Ob.js → index-DHJZ0QFt.js} +1 -1
  39. package/dist/web/assets/{infoDiagram-6WML65LV-BcSXYnWm.js → infoDiagram-6WML65LV-C3dDMfD2.js} +1 -1
  40. package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-C0LFAfcz.js → ishikawaDiagram-WSZJBQD7-QSccwWZc.js} +1 -1
  41. package/dist/web/assets/{journeyDiagram-NVQOT4AX-1Zs22Gpd.js → journeyDiagram-NVQOT4AX-BBse3ziF.js} +1 -1
  42. package/dist/web/assets/{kanban-definition-27J2QSJJ-DW6RwD7S.js → kanban-definition-27J2QSJJ-DrbZwvww.js} +1 -1
  43. package/dist/web/assets/{linear-Cfzhrbed.js → linear-DSPm6cs7.js} +1 -1
  44. package/dist/web/assets/{mermaid.core-D0VPssrU.js → mermaid.core-DXBmt4hx.js} +4 -4
  45. package/dist/web/assets/{mindmap-definition-FAOFIHXS-BeD5KiWO.js → mindmap-definition-FAOFIHXS-DnF7VGZ2.js} +1 -1
  46. package/dist/web/assets/{pegDiagram-VL7TDLO6-JDLyI4Gc.js → pegDiagram-VL7TDLO6-DU79M4ax.js} +1 -1
  47. package/dist/web/assets/{pieDiagram-7S7Q4E2Y-BY3XpjEU.js → pieDiagram-7S7Q4E2Y-D_kGXiwY.js} +1 -1
  48. package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-C4oT4Lqc.js → quadrantDiagram-CIZ2JOQS-Sy59mKJK.js} +1 -1
  49. package/dist/web/assets/{railroadDiagram-AXF67PYL-lfzvVyK7.js → railroadDiagram-AXF67PYL-i-7HN_g3.js} +1 -1
  50. package/dist/web/assets/{requirementDiagram-LRYGKXZP-wGnOD5SS.js → requirementDiagram-LRYGKXZP-MbXlWZ5N.js} +1 -1
  51. package/dist/web/assets/{sankeyDiagram-W5VNT64P-DJQ2VW0G.js → sankeyDiagram-W5VNT64P-BhU2pRtd.js} +1 -1
  52. package/dist/web/assets/{sequenceDiagram-SI44F4Z6-Bz_rjvWZ.js → sequenceDiagram-SI44F4Z6-ju70uzxa.js} +1 -1
  53. package/dist/web/assets/{sizeCapture-X5ZJPWSS-BgxIA36S.js → sizeCapture-X5ZJPWSS-Oy3UrbXL.js} +1 -1
  54. package/dist/web/assets/{stateDiagram-OKZ733FA-DpL_B9Mu.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-B6uTv46X.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-DvMUcNNs.js → timeline-definition-Z64GVDOM-CdYiXKKc.js} +1 -1
  59. package/dist/web/assets/{vennDiagram-T6HMQDX7-K_9U-J2A.js → vennDiagram-T6HMQDX7-CNquHYyi.js} +1 -1
  60. package/dist/web/assets/{wardleyDiagram-T6FBY63Y-DwMi4kCd.js → wardleyDiagram-T6FBY63Y-D-dCSbF5.js} +1 -1
  61. package/dist/web/assets/{xychartDiagram-ELKLHX3M-BkcnShpk.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-BueZNEdT.js +0 -1
  66. package/dist/web/assets/classDiagram-JCYQIIEL-CSEqsvyn.js +0 -1
  67. package/dist/web/assets/classDiagram-v2-OCEON4UE-CSEqsvyn.js +0 -1
  68. package/dist/web/assets/index-B5ZDbS-R.js +0 -244
  69. package/dist/web/assets/index-CRxbcY2R.css +0 -1
  70. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-DsD9j9Fu.js +0 -1
  71. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-BxNKQaPC.js +0 -8
package/dist/server.js CHANGED
@@ -118,13 +118,9 @@ function agentConfig(prompt, mcp) {
118
118
  casper: mcp ? { command: process.execPath, args: [mcp], env: {}, timeout: 1e4 } : { command: "casper", args: ["mcp"], env: {}, timeout: 1e4 }
119
119
  },
120
120
  tools: ["*"],
121
- toolAliases: {},
122
- allowedTools: [],
123
121
  resources: [],
124
- hooks: {},
125
- toolsSettings: {},
126
- includeMcpJson: true,
127
- model: null
122
+ hooks: [],
123
+ includeMcpJson: true
128
124
  };
129
125
  }
130
126
  function installAgentFile(home2, dataDir) {
@@ -317,17 +313,18 @@ var init_config = __esm({
317
313
  kiroBin: resolveKiroBin(setting("KIRO_BIN", "kiroBin", "kiro-cli"), home),
318
314
  defaultCwd: setting("DEFAULT_CWD", "defaultCwd", process.cwd()),
319
315
  maxLiveSessions: settingInt("MAX_LIVE_SESSIONS", "maxLiveSessions", 6),
320
- /** The agent Casper installs, with the widget tools. Falls back to kiro_default
321
- * if missing rather than failing. */
316
+ /** The agent Casper installs, with the widget tools. kiro falls back to its Default
317
+ * mode if it is missing rather than failing. */
322
318
  defaultAgent: setting("DEFAULT_AGENT", "defaultAgent", "casper"),
323
319
  /** Filesystem root the file endpoints are confined to. Defaults to /; narrow it
324
320
  * to keep authenticated users out of system files such as /etc or SSH keys. */
325
321
  fileRoot: path5.resolve(setting("CASPER_FILE_ROOT", "fileRoot", "/")),
322
+ /** kiro's session store, one directory per workspace. */
326
323
  kiroSessionsDir: path5.resolve(
327
324
  setting(
328
325
  "CASPER_KIRO_SESSIONS_DIR",
329
326
  "kiroSessionsDir",
330
- path5.join(home, ".kiro", "sessions", "cli")
327
+ path5.join(home, ".kiro", "sessions")
331
328
  )
332
329
  ),
333
330
  casperDataDir: dataDirPath(),
@@ -353,6 +350,9 @@ function open() {
353
350
  const d = new DatabaseSync(file);
354
351
  d.exec("PRAGMA journal_mode = WAL");
355
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");
356
356
  restrict(config.casperDataDir, 448);
357
357
  for (const f of [file, `${file}-wal`, `${file}-shm`]) restrict(f, 384);
358
358
  return d;
@@ -373,7 +373,8 @@ CREATE TABLE IF NOT EXISTS chats (
373
373
  chat_id TEXT PRIMARY KEY,
374
374
  session_id TEXT UNIQUE,
375
375
  title TEXT,
376
- cwd TEXT
376
+ cwd TEXT,
377
+ pinned INTEGER NOT NULL DEFAULT 0
377
378
  );
378
379
  CREATE TABLE IF NOT EXISTS logins (
379
380
  id TEXT PRIMARY KEY,
@@ -403,13 +404,13 @@ CREATE TABLE IF NOT EXISTS turn_endings (
403
404
  items TEXT NOT NULL
404
405
  );
405
406
  CREATE INDEX IF NOT EXISTS turn_endings_chat ON turn_endings (chat_id);
406
- CREATE TABLE IF NOT EXISTS subagent_links (
407
- child_session_id TEXT PRIMARY KEY,
408
- parent_session_id TEXT NOT NULL,
409
- tool_call_id TEXT NOT NULL,
410
- stage_name TEXT NOT NULL
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
411
413
  );
412
- CREATE INDEX IF NOT EXISTS subagent_links_parent ON subagent_links (parent_session_id);
413
414
  `;
414
415
  }
415
416
  });
@@ -639,10 +640,38 @@ function isJsonRpcError(m) {
639
640
  function isJsonRpcNotification(m) {
640
641
  return !("id" in m) && "method" in m;
641
642
  }
642
- var ACP_METHODS, KIRO_NOTIFICATIONS;
643
+ function kiroToolName(call) {
644
+ const meta = call._meta?.kiro;
645
+ if (meta?.kind === "agent-subtask")
646
+ return SUBAGENT_TOOL;
647
+ const server = meta?.serverName;
648
+ if (!server)
649
+ return void 0;
650
+ const title = call.title ?? "";
651
+ const prefix = `@${server}/`;
652
+ return `${server}/${title.startsWith(prefix) ? title.slice(prefix.length) : title}`;
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
+ }
670
+ var SUBAGENT_TOOL, ACP_METHODS, KIRO_AGENT_NOT_FOUND;
643
671
  var init_acp = __esm({
644
672
  "shared/dist/acp.js"() {
645
673
  "use strict";
674
+ SUBAGENT_TOOL = "invoke_sub_agent";
646
675
  ACP_METHODS = {
647
676
  initialize: "initialize",
648
677
  sessionNew: "session/new",
@@ -650,19 +679,18 @@ var init_acp = __esm({
650
679
  sessionPrompt: "session/prompt",
651
680
  sessionCancel: "session/cancel",
652
681
  sessionSetMode: "session/set_mode",
653
- sessionSetModel: "session/set_model",
682
+ sessionSetConfigOption: "session/set_config_option",
683
+ sessionFork: "session/fork",
654
684
  sessionUpdate: "session/update",
655
- commandsExecute: "_kiro.dev/commands/execute"
656
- };
657
- KIRO_NOTIFICATIONS = {
658
- metadata: "_kiro.dev/metadata",
659
- subagentListUpdate: "_kiro.dev/subagent/list_update",
660
- mcpServerInitialized: "_kiro.dev/mcp/server_initialized",
661
- mcpServerInitFailure: "_kiro.dev/mcp/server_init_failure",
662
- commandsAvailable: "_kiro.dev/commands/available",
663
- mcpOauthRequest: "_kiro.dev/mcp/oauth_request",
664
- compactionStatus: "_kiro.dev/compaction/status"
685
+ requestPermission: "session/request_permission",
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"
665
692
  };
693
+ KIRO_AGENT_NOT_FOUND = "_kiro/customAgent/not_found";
666
694
  }
667
695
  });
668
696
 
@@ -759,7 +787,7 @@ function foldSessionUpdate(draft, u, seq, ts) {
759
787
  }
760
788
  if (u.sessionUpdate === "tool_call") {
761
789
  const tc = u;
762
- const toolName = tc._meta?.kiro?.toolName;
790
+ const toolName = kiroToolName(tc);
763
791
  return {
764
792
  items: [
765
793
  ...commitDraft(draft, `s-${seq}`, ts),
@@ -801,6 +829,7 @@ var emptyDraft;
801
829
  var init_turnFold = __esm({
802
830
  "shared/dist/turnFold.js"() {
803
831
  "use strict";
832
+ init_acp();
804
833
  emptyDraft = () => ({ items: [], streamingText: "", streamingThought: "" });
805
834
  }
806
835
  });
@@ -831,10 +860,15 @@ import { execFile } from "node:child_process";
831
860
  import { promisify } from "node:util";
832
861
  function invalidateAgents() {
833
862
  cache = null;
863
+ liveModes = null;
864
+ }
865
+ function rememberModes(modes) {
866
+ if (modes.length > 0) liveModes = modes;
834
867
  }
835
868
  async function listAgents() {
869
+ if (liveModes) return liveModes;
836
870
  if (cache && Date.now() - cache.at < CACHE_TTL_MS) return cache.agents;
837
- const found = /* @__PURE__ */ new Map();
871
+ const found = /* @__PURE__ */ new Map([[DEFAULT_MODE.id, DEFAULT_MODE]]);
838
872
  try {
839
873
  const { stdout, stderr } = await execFileAsync(config.kiroBin, ["agent", "list"], {
840
874
  cwd: config.defaultCwd,
@@ -843,30 +877,26 @@ async function listAgents() {
843
877
  const text2 = (stderr || "") + "\n" + (stdout || "");
844
878
  for (const rawLine of text2.split("\n")) {
845
879
  const line = rawLine.replace(ANSI, "");
846
- const m = /^\s{0,2}(\*\s)?([A-Za-z0-9_-]+)\s{2,}(\(Built-in\)|Global|Workspace|Local)(?:\s|$)/.exec(
847
- line
848
- );
880
+ const m = /^\s{0,2}(\*\s)?([A-Za-z0-9_-]+)\s{2,}(Global|Workspace|Local)(?:\s|$)/.exec(line);
849
881
  if (!m) continue;
850
882
  const id = m[2];
851
883
  if (!found.has(id)) found.set(id, { id, name: id });
852
884
  }
853
885
  } catch {
854
886
  }
855
- const agents = found.size > 0 ? [...found.values()] : FALLBACK;
856
- cache = { at: Date.now(), agents };
887
+ cache = { at: Date.now(), agents: [...found.values()] };
857
888
  return cache.agents;
858
889
  }
859
- var execFileAsync, FALLBACK, CACHE_TTL_MS, cache, ANSI;
890
+ var execFileAsync, DEFAULT_MODE, CACHE_TTL_MS, cache, liveModes, ANSI;
860
891
  var init_agents = __esm({
861
892
  "server/src/session/agents.ts"() {
862
893
  "use strict";
863
894
  init_config();
864
895
  execFileAsync = promisify(execFile);
865
- FALLBACK = [
866
- { id: "kiro_default", name: "kiro_default", description: "General-purpose Kiro agent" }
867
- ];
896
+ DEFAULT_MODE = { id: "vibe", name: "Default", description: "General coding assistance" };
868
897
  CACHE_TTL_MS = 15e3;
869
898
  cache = null;
899
+ liveModes = null;
870
900
  ANSI = /\x1b\[[0-9;]*m/g;
871
901
  }
872
902
  });
@@ -955,6 +985,62 @@ var init_chats = __esm({
955
985
  }
956
986
  });
957
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
+
958
1044
  // server/src/acp/errors.ts
959
1045
  function describeError(error) {
960
1046
  const detail = detailText(error.data);
@@ -1072,6 +1158,10 @@ var init_AcpClient = __esm({
1072
1158
  if (this.closed) return;
1073
1159
  this.write({ jsonrpc: "2.0", id, result });
1074
1160
  }
1161
+ respondError(id, code, message) {
1162
+ if (this.closed) return;
1163
+ this.write({ jsonrpc: "2.0", id, error: { code, message } });
1164
+ }
1075
1165
  write(msg) {
1076
1166
  this.writable.write(JSON.stringify(msg) + "\n");
1077
1167
  }
@@ -1093,7 +1183,12 @@ var init_AcpClient = __esm({
1093
1183
  import { spawn } from "node:child_process";
1094
1184
  import { EventEmitter as EventEmitter3 } from "node:events";
1095
1185
  import split22 from "split2";
1096
- var STDERR_KEEP, KiroProcess;
1186
+ function permissionOutcome(params) {
1187
+ const options = params?.options ?? [];
1188
+ const pick = options.find((o) => o.kind === "allow_once") ?? options.find((o) => o.kind === "reject_once");
1189
+ return pick ? { outcome: { outcome: "selected", optionId: pick.optionId } } : { outcome: { outcome: "cancelled" } };
1190
+ }
1191
+ var STDERR_KEEP, COMPACT_TIMEOUT_MS, KiroProcess;
1097
1192
  var init_KiroProcess = __esm({
1098
1193
  "server/src/session/KiroProcess.ts"() {
1099
1194
  "use strict";
@@ -1101,18 +1196,21 @@ var init_KiroProcess = __esm({
1101
1196
  init_config();
1102
1197
  init_AcpClient();
1103
1198
  STDERR_KEEP = 8;
1199
+ COMPACT_TIMEOUT_MS = 10 * 6e4;
1104
1200
  KiroProcess = class extends EventEmitter3 {
1105
1201
  client;
1106
1202
  child;
1107
1203
  log;
1108
1204
  disposed = false;
1109
1205
  recentStderr = [];
1206
+ // kiro's optional methods, as advertised by initialize.
1207
+ extensionMethods = /* @__PURE__ */ new Set();
1208
+ // Whether kiro can rewind a session to before one of its prompts.
1209
+ checkpoints = false;
1110
1210
  constructor(opts, log) {
1111
1211
  super();
1112
1212
  this.log = log;
1113
- const args = ["acp", "--trust-all-tools"];
1114
- if (opts.agent) args.push("--agent", opts.agent);
1115
- if (opts.model) args.push("--model", opts.model);
1213
+ const args = ["acp", "--agent-engine", "v3"];
1116
1214
  this.log.info({ bin: config.kiroBin, args, cwd: opts.cwd }, "spawning kiro-cli acp");
1117
1215
  this.child = spawn(config.kiroBin, args, {
1118
1216
  cwd: opts.cwd,
@@ -1124,8 +1222,12 @@ var init_KiroProcess = __esm({
1124
1222
  this.emit("notification", n);
1125
1223
  });
1126
1224
  this.client.on("serverRequest", (req) => {
1127
- this.log.debug({ method: req.method }, "acp: unhandled server request");
1128
- this.client.respond(req.id, {});
1225
+ if (req.method === ACP_METHODS.requestPermission) {
1226
+ this.client.respond(req.id, permissionOutcome(req.params));
1227
+ return;
1228
+ }
1229
+ this.log.debug({ method: req.method }, "acp: unsupported request from kiro");
1230
+ this.client.respondError(req.id, -32601, `Method not found: ${req.method}`);
1129
1231
  });
1130
1232
  const stderrLines = this.child.stderr.pipe(split22());
1131
1233
  stderrLines.on("data", (line) => {
@@ -1157,9 +1259,9 @@ var init_KiroProcess = __esm({
1157
1259
  this.child.stdout.on("error", () => {
1158
1260
  });
1159
1261
  }
1160
- /** The ACP initialize handshake. kiro's reply is not used. */
1262
+ /** The ACP initialize handshake. Keeps the optional methods kiro advertises. */
1161
1263
  async initialize() {
1162
- await this.client.request(ACP_METHODS.initialize, {
1264
+ const res = await this.client.request(ACP_METHODS.initialize, {
1163
1265
  protocolVersion: 1,
1164
1266
  clientCapabilities: {
1165
1267
  fs: { readTextFile: false, writeTextFile: false },
@@ -1167,6 +1269,11 @@ var init_KiroProcess = __esm({
1167
1269
  },
1168
1270
  clientInfo: { name: "casper", version: "0.5.0" }
1169
1271
  });
1272
+ const methods = res?.agentCapabilities?._meta?.kiro?.extensionMethods;
1273
+ this.extensionMethods = new Set(
1274
+ Array.isArray(methods) ? methods.filter((m) => typeof m === "string") : []
1275
+ );
1276
+ this.checkpoints = res?.agentCapabilities?._meta?.kiro?.checkpoints === true;
1170
1277
  }
1171
1278
  newSession(params) {
1172
1279
  return this.client.request(ACP_METHODS.sessionNew, params);
@@ -1174,6 +1281,9 @@ var init_KiroProcess = __esm({
1174
1281
  loadSession(params) {
1175
1282
  return this.client.request(ACP_METHODS.sessionLoad, params);
1176
1283
  }
1284
+ forkSession(params) {
1285
+ return this.client.request(ACP_METHODS.sessionFork, params);
1286
+ }
1177
1287
  /** Runs a prompt turn to completion. */
1178
1288
  prompt(params) {
1179
1289
  return this.client.request(
@@ -1189,13 +1299,62 @@ var init_KiroProcess = __esm({
1189
1299
  return this.client.request(ACP_METHODS.sessionSetMode, { sessionId, modeId });
1190
1300
  }
1191
1301
  setModel(sessionId, modelId) {
1192
- return this.client.request(ACP_METHODS.sessionSetModel, { sessionId, modelId });
1302
+ return this.client.request(ACP_METHODS.sessionSetConfigOption, {
1303
+ sessionId,
1304
+ configId: "model",
1305
+ value: modelId
1306
+ });
1307
+ }
1308
+ setEffort(sessionId, effortLevel) {
1309
+ return this.client.request(ACP_METHODS.sessionSetConfigOption, {
1310
+ sessionId,
1311
+ configId: "effortLevel",
1312
+ value: effortLevel
1313
+ });
1193
1314
  }
1194
- execCommand(sessionId, command) {
1195
- return this.client.request(ACP_METHODS.commandsExecute, {
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, {
1196
1319
  sessionId,
1197
- command: { command: command.replace(/^\//, ""), args: {} }
1320
+ message,
1321
+ messageId
1198
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
+ }
1352
+ /** Summarises the conversation so far. Resolves once kiro has finished. */
1353
+ compact(sessionId) {
1354
+ if (!this.extensionMethods.has(ACP_METHODS.compact)) {
1355
+ return Promise.reject(new Error("This kiro-cli does not offer compaction"));
1356
+ }
1357
+ return this.client.request(ACP_METHODS.compact, { sessionId }, COMPACT_TIMEOUT_MS);
1199
1358
  }
1200
1359
  /** Recent stderr from kiro, for attaching to a failure. */
1201
1360
  stderrTail() {
@@ -1324,7 +1483,9 @@ var init_TurnState = __esm({
1324
1483
  case "metadata":
1325
1484
  this.snapshot = {
1326
1485
  ...s,
1327
- 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
1328
1489
  };
1329
1490
  break;
1330
1491
  case "compaction":
@@ -1355,6 +1516,9 @@ function cap(value) {
1355
1516
  }
1356
1517
  return value;
1357
1518
  }
1519
+ function steerMessage(text2, seq, ts) {
1520
+ return { type: "message", message: { id: `s-${seq}`, role: "user", text: text2, timestamp: ts, steer: true } };
1521
+ }
1358
1522
  var MAX_STRING, isImage, bounded, TurnRecorder;
1359
1523
  var init_TurnRecorder = __esm({
1360
1524
  "server/src/session/TurnRecorder.ts"() {
@@ -1390,6 +1554,8 @@ var init_TurnRecorder = __esm({
1390
1554
  );
1391
1555
  } else if (p.kind === "session_update" && this.draft) {
1392
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)] };
1393
1559
  }
1394
1560
  }
1395
1561
  /** Null when no turn was being followed. */
@@ -1418,302 +1584,450 @@ var init_SubagentTracker = __esm({
1418
1584
  "server/src/session/SubagentTracker.ts"() {
1419
1585
  "use strict";
1420
1586
  SubagentTracker = class {
1421
- byChild = /* @__PURE__ */ new Map();
1422
- pendingCalls = [];
1423
- callStarted(toolCallId, stages) {
1424
- this.pendingCalls.push({ toolCallId, stages });
1425
- }
1426
- callFinished(toolCallId) {
1427
- const i = this.pendingCalls.findIndex((c) => c.toolCallId === toolCallId);
1428
- if (i >= 0) this.pendingCalls.splice(i, 1);
1429
- }
1430
- callFor(stageName) {
1431
- for (let i = this.pendingCalls.length - 1; i >= 0; i--) {
1432
- if (this.pendingCalls[i].stages.some((s) => s.name === stageName)) return this.pendingCalls[i];
1433
- }
1434
- return void 0;
1435
- }
1436
- /** Folds a list_update into the tracked state, returning links newly resolved
1437
- * this call, so the caller persists each one once. */
1438
- apply(subagents, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
1439
- const resolved = [];
1440
- for (const info of subagents) {
1441
- const existing = this.byChild.get(info.sessionId);
1442
- const nowIso = now();
1443
- const toolCallId = existing?.toolCallId ?? this.callFor(info.sessionName)?.toolCallId;
1444
- if (toolCallId && !existing?.toolCallId) {
1445
- resolved.push({ sessionId: info.sessionId, toolCallId, stageName: info.sessionName });
1446
- }
1447
- this.byChild.set(info.sessionId, {
1448
- sessionId: info.sessionId,
1449
- stageName: info.sessionName,
1450
- toolCallId,
1451
- status: info.status.type === "terminated" ? "completed" : "working",
1452
- activity: info.status.message,
1453
- createdAt: existing?.createdAt ?? nowIso,
1454
- updatedAt: nowIso
1455
- });
1587
+ constructor(now = () => (/* @__PURE__ */ new Date()).toISOString()) {
1588
+ this.now = now;
1589
+ }
1590
+ byId = /* @__PURE__ */ new Map();
1591
+ /** The tool call that starts a subagent arrived. */
1592
+ started(subagentId, toolCallId, name) {
1593
+ if (this.byId.has(subagentId)) return false;
1594
+ const at = this.now();
1595
+ this.byId.set(subagentId, {
1596
+ sessionId: subagentId,
1597
+ stageName: name,
1598
+ toolCallId,
1599
+ status: "working",
1600
+ createdAt: at,
1601
+ updatedAt: at
1602
+ });
1603
+ return true;
1604
+ }
1605
+ /** One of the subagent's own updates arrived. `activity` is the title of a tool call it
1606
+ * started, which becomes the row's "what it is doing now". */
1607
+ progressed(subagentId, activity) {
1608
+ const s = this.byId.get(subagentId);
1609
+ if (!s || s.status !== "working") return false;
1610
+ s.updatedAt = this.now();
1611
+ if (!activity || activity === s.activity) return false;
1612
+ s.activity = activity;
1613
+ return true;
1614
+ }
1615
+ /** The call that started a subagent finished. */
1616
+ finished(toolCallId, ok2) {
1617
+ for (const s of this.byId.values()) {
1618
+ if (s.toolCallId !== toolCallId || s.status !== "working") continue;
1619
+ s.status = ok2 ? "completed" : "failed";
1620
+ s.activity = void 0;
1621
+ s.updatedAt = this.now();
1622
+ return true;
1456
1623
  }
1457
- return resolved;
1624
+ return false;
1458
1625
  }
1459
- /** Called when the parent turn ends. The `subagent` tool call blocks the turn, so
1460
- * any subagent still working was stopped with it, and kiro does not always send a
1461
- * list_update saying so. Returns whether anything changed. */
1462
- turnEnded(now = () => (/* @__PURE__ */ new Date()).toISOString()) {
1463
- let changed = this.pendingCalls.length > 0;
1464
- this.pendingCalls.length = 0;
1465
- for (const s of this.byChild.values()) {
1626
+ /** Called when the parent turn ends. The turn waits on its subagents, so any still
1627
+ * working was stopped with it, and kiro does not always report that. */
1628
+ turnEnded() {
1629
+ let changed = false;
1630
+ for (const s of this.byId.values()) {
1466
1631
  if (s.status !== "working") continue;
1467
1632
  s.status = "failed";
1468
1633
  s.activity = "Stopped";
1469
- s.updatedAt = now();
1634
+ s.updatedAt = this.now();
1470
1635
  changed = true;
1471
1636
  }
1472
1637
  return changed;
1473
1638
  }
1474
- /** Started stages plus a synthetic 'pending' row for each declared stage that
1475
- * hasn't started, so a stage waiting on a dependency still shows up. */
1476
- listForCall(toolCallId) {
1477
- const call = this.pendingCalls.find((c) => c.toolCallId === toolCallId);
1478
- const started = [...this.byChild.values()].filter((s) => s.toolCallId === toolCallId);
1479
- if (!call) return started.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
1480
- const startedNames = new Set(started.map((s) => s.stageName));
1481
- const pending = call.stages.filter((s) => !startedNames.has(s.name)).map((s) => ({
1482
- sessionId: `pending:${toolCallId}:${s.name}`,
1483
- stageName: s.name,
1484
- toolCallId,
1485
- status: "pending",
1486
- activity: s.depends_on?.length ? `Waits for ${s.depends_on.join(" and ")}` : void 0,
1487
- createdAt: "",
1488
- updatedAt: ""
1489
- }));
1490
- return [...started.sort((a, b) => a.createdAt.localeCompare(b.createdAt)), ...pending];
1491
- }
1639
+ /** Copies, so a recorded event keeps the state it was recorded with. */
1492
1640
  list() {
1493
- return [...this.byChild.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
1641
+ return [...this.byId.values()].map((s) => ({ ...s })).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
1494
1642
  }
1495
- get(sessionId) {
1496
- return this.byChild.get(sessionId);
1643
+ get(subagentId) {
1644
+ const s = this.byId.get(subagentId);
1645
+ return s && { ...s };
1497
1646
  }
1498
1647
  };
1499
1648
  }
1500
1649
  });
1501
1650
 
1502
- // server/src/session/subagentResult.ts
1503
- function showSummaryAsAnswer(transcript) {
1504
- const out = [];
1505
- for (const item of transcript) {
1506
- if (item.type === "message" && item.message.role === "assistant" && item.message.text.trim() === INTERRUPTED) {
1507
- continue;
1508
- }
1509
- if (item.type === "tool_call" && item.tool.name === "summary") {
1510
- const input = item.tool.input;
1511
- const result = typeof input?.taskResult === "string" ? input.taskResult.trim() : "";
1512
- if (result) out.push({ type: "message", message: { id: item.tool.id, role: "assistant", text: result } });
1513
- continue;
1514
- }
1515
- out.push(item);
1516
- }
1517
- return out;
1518
- }
1519
- var INTERRUPTED;
1520
- var init_subagentResult = __esm({
1521
- "server/src/session/subagentResult.ts"() {
1522
- "use strict";
1523
- INTERRUPTED = "Tool uses were interrupted, waiting for the next user prompt";
1524
- }
1525
- });
1526
-
1527
- // server/src/session/subagentFallbackMatch.ts
1528
- function isStageInput(v) {
1529
- return typeof v === "object" && v !== null && typeof v.name === "string";
1530
- }
1531
- function subagentCallInput(tool) {
1532
- if (tool.name !== "subagent") return null;
1533
- const input = tool.input;
1534
- if (!input || !Array.isArray(input.stages)) return null;
1535
- const stages = input.stages.filter(isStageInput);
1536
- return { task: typeof input.task === "string" ? input.task : void 0, stages };
1537
- }
1538
- function expectedTitle(call, stage) {
1539
- const task = call.task ?? "";
1540
- const expanded = stage.prompt_template ? stage.prompt_template.replace("{task}", task) : task;
1541
- return expanded.slice(0, TITLE_MAX_LENGTH);
1542
- }
1543
- function subagentCallsIn(transcript) {
1544
- const calls = [];
1545
- transcript.forEach((item, itemIndex) => {
1546
- if (item.type !== "tool_call") return;
1547
- const input = subagentCallInput(item.tool);
1548
- if (!input) return;
1549
- calls.push({ toolCallId: item.tool.id, itemIndex, input });
1550
- });
1551
- return calls;
1552
- }
1553
- function matchSubagentsFallback(calls, children) {
1554
- const sortedCalls = [...calls].sort((a, b) => a.itemIndex - b.itemIndex);
1555
- const unclaimed = [...children].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
1556
- const matches = [];
1557
- for (const call of sortedCalls) {
1558
- for (const stage of call.input.stages) {
1559
- const want = expectedTitle(call.input, stage);
1560
- if (!want) continue;
1561
- const i = unclaimed.findIndex((c) => want.startsWith(c.title) || c.title.startsWith(want));
1562
- if (i < 0) continue;
1563
- const child = unclaimed.splice(i, 1)[0];
1564
- matches.push({ sessionId: child.sessionId, toolCallId: call.toolCallId, stageName: stage.name });
1565
- }
1566
- }
1567
- return matches;
1568
- }
1569
- var TITLE_MAX_LENGTH;
1570
- var init_subagentFallbackMatch = __esm({
1571
- "server/src/session/subagentFallbackMatch.ts"() {
1572
- "use strict";
1573
- TITLE_MAX_LENGTH = 150;
1574
- }
1575
- });
1576
-
1577
- // server/src/session/kiroFiles.ts
1651
+ // server/src/session/kiroV3Files.ts
1578
1652
  import fs9 from "node:fs/promises";
1579
1653
  import path10 from "node:path";
1580
- function isContentBlock(v) {
1581
- return isRecord(v) && typeof v.kind === "string";
1582
- }
1583
- function toolUseData(b) {
1584
- if (b.kind !== "toolUse" || !isRecord(b.data)) return null;
1585
- const { toolUseId, name, input } = b.data;
1586
- if (typeof toolUseId !== "string") return null;
1587
- return { toolUseId, name: typeof name === "string" ? name : void 0, input };
1588
- }
1589
- function toolResultData(b) {
1590
- if (b.kind !== "toolResult" || !isRecord(b.data)) return null;
1591
- const { toolUseId, status, content } = b.data;
1592
- if (typeof toolUseId !== "string") return null;
1593
- return { toolUseId, status: typeof status === "string" ? status : void 0, content };
1594
- }
1595
- function contentBlocks(content) {
1596
- return Array.isArray(content) ? content.filter(isContentBlock) : [];
1597
- }
1598
- function blockText(c) {
1599
- if (typeof c.data === "string") return c.data;
1600
- if (isRecord(c.data) && typeof c.data.text === "string") return c.data.text;
1601
- return "";
1654
+ function iso(value) {
1655
+ if (!value) return void 0;
1656
+ const ms = Date.parse(value);
1657
+ return Number.isNaN(ms) ? value : new Date(ms).toISOString();
1602
1658
  }
1603
- function renderableBlocks(content) {
1604
- return contentBlocks(content).filter((b) => b.kind !== "image");
1659
+ async function workspaceDirs() {
1660
+ const root = config.kiroSessionsDir;
1661
+ try {
1662
+ const entries = await fs9.readdir(root, { withFileTypes: true });
1663
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== V2_STORE).map((e) => path10.join(root, e.name));
1664
+ } catch {
1665
+ return [];
1666
+ }
1605
1667
  }
1606
- function summarize(j) {
1607
- const state = j.session_state;
1608
- const turns = state?.conversation_metadata?.user_turn_metadatas ?? [];
1609
- const contextUsagePercentage = state?.rts_model_state?.context_usage_percentage ?? turns[turns.length - 1]?.context_usage_percentage;
1610
- return {
1611
- sessionId: j.session_id,
1612
- title: j.title?.trim() ?? "",
1613
- cwd: j.cwd,
1614
- createdAt: j.created_at,
1615
- updatedAt: j.updated_at,
1616
- agentId: state?.agent_name,
1617
- modelId: state?.rts_model_state?.model_info?.model_id,
1618
- contextUsagePercentage
1619
- };
1668
+ async function v3SessionDirs() {
1669
+ const out = /* @__PURE__ */ new Map();
1670
+ await Promise.all(
1671
+ (await workspaceDirs()).map(async (dir) => {
1672
+ try {
1673
+ for (const e of await fs9.readdir(dir, { withFileTypes: true })) {
1674
+ if (e.isDirectory() && !e.name.startsWith(".") && isValidSessionId(e.name)) {
1675
+ out.set(e.name, path10.join(dir, e.name));
1676
+ }
1677
+ }
1678
+ } catch {
1679
+ }
1680
+ })
1681
+ );
1682
+ return out;
1620
1683
  }
1621
- async function deletePersistedSession(sessionId) {
1622
- if (!isValidSessionId(sessionId)) return;
1623
- const targets = [
1624
- path10.join(config.kiroSessionsDir, `${sessionId}.json`),
1625
- path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`),
1626
- path10.join(config.kiroSessionsDir, `${sessionId}.history`),
1627
- path10.join(config.kiroSessionsDir, `${sessionId}.lock`),
1628
- path10.join(config.kiroSessionsDir, sessionId)
1629
- ];
1630
- await Promise.all(targets.map((f) => fs9.rm(f, { recursive: true, force: true })));
1684
+ async function v3SessionDir(sessionId) {
1685
+ if (!isValidSessionId(sessionId)) return void 0;
1686
+ const found = await Promise.all(
1687
+ (await workspaceDirs()).map(async (dir) => {
1688
+ const candidate = path10.join(dir, sessionId);
1689
+ try {
1690
+ await fs9.access(path10.join(candidate, "session.json"));
1691
+ return candidate;
1692
+ } catch {
1693
+ return void 0;
1694
+ }
1695
+ })
1696
+ );
1697
+ return found.find((d) => d !== void 0);
1631
1698
  }
1632
- async function readPersistedSession(sessionId) {
1633
- if (!isValidSessionId(sessionId)) return null;
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;
1634
1703
  try {
1635
- const raw = await fs9.readFile(
1636
- path10.join(config.kiroSessionsDir, `${sessionId}.json`),
1637
- "utf8"
1638
- );
1639
- return summarize(JSON.parse(raw));
1704
+ const l = JSON.parse(line);
1705
+ const p = l.payload;
1706
+ const value = isRecord(p.value) ? p.value.usagePercentage : 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);
1640
1711
  } catch {
1641
- return null;
1642
1712
  }
1643
1713
  }
1644
- async function withLastWrite(child) {
1714
+ async function readTail(file) {
1715
+ const facts = {};
1716
+ const done = () => facts.contextUsage !== void 0 && facts.lastPromptAt !== void 0;
1717
+ let handle2;
1645
1718
  try {
1646
- const st = await fs9.stat(path10.join(config.kiroSessionsDir, `${child.sessionId}.jsonl`));
1647
- const mtime = st.mtime.toISOString();
1648
- return mtime > child.updatedAt ? { ...child, updatedAt: mtime } : child;
1719
+ handle2 = await fs9.open(file, "r");
1649
1720
  } catch {
1650
- return child;
1721
+ return facts;
1651
1722
  }
1652
- }
1653
- async function readChildSession(childSessionId, parentSessionId) {
1654
- if (!isValidSessionId(childSessionId)) return null;
1655
1723
  try {
1656
- const raw = await fs9.readFile(path10.join(config.kiroSessionsDir, `${childSessionId}.json`), "utf8");
1657
- const j = JSON.parse(raw);
1658
- return j.parent_session_id === parentSessionId ? await withLastWrite({ ...summarize(j), parentSessionId }) : null;
1659
- } catch {
1660
- return null;
1724
+ const { size } = await handle2.stat();
1725
+ let end = size;
1726
+ let tail = "";
1727
+ while (end > 0 && size - end < CONTEXT_TAIL_BYTES && !done()) {
1728
+ const start = Math.max(0, end - CONTEXT_CHUNK_BYTES);
1729
+ const buf = Buffer.alloc(end - start);
1730
+ await handle2.read(buf, 0, buf.length, start);
1731
+ end = start;
1732
+ const lines = (buf.toString("utf8") + tail).split("\n");
1733
+ tail = end > 0 ? lines.shift() : "";
1734
+ for (let i = lines.length - 1; i >= 0 && !done(); i--) readTailLine(lines[i], facts);
1735
+ }
1736
+ if (tail && !done()) readTailLine(tail, facts);
1737
+ return facts;
1738
+ } finally {
1739
+ await handle2.close();
1661
1740
  }
1662
1741
  }
1663
- async function listChildSessions(parentSessionId) {
1664
- if (!isValidSessionId(parentSessionId)) return [];
1665
- let names;
1742
+ async function readV3Session(dir) {
1743
+ let j;
1666
1744
  try {
1667
- names = await fs9.readdir(config.kiroSessionsDir);
1745
+ j = JSON.parse(await fs9.readFile(path10.join(dir, "session.json"), "utf8"));
1668
1746
  } catch {
1669
- return [];
1747
+ return null;
1670
1748
  }
1671
- const out = [];
1672
- await Promise.all(
1673
- names.filter((n) => n.endsWith(".json")).map(async (n) => {
1674
- try {
1675
- const raw = await fs9.readFile(path10.join(config.kiroSessionsDir, n), "utf8");
1676
- const j = JSON.parse(raw);
1677
- if (j.parent_session_id === parentSessionId) {
1678
- out.push(await withLastWrite({ ...summarize(j), parentSessionId }));
1679
- }
1680
- } catch {
1681
- }
1682
- })
1683
- );
1684
- return out;
1685
- }
1686
- async function promptCount(sessionId) {
1687
- const entries = await readJsonlEntries(sessionId);
1688
- return entries.filter((e) => e.kind === "Prompt").length;
1749
+ const title = j.title?.trim() ?? "";
1750
+ const createdAt = iso(j.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
1751
+ const tail = await readTail(path10.join(dir, "messages.jsonl"));
1752
+ return {
1753
+ sessionId: j.id,
1754
+ title: title === PLACEHOLDER_TITLE ? "" : title,
1755
+ cwd: j.workspacePaths?.[0] ?? "",
1756
+ 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,
1760
+ agentId: j.agentMode,
1761
+ modelId: j.modelId,
1762
+ effortLevel: j.effortLevel,
1763
+ contextUsagePercentage: tail.contextUsage
1764
+ };
1689
1765
  }
1690
- async function readJsonlEntries(sessionId) {
1691
- if (!isValidSessionId(sessionId)) return [];
1766
+ async function readLines(file, filter) {
1692
1767
  let raw;
1693
1768
  try {
1694
- raw = await fs9.readFile(path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`), "utf8");
1769
+ raw = await fs9.readFile(file, "utf8");
1695
1770
  } catch {
1696
1771
  return [];
1697
1772
  }
1698
- const entries = [];
1773
+ const out = [];
1699
1774
  for (const line of raw.split("\n")) {
1700
- const trimmed = line.trim();
1701
- if (!trimmed) continue;
1775
+ if (!line.trim() || filter && !line.includes(filter)) continue;
1702
1776
  try {
1703
- entries.push(JSON.parse(trimmed));
1777
+ const parsed = JSON.parse(line);
1778
+ if (isRecord(parsed) && isRecord(parsed.payload) && typeof parsed.id === "string") out.push(parsed);
1704
1779
  } catch {
1705
1780
  }
1706
1781
  }
1707
- return entries;
1782
+ return out;
1708
1783
  }
1709
- async function hasRecordedTurns(sessionId) {
1710
- if (!isValidSessionId(sessionId)) return false;
1711
- try {
1712
- const st = await fs9.stat(path10.join(config.kiroSessionsDir, `${sessionId}.jsonl`));
1713
- return st.size > 0;
1714
- } catch {
1715
- return false;
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
+ }
1798
+ function userText(content) {
1799
+ if (typeof content === "string") return content;
1800
+ if (!Array.isArray(content)) return "";
1801
+ return content.map((b) => isRecord(b) && b.type === "text" && typeof b.text === "string" ? b.text : "").join("");
1802
+ }
1803
+ function resultBlocks(content) {
1804
+ if (typeof content === "string") return content ? [{ type: "text", text: content }] : [];
1805
+ if (Array.isArray(content)) return content.filter((b) => !(isRecord(b) && b.type === "image"));
1806
+ return isRecord(content) ? [{ kind: "json", data: content }] : [];
1807
+ }
1808
+ function isCompactionCopy(id, seen) {
1809
+ if (!id.startsWith("compact_")) return false;
1810
+ const rest = id.slice("compact_".length);
1811
+ const sep = rest.indexOf("_");
1812
+ return sep >= 0 && seen.has(rest.slice(sep + 1));
1813
+ }
1814
+ async function readV3Entries(file) {
1815
+ const out = [];
1816
+ const toolsById = /* @__PURE__ */ new Map();
1817
+ const addTool = (tool) => {
1818
+ const seen2 = toolsById.get(tool.id);
1819
+ if (seen2) {
1820
+ Object.assign(seen2, { ...tool, content: seen2.content, output: seen2.output ?? tool.output });
1821
+ return;
1822
+ }
1823
+ toolsById.set(tool.id, tool);
1824
+ out.push({ kind: "item", item: { type: "tool_call", tool } });
1825
+ };
1826
+ const seen = /* @__PURE__ */ new Set();
1827
+ const steers = /* @__PURE__ */ new Map();
1828
+ for (const line of await readLines(file)) {
1829
+ const copy = isCompactionCopy(line.id, seen);
1830
+ seen.add(line.id);
1831
+ if (copy) continue;
1832
+ const p = line.payload;
1833
+ const parsed = line.timestamp ? Date.parse(line.timestamp) : NaN;
1834
+ const at = Number.isNaN(parsed) ? void 0 : parsed;
1835
+ switch (p.type) {
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
+ }
1852
+ out.push({ kind: "prompt", id: line.id, text: userText(p.content), atMs: at, timestamp: at });
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
+ }
1875
+ case "assistant": {
1876
+ const text2 = str2(p.content) ?? "";
1877
+ if (!text2.trim()) break;
1878
+ if (p.operationType === "Summary") {
1879
+ out.push({ kind: "item", item: { type: "compaction", id: `c-${line.id}`, summary: text2, timestamp: at } });
1880
+ } else if (p.operationType === "Reasoning") {
1881
+ if (text2.trim() !== HIDDEN_REASONING) {
1882
+ out.push({ kind: "item", item: { type: "message", message: { id: `t-${line.id}`, role: "thinking", text: text2.trim(), timestamp: at } } });
1883
+ }
1884
+ } else {
1885
+ out.push({ kind: "item", item: { type: "message", message: { id: `a-${line.id}`, role: "assistant", text: text2, timestamp: at } } });
1886
+ }
1887
+ break;
1888
+ }
1889
+ case "tool_call": {
1890
+ const id = str2(p.toolCallId);
1891
+ if (!id) break;
1892
+ const title = str2(p.title);
1893
+ const meta = isRecord(p._meta) ? p._meta : void 0;
1894
+ const args = p.args;
1895
+ const input = p.toolName === "tool_call" && isRecord(args) && "arguments" in args ? args.arguments : args;
1896
+ addTool({
1897
+ id,
1898
+ // A call kiro copied in from a kiro 2.x session has no kind, only 2.x's tool name,
1899
+ // which the web renders by.
1900
+ name: kiroToolName({ title, _meta: meta }) ?? (p.kind === void 0 ? str2(p.toolName) : void 0),
1901
+ title: title ?? str2(p.toolName) ?? id,
1902
+ kind: str2(p.kind),
1903
+ status: p.status === "failed" ? "failed" : "completed",
1904
+ input,
1905
+ content: []
1906
+ });
1907
+ break;
1908
+ }
1909
+ case "tool_result": {
1910
+ const tool = toolsById.get(str2(p.toolCallId) ?? "");
1911
+ if (!tool) break;
1912
+ tool.status = p.success === false ? "failed" : "completed";
1913
+ tool.content = resultBlocks(p.content);
1914
+ break;
1915
+ }
1916
+ case "sub_agent_start": {
1917
+ const name = str2(p.subAgentName) ?? "subagent";
1918
+ addTool({
1919
+ id: line.id.endsWith(START_SUFFIX) ? line.id.slice(0, -START_SUFFIX.length) : line.id,
1920
+ name: SUBAGENT_TOOL,
1921
+ title: `Sub-agent: ${name}`,
1922
+ kind: "other",
1923
+ // Without a result the parent turn ended while the subagent ran.
1924
+ status: "failed",
1925
+ input: { name, prompt: str2(p.prompt), explanation: str2(p.explanation) },
1926
+ content: []
1927
+ });
1928
+ break;
1929
+ }
1930
+ case "sub_agent_complete": {
1931
+ const tool = toolsById.get(line.id.endsWith(COMPLETE_SUFFIX) ? line.id.slice(0, -COMPLETE_SUFFIX.length) : "");
1932
+ if (!tool) break;
1933
+ tool.status = p.status === "success" ? "completed" : "failed";
1934
+ tool.output = str2(p.response);
1935
+ break;
1936
+ }
1937
+ default:
1938
+ break;
1939
+ }
1940
+ }
1941
+ return out;
1942
+ }
1943
+ async function readSubExecutions(dir) {
1944
+ const byId = /* @__PURE__ */ new Map();
1945
+ for (const line of await readLines(path10.join(dir, "messages.jsonl"), '"sub_agent_')) {
1946
+ const p = line.payload;
1947
+ const subId = str2(p.subSessionId);
1948
+ if (!subId) continue;
1949
+ const at = iso(line.timestamp) ?? (/* @__PURE__ */ new Date(0)).toISOString();
1950
+ if (p.type === "sub_agent_start") {
1951
+ byId.set(subId, {
1952
+ sessionId: subId,
1953
+ name: str2(p.subAgentName) ?? subId,
1954
+ toolCallId: line.id.endsWith(START_SUFFIX) ? line.id.slice(0, -START_SUFFIX.length) : line.id,
1955
+ status: "failed",
1956
+ stopped: true,
1957
+ createdAt: at,
1958
+ updatedAt: at
1959
+ });
1960
+ } else if (p.type === "sub_agent_complete") {
1961
+ const sub = byId.get(subId);
1962
+ if (!sub) continue;
1963
+ sub.status = p.status === "success" ? "completed" : "failed";
1964
+ sub.stopped = false;
1965
+ sub.updatedAt = at;
1966
+ }
1967
+ }
1968
+ return [...byId.values()];
1969
+ }
1970
+ function subExecutionFile(dir, subagentId) {
1971
+ return isValidSessionId(subagentId) ? path10.join(dir, "sub-executions", `${subagentId}.jsonl`) : void 0;
1972
+ }
1973
+ var PLACEHOLDER_TITLE, HIDDEN_REASONING, CONTEXT_TAIL_BYTES, CONTEXT_CHUNK_BYTES, isRecord, str2, V2_STORE, START_SUFFIX, COMPLETE_SUFFIX;
1974
+ var init_kiroV3Files = __esm({
1975
+ "server/src/session/kiroV3Files.ts"() {
1976
+ "use strict";
1977
+ init_dist();
1978
+ init_config();
1979
+ init_paths2();
1980
+ PLACEHOLDER_TITLE = "New Session";
1981
+ HIDDEN_REASONING = "...";
1982
+ CONTEXT_TAIL_BYTES = 1024 * 1024;
1983
+ CONTEXT_CHUNK_BYTES = 16 * 1024;
1984
+ isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1985
+ str2 = (v) => typeof v === "string" ? v : void 0;
1986
+ V2_STORE = "cli";
1987
+ START_SUFFIX = "-sub-agent-start";
1988
+ COMPLETE_SUFFIX = "-sub-agent-complete";
1716
1989
  }
1990
+ });
1991
+
1992
+ // server/src/session/kiroFiles.ts
1993
+ import fs10 from "node:fs/promises";
1994
+ import path11 from "node:path";
1995
+ async function readPersistedSession(sessionId) {
1996
+ const dir = await v3SessionDir(sessionId);
1997
+ return dir ? readV3Session(dir) : null;
1998
+ }
1999
+ async function readPersistedSessions(sessionIds) {
2000
+ const dirs = await v3SessionDirs();
2001
+ const out = /* @__PURE__ */ new Map();
2002
+ await Promise.all(
2003
+ sessionIds.filter(isValidSessionId).map(async (id) => {
2004
+ const dir = dirs.get(id);
2005
+ const found = dir ? await readV3Session(dir) : null;
2006
+ if (found) out.set(id, found);
2007
+ })
2008
+ );
2009
+ return out;
2010
+ }
2011
+ async function deletePersistedSession(sessionId) {
2012
+ const dir = await v3SessionDir(sessionId);
2013
+ if (dir) await fs10.rm(dir, { recursive: true, force: true });
2014
+ }
2015
+ async function isLoadable(sessionId) {
2016
+ return await v3SessionDir(sessionId) !== void 0;
2017
+ }
2018
+ async function readEntries(sessionId) {
2019
+ const dir = await v3SessionDir(sessionId);
2020
+ return dir ? readV3Entries(path11.join(dir, "messages.jsonl")) : [];
2021
+ }
2022
+ async function sessionCredits(sessionId) {
2023
+ const dir = await v3SessionDir(sessionId);
2024
+ return dir ? readV3Credits(dir) : void 0;
2025
+ }
2026
+ async function promptCount(sessionId) {
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 }] : []);
1717
2031
  }
1718
2032
  function endingItems(e) {
1719
2033
  const prefix = `te${e.id}-`;
@@ -1725,110 +2039,96 @@ function endingItems(e) {
1725
2039
  );
1726
2040
  return items;
1727
2041
  }
1728
- async function hydrateTranscript(sessionId, attachments, endings = []) {
2042
+ function mergeTranscript(entries, attachments, endings) {
1729
2043
  const items = [];
1730
2044
  let userOrdinal = 0;
1731
- const toolsById = /* @__PURE__ */ new Map();
1732
2045
  const waiting = [...endings];
1733
2046
  let replaced = false;
1734
2047
  const unrecorded = (e) => [
1735
2048
  { type: "message", message: { id: `te${e.id}-u`, role: "user", text: e.prompt, timestamp: e.startedAt } },
1736
2049
  ...endingItems(e)
1737
2050
  ];
1738
- for (const entry of await readJsonlEntries(sessionId)) {
1739
- const content = contentBlocks(entry.data.content);
1740
- const textOf = (kind) => content.filter((c) => c.kind === kind).map(blockText).join("");
1741
- const baseId = entry.data.message_id ?? `${items.length}`;
1742
- const ts = entry.data.meta?.timestamp;
1743
- const pushMsg = (msg) => items.push({ type: "message", message: msg });
1744
- if (entry.kind === "Prompt") {
1745
- replaced = false;
1746
- const text2 = stripAttachmentsLine(textOf("text"));
1747
- let matched;
1748
- if (ts !== void 0) {
1749
- const at = ts * 1e3;
1750
- matched = waiting.find(
1751
- (e) => e.prompt.trim() === text2.trim() && at >= e.startedAt - STAMP_PAD_MS && at <= e.endedAt + STAMP_PAD_MS
1752
- );
1753
- while (waiting.length && waiting[0] !== matched && waiting[0].endedAt < at - STAMP_PAD_MS) {
1754
- items.push(...unrecorded(waiting.shift()));
1755
- }
1756
- if (matched) waiting.splice(waiting.indexOf(matched), 1);
1757
- }
1758
- const attached = attachments?.get(userOrdinal);
1759
- userOrdinal++;
1760
- if (text2.trim() || attached?.length)
1761
- pushMsg({ id: `u-${baseId}`, role: "user", text: text2, timestamp: ts, attachments: attached });
1762
- if (matched) {
1763
- items.push(...endingItems(matched));
1764
- replaced = true;
1765
- }
1766
- } else if (entry.kind === "AssistantMessage") {
1767
- if (replaced) continue;
1768
- const thinking = textOf("thinking");
1769
- if (thinking.trim())
1770
- pushMsg({ id: `t-${baseId}`, role: "thinking", text: thinking.trim(), timestamp: ts });
1771
- const text2 = textOf("text");
1772
- if (text2.trim())
1773
- pushMsg({ id: `a-${baseId}`, role: "assistant", text: text2, timestamp: ts });
1774
- for (const c of content) {
1775
- const d = toolUseData(c);
1776
- if (!d) continue;
1777
- const tool = {
1778
- id: d.toolUseId,
1779
- name: d.name,
1780
- title: d.name ?? d.toolUseId,
1781
- status: "completed",
1782
- input: d.input,
1783
- content: []
1784
- };
1785
- toolsById.set(d.toolUseId, tool);
1786
- items.push({ type: "tool_call", tool });
1787
- }
1788
- } else if (entry.kind === "ToolResults") {
1789
- if (replaced) continue;
1790
- for (const c of content) {
1791
- const d = toolResultData(c);
1792
- if (!d) continue;
1793
- const tool = toolsById.get(d.toolUseId);
1794
- if (!tool) continue;
1795
- tool.status = d.status === "error" ? "failed" : "completed";
1796
- tool.content = renderableBlocks(d.content);
2051
+ for (const entry of entries) {
2052
+ if (entry.kind === "item") {
2053
+ if (!replaced || entry.item.type === "compaction") items.push(entry.item);
2054
+ continue;
2055
+ }
2056
+ replaced = false;
2057
+ const text2 = stripAttachmentsLine(entry.text);
2058
+ let matched;
2059
+ const at = entry.atMs;
2060
+ if (at !== void 0) {
2061
+ matched = waiting.find(
2062
+ (e) => e.prompt.trim() === text2.trim() && at >= e.startedAt - STAMP_PAD_MS && at <= e.endedAt + STAMP_PAD_MS
2063
+ );
2064
+ while (waiting.length && waiting[0] !== matched && waiting[0].endedAt < at - STAMP_PAD_MS) {
2065
+ items.push(...unrecorded(waiting.shift()));
1797
2066
  }
1798
- } else if (entry.kind === "Compaction") {
1799
- const summary = entry.data.summary ?? "";
1800
- if (summary.trim())
1801
- items.push({ type: "compaction", id: `c-${baseId}`, summary, timestamp: ts });
2067
+ if (matched) waiting.splice(waiting.indexOf(matched), 1);
2068
+ }
2069
+ const attached = attachments?.get(userOrdinal);
2070
+ userOrdinal++;
2071
+ if (text2.trim() || attached?.length) {
2072
+ items.push({ type: "message", message: { id: `u-${entry.id}`, role: "user", text: text2, timestamp: entry.timestamp, attachments: attached, messageId: entry.id } });
2073
+ }
2074
+ if (matched) {
2075
+ items.push(...endingItems(matched));
2076
+ replaced = true;
1802
2077
  }
1803
2078
  }
1804
2079
  for (const e of waiting) items.push(...unrecorded(e));
1805
2080
  return items;
1806
2081
  }
1807
- var isRecord, STAMP_PAD_MS;
2082
+ async function hydrateTranscript(sessionId, attachments, endings = []) {
2083
+ return mergeTranscript(await readEntries(sessionId), attachments, endings);
2084
+ }
2085
+ async function listSubExecutions(sessionId) {
2086
+ const dir = await v3SessionDir(sessionId);
2087
+ return dir ? readSubExecutions(dir) : null;
2088
+ }
2089
+ async function hydrateSubExecution(sessionId, subagentId) {
2090
+ const dir = await v3SessionDir(sessionId);
2091
+ const file = dir ? subExecutionFile(dir, subagentId) : void 0;
2092
+ if (!file) return null;
2093
+ try {
2094
+ await fs10.access(file);
2095
+ } catch {
2096
+ return null;
2097
+ }
2098
+ return mergeTranscript(await readV3Entries(file), void 0, []);
2099
+ }
2100
+ var STAMP_PAD_MS;
1808
2101
  var init_kiroFiles = __esm({
1809
2102
  "server/src/session/kiroFiles.ts"() {
1810
2103
  "use strict";
1811
2104
  init_dist();
1812
- init_config();
1813
2105
  init_paths2();
1814
- isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2106
+ init_kiroV3Files();
1815
2107
  STAMP_PAD_MS = 2e3;
1816
2108
  }
1817
2109
  });
1818
2110
 
1819
2111
  // server/src/session/chatStore.ts
1820
- var ChatStore;
2112
+ var COLUMNS, rowOf, ChatStore;
1821
2113
  var init_chatStore = __esm({
1822
2114
  "server/src/session/chatStore.ts"() {
1823
2115
  "use strict";
1824
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
+ });
1825
2125
  ChatStore = class {
1826
2126
  all() {
1827
- 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);
1828
2128
  }
1829
2129
  get(chatId) {
1830
- const r = db().prepare("SELECT chat_id, session_id, title, cwd FROM chats WHERE chat_id = ?").get(chatId);
1831
- 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;
1832
2132
  }
1833
2133
  create(chatId) {
1834
2134
  db().prepare("INSERT INTO chats (chat_id) VALUES (?) ON CONFLICT(chat_id) DO NOTHING").run(chatId);
@@ -1855,6 +2155,11 @@ var init_chatStore = __esm({
1855
2155
  setCwd(chatId, cwd) {
1856
2156
  this.write(chatId, "cwd", cwd);
1857
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
+ }
1858
2163
  /** What was attached to one prompt, keyed by ordinal (see db.ts). */
1859
2164
  setAttachments(chatId, ordinal, files) {
1860
2165
  if (files.length === 0) return;
@@ -1914,6 +2219,27 @@ var init_chatStore = __esm({
1914
2219
  }
1915
2220
  return out;
1916
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
+ }
1917
2243
  remove(chatId) {
1918
2244
  db().prepare("DELETE FROM chats WHERE chat_id = ?").run(chatId);
1919
2245
  db().prepare("DELETE FROM message_attachments WHERE chat_id = ?").run(chatId);
@@ -1933,50 +2259,18 @@ var init_chatStore = __esm({
1933
2259
  }
1934
2260
  });
1935
2261
 
1936
- // server/src/session/subagentLinks.ts
1937
- var SubagentLinkStore;
1938
- var init_subagentLinks = __esm({
1939
- "server/src/session/subagentLinks.ts"() {
1940
- "use strict";
1941
- init_db();
1942
- SubagentLinkStore = class {
1943
- /** Record (or correct) one child's link. Idempotent: a repeat call with the same
1944
- * values is a no-op, and the row always reflects the latest call to resolve it. */
1945
- record(childSessionId, parentSessionId, toolCallId, stageName) {
1946
- db().prepare(
1947
- `INSERT INTO subagent_links (child_session_id, parent_session_id, tool_call_id, stage_name)
1948
- VALUES (?, ?, ?, ?)
1949
- ON CONFLICT(child_session_id) DO UPDATE SET
1950
- parent_session_id = excluded.parent_session_id,
1951
- tool_call_id = excluded.tool_call_id,
1952
- stage_name = excluded.stage_name`
1953
- ).run(childSessionId, parentSessionId, toolCallId, stageName);
1954
- }
1955
- /** Every link recorded for one parent session's children, keyed by child session id. */
1956
- forParent(parentSessionId) {
1957
- const rows = db().prepare(
1958
- "SELECT child_session_id, tool_call_id, stage_name FROM subagent_links WHERE parent_session_id = ?"
1959
- ).all(parentSessionId);
1960
- const out = /* @__PURE__ */ new Map();
1961
- for (const r of rows) out.set(r.child_session_id, { toolCallId: r.tool_call_id, stageName: r.stage_name });
1962
- return out;
1963
- }
1964
- };
1965
- }
1966
- });
1967
-
1968
2262
  // server/src/session/SessionManager.ts
1969
- import fs10 from "node:fs";
1970
- import path11 from "node:path";
2263
+ import fs11 from "node:fs";
2264
+ import path12 from "node:path";
1971
2265
  function resolveCwd(input) {
1972
2266
  const raw = input?.trim();
1973
- const abs = raw ? path11.resolve(config.defaultCwd, raw) : config.defaultCwd;
2267
+ const abs = raw ? path12.resolve(config.defaultCwd, raw) : config.defaultCwd;
1974
2268
  if (!isWithinRoot(config.fileRoot, abs)) {
1975
2269
  throw new Error(`Working directory is outside the allowed root: ${abs}`);
1976
2270
  }
1977
2271
  let stat;
1978
2272
  try {
1979
- stat = fs10.statSync(abs);
2273
+ stat = fs11.statSync(abs);
1980
2274
  } catch {
1981
2275
  stat = void 0;
1982
2276
  }
@@ -1984,49 +2278,102 @@ function resolveCwd(input) {
1984
2278
  throw new Error(`Working directory path is a file, not a directory: ${abs}`);
1985
2279
  }
1986
2280
  if (!stat) {
1987
- fs10.mkdirSync(abs, { recursive: true });
2281
+ fs11.mkdirSync(abs, { recursive: true });
1988
2282
  }
1989
2283
  return abs;
1990
2284
  }
1991
- function mapNotification(n) {
1992
- switch (n.method) {
1993
- case "session/update":
1994
- return { kind: "session_update", update: n.params.update };
1995
- case KIRO_NOTIFICATIONS.metadata:
1996
- return { kind: "metadata", params: n.params };
1997
- case KIRO_NOTIFICATIONS.compactionStatus:
1998
- return { kind: "compaction", params: n.params };
2285
+ function kiroMeta(update) {
2286
+ return update._meta?.kiro;
2287
+ }
2288
+ function sessionInfoEvent(sessionId, update) {
2289
+ const info = update._meta?.kiro;
2290
+ switch (info?.kind) {
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;
2309
+ case "summarization_started":
2310
+ return { kind: "compaction", params: { sessionId, status: { type: "started" }, summary: null } };
2311
+ case "summarization_completed":
2312
+ return {
2313
+ kind: "compaction",
2314
+ params: { sessionId, status: { type: "completed" }, summary: info.conversationSummary ?? null }
2315
+ };
2316
+ case "summarization_failed":
2317
+ return { kind: "compaction", params: { sessionId, status: { type: "failed" }, summary: null } };
1999
2318
  default:
2000
2319
  return null;
2001
2320
  }
2002
2321
  }
2003
- function notificationSessionId(n) {
2004
- const params = n.params;
2005
- return params?.sessionId;
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
+ };
2006
2328
  }
2007
- function subagentCallStages(update) {
2008
- if (update.sessionUpdate !== "tool_call") return null;
2009
- const meta = update._meta;
2010
- if (meta?.kiro?.toolName !== "subagent") return null;
2011
- const toolCallId = update.toolCallId;
2012
- const rawInput = update.rawInput;
2013
- if (typeof toolCallId !== "string" || !Array.isArray(rawInput?.stages)) return null;
2014
- const stages = rawInput.stages.filter(
2015
- (s) => typeof s === "object" && s !== null && typeof s.name === "string"
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
2016
2333
  );
2017
- return { toolCallId, stages };
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
+ };
2354
+ }
2355
+ function subExecutionSummary(sub) {
2356
+ return {
2357
+ sessionId: sub.sessionId,
2358
+ stageName: sub.name,
2359
+ toolCallId: sub.toolCallId,
2360
+ status: sub.status,
2361
+ activity: sub.stopped ? "Stopped" : void 0,
2362
+ createdAt: sub.createdAt,
2363
+ updatedAt: sub.updatedAt
2364
+ };
2018
2365
  }
2019
2366
  function newerOf(a, b) {
2020
2367
  return a && a.localeCompare(b) > 0 ? a : b;
2021
2368
  }
2022
2369
  function firstPromptText(transcript) {
2023
- 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);
2024
2371
  return first?.type === "message" ? first.message.text : void 0;
2025
2372
  }
2026
2373
  function isGhost(s, hasFile) {
2027
2374
  return !hasFile && s.hasBeenLive && !s.proc;
2028
2375
  }
2029
- var CANCEL_GRACE_MS, Session, TRANSCRIPT_PAGE_SIZE, SessionManager;
2376
+ var CANCEL_GRACE_MS, Session, TRANSCRIPT_UPDATES, TRANSCRIPT_PAGE_SIZE, SessionManager;
2030
2377
  var init_SessionManager = __esm({
2031
2378
  "server/src/session/SessionManager.ts"() {
2032
2379
  "use strict";
@@ -2034,17 +2381,15 @@ var init_SessionManager = __esm({
2034
2381
  init_config();
2035
2382
  init_agents();
2036
2383
  init_chats();
2384
+ init_kiroModels();
2037
2385
  init_paths2();
2038
2386
  init_KiroProcess();
2039
2387
  init_EventStore();
2040
2388
  init_TurnState();
2041
2389
  init_TurnRecorder();
2042
2390
  init_SubagentTracker();
2043
- init_subagentResult();
2044
- init_subagentFallbackMatch();
2045
2391
  init_kiroFiles();
2046
2392
  init_chatStore();
2047
- init_subagentLinks();
2048
2393
  CANCEL_GRACE_MS = 1e4;
2049
2394
  Session = class {
2050
2395
  sessionId;
@@ -2055,8 +2400,21 @@ var init_SessionManager = __esm({
2055
2400
  cwd;
2056
2401
  agentId;
2057
2402
  modelId;
2403
+ effortLevel;
2058
2404
  currentModeId;
2059
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;
2060
2418
  title = "";
2061
2419
  createdAt = (/* @__PURE__ */ new Date()).toISOString();
2062
2420
  updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2093,21 +2451,24 @@ var init_SessionManager = __esm({
2093
2451
  return event;
2094
2452
  }
2095
2453
  };
2454
+ TRANSCRIPT_UPDATES = /* @__PURE__ */ new Set(["agent_message_chunk", "agent_thought_chunk", "tool_call", "tool_call_update"]);
2096
2455
  TRANSCRIPT_PAGE_SIZE = 80;
2097
2456
  SessionManager = class {
2098
2457
  sessions = /* @__PURE__ */ new Map();
2099
2458
  log;
2100
2459
  store = new ChatStore();
2101
- subagentLinks = new SubagentLinkStore();
2102
- // Children the title fallback already tried and could not match. Matching hydrates the
2103
- // whole parent transcript (145 MB in one chat), so each child is tried once per process.
2104
- unmatchedChildren = /* @__PURE__ */ new Map();
2105
2460
  spawnProcess;
2106
2461
  cancelGraceMs;
2462
+ defaultModel;
2463
+ defaultEffort;
2464
+ defaultAgent;
2107
2465
  constructor(log, opts = {}) {
2108
2466
  this.log = log;
2109
2467
  this.spawnProcess = opts.spawn ?? ((o, l) => new KiroProcess(o, l));
2110
2468
  this.cancelGraceMs = opts.cancelGraceMs ?? CANCEL_GRACE_MS;
2469
+ this.defaultModel = opts.defaultModel ?? (async () => void 0);
2470
+ this.defaultEffort = opts.defaultEffort ?? (async () => void 0);
2471
+ this.defaultAgent = opts.defaultAgent ?? (async () => void 0);
2111
2472
  }
2112
2473
  /** What a session is called, from every read path. */
2113
2474
  titleOf(chat, parts) {
@@ -2115,7 +2476,7 @@ var init_SessionManager = __esm({
2115
2476
  override: chat.title ?? void 0,
2116
2477
  kiroTitle: parts.kiroTitle,
2117
2478
  firstPrompt: parts.firstPrompt,
2118
- folder: isManagedWorkspace(parts.cwd) ? void 0 : path11.basename(parts.cwd)
2479
+ folder: isManagedWorkspace(parts.cwd) ? void 0 : path12.basename(parts.cwd)
2119
2480
  });
2120
2481
  }
2121
2482
  renameChat(chatId, title) {
@@ -2124,6 +2485,9 @@ var init_SessionManager = __esm({
2124
2485
  const s = this.sessions.get(this.store.sessionIdForChat(chatId) ?? "");
2125
2486
  if (s) s.title = clean || s.title;
2126
2487
  }
2488
+ pinChat(chatId, pinned) {
2489
+ if (!this.store.setPinned(chatId, pinned)) throw new Error(`Unknown chat: ${chatId}`);
2490
+ }
2127
2491
  // A live process was spawned with the old cwd, so it's disposed and the next
2128
2492
  // turn respawns.
2129
2493
  async setChatCwd(chatId, input) {
@@ -2173,7 +2537,7 @@ var init_SessionManager = __esm({
2173
2537
  }
2174
2538
  /** The reload itself, called with the session's reload claim held. */
2175
2539
  async replaceProcess(chatId, s) {
2176
- if (!await hasRecordedTurns(s.sessionId)) {
2540
+ if (!await isLoadable(s.sessionId)) {
2177
2541
  throw new Error(
2178
2542
  "kiro has not saved this session yet. Send a message first, then reload."
2179
2543
  );
@@ -2187,9 +2551,9 @@ var init_SessionManager = __esm({
2187
2551
  });
2188
2552
  }
2189
2553
  s.availableModes = [];
2554
+ invalidateAgents();
2190
2555
  await this.ensureProc(s);
2191
2556
  s.lastActivity = Date.now();
2192
- invalidateAgents();
2193
2557
  this.log.info({ sessionId: s.sessionId, cwd: s.cwd }, "session reloaded");
2194
2558
  return this.getDetail(chatId);
2195
2559
  }
@@ -2234,6 +2598,7 @@ var init_SessionManager = __esm({
2234
2598
  s.agentId = persisted.agentId;
2235
2599
  s.currentModeId = persisted.agentId;
2236
2600
  s.modelId = persisted.modelId;
2601
+ s.effortLevel = persisted.effortLevel;
2237
2602
  s.createdAt = persisted.createdAt;
2238
2603
  s.updatedAt = persisted.updatedAt;
2239
2604
  s.markLive();
@@ -2243,31 +2608,70 @@ var init_SessionManager = __esm({
2243
2608
  }
2244
2609
  wire(s, proc) {
2245
2610
  proc.on("notification", (n) => {
2246
- if (s.replaying) return;
2247
- if (n.method === KIRO_NOTIFICATIONS.subagentListUpdate) {
2248
- const params = n.params;
2249
- const resolved = s.subagents.apply(params.subagents);
2250
- for (const link of resolved) {
2251
- this.subagentLinks.record(link.sessionId, s.sessionId, link.toolCallId, link.stageName);
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);
2252
2617
  }
2253
- s.record({ kind: "subagents_changed", subagents: s.subagents.list() });
2254
2618
  return;
2255
2619
  }
2256
- const notifSessionId = notificationSessionId(n);
2257
- if (!notifSessionId || notifSessionId === s.sessionId) {
2258
- const payload = mapNotification(n);
2259
- if (!payload) return;
2260
- if (payload.kind === "session_update") {
2261
- const call = subagentCallStages(payload.update);
2262
- if (call) s.subagents.callStarted(call.toolCallId, call.stages);
2263
- const update = payload.update;
2264
- if (update.sessionUpdate === "tool_call_update" && typeof update.toolCallId === "string" && (update.status === "completed" || update.status === "failed")) {
2265
- s.subagents.callFinished(update.toolCallId);
2266
- }
2620
+ if (n.method === KIRO_AGENT_NOT_FOUND) {
2621
+ this.log.warn({ sessionId: s.sessionId, detail: n.params }, "kiro could not use the requested agent");
2622
+ return;
2623
+ }
2624
+ if (n.method !== ACP_METHODS.sessionUpdate) return;
2625
+ const params = n.params;
2626
+ if (!params?.update || params.sessionId && params.sessionId !== s.sessionId) return;
2627
+ const { update } = params;
2628
+ const meta = kiroMeta(update);
2629
+ if (meta?.replay) return;
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;
2267
2635
  }
2268
- s.record(payload);
2636
+ if (info?.kind === "turn_completion") {
2637
+ s.turnStats = turnStatsOf(info);
2638
+ return;
2639
+ }
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
+ }
2649
+ if (payload) s.record(payload);
2650
+ return;
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
+ }
2665
+ if (!TRANSCRIPT_UPDATES.has(update.sessionUpdate)) return;
2666
+ const subagentId = meta?.agentSubtaskId;
2667
+ if (subagentId && meta.kind !== "agent-subtask") {
2668
+ const title = update.sessionUpdate === "tool_call" ? update.title : void 0;
2669
+ if (s.subagents.progressed(subagentId, title)) this.recordSubagents(s);
2269
2670
  return;
2270
2671
  }
2672
+ if (subagentId) this.trackSubagentCall(s, subagentId, update);
2673
+ if (s.lastTurn) s.lastTurn.output = true;
2674
+ s.record({ kind: "session_update", update });
2271
2675
  });
2272
2676
  proc.on("exit", (code, signal) => {
2273
2677
  if (s.proc !== proc) return;
@@ -2276,28 +2680,64 @@ var init_SessionManager = __esm({
2276
2680
  s.running = false;
2277
2681
  });
2278
2682
  }
2683
+ /** Follows the tool call that starts a subagent, from its start to its result. */
2684
+ trackSubagentCall(s, subagentId, update) {
2685
+ const call = update;
2686
+ if (typeof call.toolCallId !== "string") return;
2687
+ let changed = false;
2688
+ if (call.sessionUpdate === "tool_call") {
2689
+ const name = typeof call.rawInput?.name === "string" ? call.rawInput.name : call.title ?? subagentId;
2690
+ changed = s.subagents.started(subagentId, call.toolCallId, name);
2691
+ } else if (call.status === "completed" || call.status === "failed") {
2692
+ changed = s.subagents.finished(call.toolCallId, call.status === "completed");
2693
+ }
2694
+ if (changed) this.recordSubagents(s);
2695
+ }
2696
+ recordSubagents(s) {
2697
+ s.record({ kind: "subagents_changed", subagents: s.subagents.list() });
2698
+ }
2279
2699
  /** Gets or spawns the kiro process for a session. */
2280
2700
  async ensureProc(s) {
2281
2701
  if (s.proc) return s.proc;
2282
2702
  if (s.spawning) return s.spawning;
2283
2703
  s.spawning = (async () => {
2284
2704
  await this.ensureCapacity();
2285
- const proc = this.spawnProcess(
2286
- { cwd: s.cwd, agent: s.agentId, model: s.modelId },
2287
- this.log
2288
- );
2705
+ const proc = this.spawnProcess({ cwd: s.cwd }, this.log);
2289
2706
  this.wire(s, proc);
2290
2707
  await proc.initialize();
2291
2708
  let res;
2292
2709
  if (s.hasBeenLive) {
2293
2710
  s.replaying = true;
2294
2711
  try {
2295
- res = await proc.loadSession({ sessionId: s.sessionId, cwd: s.cwd, mcpServers: [] });
2712
+ res = await proc.loadSession({
2713
+ sessionId: s.sessionId,
2714
+ cwd: s.cwd,
2715
+ mcpServers: [],
2716
+ ...s.modelId ? { _meta: { kiro: { modelId: s.modelId } } } : {}
2717
+ });
2718
+ } finally {
2719
+ s.replaying = false;
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 };
2296
2731
  } finally {
2297
2732
  s.replaying = false;
2298
2733
  }
2734
+ res = await this.applyForkSettings(proc, s, res);
2299
2735
  } else {
2300
- res = await proc.newSession({ cwd: s.cwd, mcpServers: [] });
2736
+ res = await proc.newSession({
2737
+ cwd: s.cwd,
2738
+ mcpServers: [],
2739
+ _meta: { kiro: { modeId: s.agentId, modelId: s.modelId, effortLevel: s.effortLevel } }
2740
+ });
2301
2741
  }
2302
2742
  if (!s.hasBeenLive && res.sessionId !== s.sessionId) {
2303
2743
  this.sessions.delete(s.sessionId);
@@ -2307,6 +2747,13 @@ var init_SessionManager = __esm({
2307
2747
  s.availableModes = res.modes.availableModes;
2308
2748
  s.currentModeId = res.modes.currentModeId;
2309
2749
  s.agentId = res.modes.currentModeId ?? s.agentId;
2750
+ const settings = sessionSettings(res.configOptions);
2751
+ if (settings) {
2752
+ s.modelId = settings.modelId ?? s.modelId;
2753
+ s.effortLevel = settings.effortLevel ?? void 0;
2754
+ }
2755
+ rememberModes(res.modes.availableModes);
2756
+ rememberKiroModels(res.configOptions);
2310
2757
  s.markLive();
2311
2758
  s.proc = proc;
2312
2759
  s.spawning = void 0;
@@ -2319,6 +2766,25 @@ var init_SessionManager = __esm({
2319
2766
  throw err;
2320
2767
  }
2321
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
+ }
2322
2788
  async ensureCapacity() {
2323
2789
  const liveIds = [...this.sessions.values()].filter((s) => s.proc);
2324
2790
  if (liveIds.length < config.maxLiveSessions) return;
@@ -2338,33 +2804,116 @@ var init_SessionManager = __esm({
2338
2804
  this.log.warn("at capacity but all processes are busy");
2339
2805
  }
2340
2806
  }
2341
- /** 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. */
2342
2809
  async createChat(opts) {
2343
2810
  const chatId = isValidChatId(opts.chatId) ? opts.chatId : crypto.randomUUID();
2344
2811
  const cwd = opts.freshWorkspace ? createChatWorkspace(chatId) : resolveCwd(opts.cwd);
2345
- const tempId = `pending-${Date.now()}-${Math.floor(this.sessions.size)}`;
2346
- 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()}`;
2347
2825
  const s = new Session(tempId, store, cwd);
2348
- s.agentId = opts.agentId ?? config.defaultAgent;
2349
- s.currentModeId = s.agentId;
2350
- s.modelId = opts.modelId;
2826
+ s.agentId = init.agentId;
2827
+ s.currentModeId = init.agentId;
2828
+ s.modelId = init.modelId;
2829
+ s.effortLevel = init.effortLevel;
2830
+ s.forkOf = init.forkOf;
2351
2831
  this.sessions.set(tempId, s);
2352
2832
  try {
2353
2833
  await this.ensureProc(s);
2354
2834
  } catch (err) {
2355
- this.evict(s.sessionId);
2835
+ this.sessions.delete(s.sessionId);
2836
+ s.proc?.dispose();
2356
2837
  throw err;
2357
2838
  }
2358
- this.store.create(chatId);
2359
- this.store.bindSession(chatId, s.sessionId);
2360
- const name = sanitizeTitle(opts.title ?? "") || (opts.freshWorkspace ? "" : path11.basename(s.cwd));
2361
- if (name) {
2362
- this.store.setTitle(chatId, name);
2363
- 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;
2364
2870
  }
2365
- 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);
2884
+ }
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);
2366
2915
  }
2367
- async runPrompt(chatId, content, attachments) {
2916
+ async runPrompt(chatId, content, attachments, opts = {}) {
2368
2917
  const s = await this.ensureOpen(chatId);
2369
2918
  await this.settleReload(s);
2370
2919
  if (s.running) throw new Error("A turn is already running for this session");
@@ -2377,13 +2926,9 @@ var init_SessionManager = __esm({
2377
2926
  s.running = false;
2378
2927
  throw err;
2379
2928
  }
2380
- if (!this.store.getTitle(chatId)) {
2381
- const title = titleFromPrompt(content);
2382
- if (title) {
2383
- this.store.setTitle(chatId, title);
2384
- s.title = title;
2385
- }
2386
- }
2929
+ if (opts.retry) await this.dropFailedTurn(chatId, s, proc);
2930
+ s.lastTurn = { output: false, failed: false };
2931
+ s.turnStats = void 0;
2387
2932
  let recorded;
2388
2933
  if (attachments?.length) {
2389
2934
  const ordinal = await promptCount(s.sessionId);
@@ -2391,11 +2936,18 @@ var init_SessionManager = __esm({
2391
2936
  recorded = attachments;
2392
2937
  }
2393
2938
  s.record({ kind: "turn_started", prompt: content, attachments: recorded });
2394
- proc.prompt({ sessionId: s.sessionId, prompt: content }).then((res) => {
2939
+ proc.prompt({ sessionId: s.sessionId, prompt: content }).then(async (res) => {
2395
2940
  if (res.stopReason === "cancelled") this.saveEnding(chatId, s.recorder.finish({ kind: "interrupted" }));
2396
2941
  else s.recorder.discard();
2397
- s.record({ kind: "turn_ended", stopReason: res.stopReason });
2398
- }).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);
2399
2951
  if (s.cancelRequested) {
2400
2952
  this.saveEnding(chatId, s.recorder.finish({ kind: "interrupted" }));
2401
2953
  s.record({ kind: "turn_ended", stopReason: "cancelled" });
@@ -2418,6 +2970,67 @@ ${tail}` : err.message;
2418
2970
  if (s.subagents.turnEnded()) s.record({ kind: "subagents_changed", subagents: s.subagents.list() });
2419
2971
  });
2420
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
+ }
2421
3034
  saveEnding(chatId, ending) {
2422
3035
  if (!ending) return;
2423
3036
  try {
@@ -2467,12 +3080,65 @@ ${tail}` : err.message;
2467
3080
  s.modelId = modelId;
2468
3081
  s.lastActivity = Date.now();
2469
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
+ }
3119
+ /** Runs one of kiro's slash commands. Compaction is the only one Casper offers. It
3120
+ * returns once kiro has started; its progress arrives as compaction events. */
2470
3121
  async execCommand(chatId, command) {
3122
+ if (command.replace(/^\//, "") !== "compact") throw new Error(`Unsupported command: ${command}`);
2471
3123
  const s = await this.ensureOpen(chatId);
2472
3124
  await this.settleReload(s);
3125
+ if (s.turnState.get().compacting) throw new Error("The conversation is already being compacted");
2473
3126
  const proc = await this.ensureProc(s);
2474
- await proc.execCommand(s.sessionId, command);
2475
3127
  s.lastActivity = Date.now();
3128
+ const status = (type) => ({
3129
+ kind: "compaction",
3130
+ params: { sessionId: s.sessionId, status: { type }, summary: null }
3131
+ });
3132
+ s.record(status("started"));
3133
+ proc.compact(s.sessionId).then(
3134
+ () => {
3135
+ if (s.turnState.get().compacting && s.proc === proc) s.record(status("completed"));
3136
+ },
3137
+ (err) => {
3138
+ this.log.warn({ err, sessionId: s.sessionId }, "compaction failed");
3139
+ if (s.turnState.get().compacting && s.proc === proc) s.record(status("failed"));
3140
+ }
3141
+ );
2476
3142
  }
2477
3143
  /** The one place a ChatSummary is assembled, since kiro's file and Casper's
2478
3144
  * live state each hold part of the truth. */
@@ -2483,7 +3149,8 @@ ${tail}` : err.message;
2483
3149
  chatId: chat.chatId,
2484
3150
  sessionId: live?.sessionId ?? chat.sessionId ?? void 0,
2485
3151
  title: this.titleOf(chat, {
2486
- 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,
2487
3154
  firstPrompt: transcript && firstPromptText(transcript),
2488
3155
  cwd
2489
3156
  }),
@@ -2493,20 +3160,22 @@ ${tail}` : err.message;
2493
3160
  liveness: live?.proc ? "live" : "dormant",
2494
3161
  agentId: live?.agentId ?? persisted?.agentId,
2495
3162
  modelId: live?.modelId ?? persisted?.modelId,
3163
+ effortLevel: live ? live.effortLevel : persisted?.effortLevel,
2496
3164
  running: live?.running ?? false,
3165
+ pinned: chat.pinned,
2497
3166
  // A dormant session falls back to kiro's file.
2498
3167
  contextUsagePercentage: snap?.contextUsagePercentage || persisted?.contextUsagePercentage
2499
3168
  };
2500
3169
  }
2501
3170
  async listChats() {
2502
3171
  const rows = this.store.all();
2503
- const files = await Promise.all(
2504
- rows.map((r) => r.sessionId ? readPersistedSession(r.sessionId) : null)
3172
+ const files = await readPersistedSessions(
3173
+ rows.map((r) => r.sessionId).filter((id) => !!id)
2505
3174
  );
2506
3175
  const out = [];
2507
- rows.forEach((row, i) => {
3176
+ rows.forEach((row) => {
2508
3177
  const live = row.sessionId ? this.sessions.get(row.sessionId) : void 0;
2509
- const persisted = files[i] ?? void 0;
3178
+ const persisted = row.sessionId ? files.get(row.sessionId) : void 0;
2510
3179
  if (live && isGhost(live, !!persisted)) {
2511
3180
  this.evict(live.sessionId);
2512
3181
  return;
@@ -2549,68 +3218,36 @@ ${tail}` : err.message;
2549
3218
  const end = Math.max(start, Math.min(start + limit, transcript.length));
2550
3219
  return transcript.slice(start, end);
2551
3220
  }
2552
- /**
2553
- * A chat's subagents: live ones from the tracker, joined with children found on disk.
2554
- * A child the tracker never saw falls back to a recorded link or title matching
2555
- * (subagentFallbackMatch.ts).
2556
- */
3221
+ /** A chat's subagents: the ones kiro recorded in the session's file, and live ones from
3222
+ * the tracker. A live row wins over the file's until the file records how it ended. */
2557
3223
  async getSubagents(chatId) {
2558
3224
  const sessionId = this.sessionIdOf(chatId);
2559
- const s = this.sessions.get(sessionId);
2560
- const live = s ? s.subagents.list() : [];
2561
- const liveToolCalls = new Set(live.map((a) => a.toolCallId).filter((id) => !!id));
2562
- const pending = s ? [...liveToolCalls].flatMap((id) => s.subagents.listForCall(id).filter((a) => a.status === "pending")) : [];
2563
- const onDisk = await listChildSessions(sessionId);
2564
- const links = this.subagentLinks.forParent(sessionId);
2565
- const liveIds = new Set(live.map((a) => a.sessionId));
2566
- const tried = this.unmatchedChildren.get(sessionId) ?? /* @__PURE__ */ new Set();
2567
- const unresolved = onDisk.filter(
2568
- (c) => !liveIds.has(c.sessionId) && !links.has(c.sessionId) && !tried.has(c.sessionId)
2569
- );
2570
- const fallback = unresolved.length ? matchSubagentsFallback(subagentCallsIn(await hydrateTranscript(sessionId)), unresolved) : [];
2571
- const fallbackByChild = new Map(fallback.map((m) => [m.sessionId, m]));
2572
- for (const m of fallback) this.subagentLinks.record(m.sessionId, sessionId, m.toolCallId, m.stageName);
2573
- for (const c of unresolved) if (!fallbackByChild.has(c.sessionId)) tried.add(c.sessionId);
2574
- this.unmatchedChildren.set(sessionId, tried);
3225
+ const live = this.sessions.get(sessionId)?.subagents.list() ?? [];
2575
3226
  const byId = /* @__PURE__ */ new Map();
2576
- for (const child of onDisk) {
2577
- const link = links.get(child.sessionId) ?? fallbackByChild.get(child.sessionId);
2578
- byId.set(child.sessionId, {
2579
- sessionId: child.sessionId,
2580
- stageName: link?.stageName ?? (child.title || child.sessionId),
2581
- toolCallId: link?.toolCallId,
2582
- status: "completed",
2583
- createdAt: child.createdAt,
2584
- updatedAt: child.updatedAt
2585
- });
3227
+ const finished = /* @__PURE__ */ new Set();
3228
+ for (const sub of await listSubExecutions(sessionId) ?? []) {
3229
+ byId.set(sub.sessionId, subExecutionSummary(sub));
3230
+ if (!sub.stopped) finished.add(sub.sessionId);
2586
3231
  }
2587
- for (const a of live) byId.set(a.sessionId, a);
2588
- const startedThenPending = [...byId.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2589
- return [...startedThenPending, ...pending];
3232
+ for (const a of live) if (!finished.has(a.sessionId)) byId.set(a.sessionId, a);
3233
+ return [...byId.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2590
3234
  }
2591
3235
  /** One subagent's own transcript, hydrated the same way a chat's is. */
2592
- // Polled while a subagent runs, so it reads only the child's own files.
2593
- async getSubagentDetail(chatId, childSessionId) {
3236
+ // Polled while a subagent runs, so it reads only the subagent's own files.
3237
+ async getSubagentDetail(chatId, subagentId) {
2594
3238
  const parentSessionId = this.sessionIdOf(chatId);
2595
- const child = await readChildSession(childSessionId, parentSessionId);
2596
- if (!child) throw new Error(`Unknown subagent: ${childSessionId}`);
2597
- const live = this.sessions.get(parentSessionId)?.subagents.list().find((a) => a.sessionId === childSessionId);
2598
- const link = this.subagentLinks.forParent(parentSessionId).get(childSessionId);
2599
- const summary = live ?? {
2600
- sessionId: childSessionId,
2601
- stageName: link?.stageName ?? (child.title || childSessionId),
2602
- toolCallId: link?.toolCallId,
2603
- status: "completed",
2604
- createdAt: child.createdAt,
2605
- updatedAt: child.updatedAt
2606
- };
2607
- const transcript = showSummaryAsAnswer(await hydrateTranscript(childSessionId));
2608
- return { subagent: summary, transcript };
3239
+ const live = this.sessions.get(parentSessionId)?.subagents.get(subagentId);
3240
+ const recorded = (await listSubExecutions(parentSessionId))?.find((x) => x.sessionId === subagentId);
3241
+ if (!live && !recorded) throw new Error(`Unknown subagent: ${subagentId}`);
3242
+ const transcript = await hydrateSubExecution(parentSessionId, subagentId) ?? [];
3243
+ const subagent = recorded && (!live || !recorded.stopped) ? subExecutionSummary(recorded) : live;
3244
+ return { subagent, transcript };
2609
3245
  }
2610
3246
  /** One projection, so a dormant session and a live one cannot disagree. */
2611
- buildDetail(chat, s, transcript, persisted) {
3247
+ buildDetail(chat, s, hydrated, persisted) {
3248
+ const { transcript, head } = this.resumePoint(s, hydrated);
2612
3249
  return {
2613
- summary: this.summaryOf(chat, s, persisted, transcript),
3250
+ summary: this.summaryOf(chat, s, persisted, hydrated),
2614
3251
  modes: s?.availableModes ?? [],
2615
3252
  currentModeId: s?.currentModeId ?? persisted?.agentId,
2616
3253
  transcript: transcript.slice(-TRANSCRIPT_PAGE_SIZE),
@@ -2619,16 +3256,19 @@ ${tail}` : err.message;
2619
3256
  ...emptyObservabilitySnapshot(),
2620
3257
  contextUsagePercentage: persisted?.contextUsagePercentage ?? 0
2621
3258
  },
2622
- head: s ? this.replayHead(s, transcript) : 0
3259
+ head
2623
3260
  };
2624
3261
  }
2625
3262
  /**
2626
- * The cursor a reconnecting client starts from: the store head, unless the in-flight
2627
- * turn isn't in kiro's jsonl yet, in which case rewind to its turn_started.
3263
+ * Where a reconnecting client picks up: the transcript, and the cursor its events replay
3264
+ * after. A turn in flight replays whole from its turn_started and is left out of the
3265
+ * transcript, since kiro writes the prompt at once but a tool call only once it ends, so
3266
+ * kiro's copy of the turn lacks whatever is still running.
2628
3267
  */
2629
- replayHead(s, transcript) {
3268
+ resumePoint(s, transcript) {
3269
+ if (!s) return { transcript, head: 0 };
2630
3270
  const head = s.store.head();
2631
- if (!s.running) return head;
3271
+ if (!s.running) return { transcript, head };
2632
3272
  const { events } = s.store.getSince(0);
2633
3273
  let started;
2634
3274
  for (let i = events.length - 1; i >= 0; i--) {
@@ -2637,18 +3277,19 @@ ${tail}` : err.message;
2637
3277
  break;
2638
3278
  }
2639
3279
  }
2640
- if (!started || started.payload.kind !== "turn_started") return head;
3280
+ if (!started || started.payload.kind !== "turn_started") return { transcript, head };
2641
3281
  const promptText = stripAttachmentsLine(
2642
3282
  started.payload.prompt.filter((b) => b.type === "text").map((b) => b.text).join("")
2643
3283
  ).trim();
3284
+ let cut = transcript.length;
2644
3285
  for (let i = transcript.length - 1; i >= 0; i--) {
2645
3286
  const it = transcript[i];
2646
- if (it.type === "message" && it.message.role === "user") {
2647
- if (promptText && it.message.text.trim() === promptText) return head;
3287
+ if (it.type === "message" && it.message.role === "user" && !it.message.steer) {
3288
+ if (promptText && it.message.text.trim() === promptText) cut = i;
2648
3289
  break;
2649
3290
  }
2650
3291
  }
2651
- return started.seq - 1;
3292
+ return { transcript: transcript.slice(0, cut), head: started.seq - 1 };
2652
3293
  }
2653
3294
  evict(sessionId) {
2654
3295
  const s = this.sessions.get(sessionId);
@@ -2671,14 +3312,159 @@ ${tail}` : err.message;
2671
3312
  await removeChatDir(chatId);
2672
3313
  if (!sessionId) return;
2673
3314
  this.evict(sessionId);
2674
- 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
+ });
2675
3322
  setTimeout(() => void deletePersistedSession(sessionId).catch(() => {
2676
3323
  }), 2500).unref?.();
2677
3324
  }
2678
3325
  disposeAll() {
2679
3326
  for (const id of [...this.sessions.keys()]) this.evict(id);
2680
3327
  }
2681
- };
3328
+ };
3329
+ }
3330
+ });
3331
+
3332
+ // server/src/session/models.ts
3333
+ import { execFile as execFile2 } from "node:child_process";
3334
+ import { promisify as promisify2 } from "node:util";
3335
+ async function configuredDefault() {
3336
+ try {
3337
+ const { stdout } = await execFileAsync2(config.kiroBin, [
3338
+ "settings",
3339
+ "chat.defaultModel",
3340
+ "-f",
3341
+ "json"
3342
+ ]);
3343
+ const value = JSON.parse(stdout);
3344
+ return typeof value === "string" && value ? value : void 0;
3345
+ } catch {
3346
+ return void 0;
3347
+ }
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
+ }
3383
+ async function listModels(log) {
3384
+ if (!cache2) await loadModels(log);
3385
+ const known = kiroModels();
3386
+ return cache2.map((m) => withKiroDetails(m, known));
3387
+ }
3388
+ async function loadModels(log) {
3389
+ const { stdout } = await execFileAsync2(
3390
+ config.kiroBin,
3391
+ ["chat", "--list-models", "-f", "json"],
3392
+ { maxBuffer: 4 * 1024 * 1024 }
3393
+ );
3394
+ const parsed = JSON.parse(stdout);
3395
+ const [configured, efforts] = await Promise.all([configuredDefault(), configuredEfforts()]);
3396
+ userEfforts = efforts;
3397
+ const preferred = parsed.models.some((m) => m.model_id === configured) ? configured : parsed.default_model;
3398
+ cache2 = parsed.models.map((m, i) => ({
3399
+ modelId: m.model_id,
3400
+ modelName: m.model_name,
3401
+ description: m.description,
3402
+ contextWindowTokens: m.context_window_tokens,
3403
+ rateMultiplier: m.rate_multiplier,
3404
+ rateUnit: m.rate_unit,
3405
+ isDefault: preferred ? m.model_id === preferred : m.default_model ?? i === 0
3406
+ }));
3407
+ log.info({ count: cache2.length, default: preferred }, "loaded model list");
3408
+ }
3409
+ async function defaultModelId(log) {
3410
+ try {
3411
+ return (await listModels(log)).find((m) => m.isDefault)?.modelId;
3412
+ } catch {
3413
+ return void 0;
3414
+ }
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
+ }
3451
+ async function kiroVersion() {
3452
+ try {
3453
+ const { stdout } = await execFileAsync2(config.kiroBin, ["--version"]);
3454
+ return stdout.trim();
3455
+ } catch {
3456
+ return void 0;
3457
+ }
3458
+ }
3459
+ var execFileAsync2, cache2, userEfforts;
3460
+ var init_models = __esm({
3461
+ "server/src/session/models.ts"() {
3462
+ "use strict";
3463
+ init_config();
3464
+ init_kiroModels();
3465
+ execFileAsync2 = promisify2(execFile2);
3466
+ cache2 = null;
3467
+ userEfforts = /* @__PURE__ */ new Map();
2682
3468
  }
2683
3469
  });
2684
3470
 
@@ -2828,81 +3614,157 @@ var init_auth = __esm({
2828
3614
  }
2829
3615
  });
2830
3616
 
2831
- // server/src/session/models.ts
2832
- import { execFile as execFile2 } from "node:child_process";
2833
- import { promisify as promisify2 } from "node:util";
2834
- async function configuredDefault() {
3617
+ // server/src/routes/models.ts
3618
+ function registerModelRoutes(app) {
3619
+ app.get("/api/models", async () => {
3620
+ const models = await listModels(app.log);
3621
+ return { models };
3622
+ });
3623
+ }
3624
+ var init_models2 = __esm({
3625
+ "server/src/routes/models.ts"() {
3626
+ "use strict";
3627
+ init_models();
3628
+ }
3629
+ });
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) {
2835
3640
  try {
2836
- const { stdout } = await execFileAsync2(config.kiroBin, [
2837
- "settings",
2838
- "chat.defaultModel",
2839
- "-f",
2840
- "json"
2841
- ]);
2842
- const value = JSON.parse(stdout);
2843
- return typeof value === "string" && value ? value : void 0;
3641
+ const { stdout } = await execFileAsync3(config.kiroBin, ["settings", key, "-f", "json"]);
3642
+ return JSON.parse(stdout);
2844
3643
  } catch {
2845
3644
  return void 0;
2846
3645
  }
2847
3646
  }
2848
- async function listModels(log) {
2849
- if (cache2) return cache2;
2850
- const { stdout } = await execFileAsync2(
2851
- config.kiroBin,
2852
- ["chat", "--list-models", "-f", "json"],
2853
- { maxBuffer: 4 * 1024 * 1024 }
2854
- );
2855
- const parsed = JSON.parse(stdout);
2856
- const configured = await configuredDefault();
2857
- const preferred = parsed.models.some((m) => m.model_id === configured) ? configured : parsed.default_model;
2858
- cache2 = parsed.models.map((m, i) => ({
2859
- modelId: m.model_id,
2860
- modelName: m.model_name,
2861
- description: m.description,
2862
- contextWindowTokens: m.context_window_tokens,
2863
- rateMultiplier: m.rate_multiplier,
2864
- rateUnit: m.rate_unit,
2865
- isDefault: preferred ? m.model_id === preferred : m.default_model ?? i === 0
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
2866
3687
  }));
2867
- log.info({ count: cache2.length, default: preferred }, "loaded model list");
2868
- return cache2;
2869
3688
  }
2870
- async function kiroVersion() {
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;
2871
3735
  try {
2872
- const { stdout } = await execFileAsync2(config.kiroBin, ["--version"]);
2873
- return stdout.trim();
3736
+ entries = (await fs12.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);
2874
3737
  } catch {
2875
- return void 0;
3738
+ return [];
2876
3739
  }
2877
- }
2878
- var execFileAsync2, cache2;
2879
- var init_models = __esm({
2880
- "server/src/session/models.ts"() {
2881
- "use strict";
2882
- init_config();
2883
- execFileAsync2 = promisify2(execFile2);
2884
- cache2 = null;
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
+ }
2885
3748
  }
2886
- });
2887
-
2888
- // server/src/routes/models.ts
2889
- function registerModelRoutes(app) {
2890
- app.get("/api/models", async () => {
2891
- const models = await listModels(app.log);
2892
- return { models };
2893
- });
3749
+ return out;
2894
3750
  }
2895
- var init_models2 = __esm({
2896
- "server/src/routes/models.ts"() {
3751
+ var execFileAsync3, mcpFile, agentCache, strings, SERVER_NAME;
3752
+ var init_kiroTools = __esm({
3753
+ "server/src/session/kiroTools.ts"() {
2897
3754
  "use strict";
2898
- init_models();
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}$/;
2899
3761
  }
2900
3762
  });
2901
3763
 
2902
3764
  // server/src/routes/agents.ts
2903
3765
  function registerAgentRoutes(app) {
2904
3766
  app.get("/api/agents", async () => {
2905
- return { agents: await listAgents(), defaultAgentId: config.defaultAgent };
3767
+ return { agents: await listAgents(), defaultAgentId: await configuredDefaultAgent() ?? config.defaultAgent };
2906
3768
  });
2907
3769
  }
2908
3770
  var init_agents2 = __esm({
@@ -2910,12 +3772,13 @@ var init_agents2 = __esm({
2910
3772
  "use strict";
2911
3773
  init_agents();
2912
3774
  init_config();
3775
+ init_kiroTools();
2913
3776
  }
2914
3777
  });
2915
3778
 
2916
3779
  // server/src/util/confinedFile.ts
2917
- import fs11 from "node:fs/promises";
2918
- import path12 from "node:path";
3780
+ import fs13 from "node:fs/promises";
3781
+ import path14 from "node:path";
2919
3782
  async function resolveConfined(input, spec) {
2920
3783
  let target = null;
2921
3784
  for (const root of spec.lexical) {
@@ -2931,7 +3794,7 @@ async function resolveConfined(input, spec) {
2931
3794
  if (!real) return { ok: false, ...await spec.notFound() };
2932
3795
  let stat;
2933
3796
  try {
2934
- stat = await fs11.stat(real);
3797
+ stat = await fs13.stat(real);
2935
3798
  } catch {
2936
3799
  return { ok: false, ...await spec.notFound() };
2937
3800
  }
@@ -2970,7 +3833,7 @@ async function resolveChatPath(sessions, sessionId, requestedPath, require2) {
2970
3833
  async function workspaceNotFound(cwd) {
2971
3834
  let missing;
2972
3835
  try {
2973
- missing = !(await fs11.stat(cwd)).isDirectory();
3836
+ missing = !(await fs13.stat(cwd)).isDirectory();
2974
3837
  } catch {
2975
3838
  missing = true;
2976
3839
  }
@@ -2990,7 +3853,7 @@ async function resolveAbsolutePath(input, require2) {
2990
3853
  });
2991
3854
  }
2992
3855
  async function classifyDirent(parentReal, entry, realRoots) {
2993
- const absolute = path12.join(parentReal, entry.name);
3856
+ const absolute = path14.join(parentReal, entry.name);
2994
3857
  if (entry.isDirectory()) return { kind: "directory", real: absolute };
2995
3858
  if (entry.isFile()) return { kind: "file", real: absolute };
2996
3859
  if (!entry.isSymbolicLink()) return null;
@@ -2998,7 +3861,7 @@ async function classifyDirent(parentReal, entry, realRoots) {
2998
3861
  const real = await realConfineToRoot(root, absolute);
2999
3862
  if (!real) continue;
3000
3863
  try {
3001
- const stat = await fs11.stat(real);
3864
+ const stat = await fs13.stat(real);
3002
3865
  if (stat.isDirectory()) return { kind: "directory", real };
3003
3866
  if (stat.isFile()) return { kind: "file", real };
3004
3867
  } catch {
@@ -3016,7 +3879,7 @@ var init_confinedFile = __esm({
3016
3879
  });
3017
3880
 
3018
3881
  // server/src/util/filekind.ts
3019
- import path13 from "node:path";
3882
+ import path15 from "node:path";
3020
3883
  function mimeForExt(ext) {
3021
3884
  return MIME_MAP[ext.toLowerCase()] ?? "application/octet-stream";
3022
3885
  }
@@ -3036,7 +3899,7 @@ function looksBinary(buf) {
3036
3899
  return control / buf.length > 0.3;
3037
3900
  }
3038
3901
  function classifyKind(name) {
3039
- const ext = path13.extname(name).toLowerCase();
3902
+ const ext = path15.extname(name).toLowerCase();
3040
3903
  if (ext in IMAGE_EXTS) return "image";
3041
3904
  if (TEXT_EXTS.has(ext)) return "text";
3042
3905
  return "binary";
@@ -3146,9 +4009,9 @@ var init_filekind = __esm({
3146
4009
  });
3147
4010
 
3148
4011
  // server/src/routes/filePreview.ts
3149
- import fs12 from "node:fs/promises";
4012
+ import fs14 from "node:fs/promises";
3150
4013
  import { createReadStream } from "node:fs";
3151
- import path14 from "node:path";
4014
+ import path16 from "node:path";
3152
4015
  function tooLargeForPreview(size, maxBytes) {
3153
4016
  return {
3154
4017
  error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
@@ -3175,7 +4038,7 @@ function hexdump(buf) {
3175
4038
  return lines.join("\n");
3176
4039
  }
3177
4040
  async function sendFilePreview(req, reply, realTarget, stat) {
3178
- const ext = path14.extname(realTarget).toLowerCase();
4041
+ const ext = path16.extname(realTarget).toLowerCase();
3179
4042
  const mime = mimeForExt(ext);
3180
4043
  const isImage2 = mime.startsWith("image/");
3181
4044
  const isPdf = mime === "application/pdf";
@@ -3220,7 +4083,7 @@ async function sendFilePreview(req, reply, realTarget, stat) {
3220
4083
  if (kind === "binary") {
3221
4084
  let fh;
3222
4085
  try {
3223
- fh = await fs12.open(realTarget, "r");
4086
+ fh = await fs14.open(realTarget, "r");
3224
4087
  const buf = Buffer.alloc(Math.min(HEXDUMP_BYTES, stat.size));
3225
4088
  const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
3226
4089
  const head = buf.subarray(0, bytesRead);
@@ -3264,9 +4127,9 @@ var init_filePreview = __esm({
3264
4127
  });
3265
4128
 
3266
4129
  // server/src/routes/fs.ts
3267
- import fs13 from "node:fs/promises";
4130
+ import fs15 from "node:fs/promises";
3268
4131
  import { createReadStream as createReadStream2 } from "node:fs";
3269
- import path15 from "node:path";
4132
+ import path17 from "node:path";
3270
4133
  function registerFsRoutes(app) {
3271
4134
  app.get(
3272
4135
  "/api/fs/dirs",
@@ -3274,10 +4137,10 @@ function registerFsRoutes(app) {
3274
4137
  const input = (req.query.path ?? "").trim();
3275
4138
  const base = config.defaultCwd;
3276
4139
  const endsWithSep = input.endsWith("/");
3277
- const resolved = input ? path15.resolve(base, input) : base;
3278
- const dir = endsWithSep || !input ? resolved : path15.dirname(resolved);
3279
- const prefix = endsWithSep || !input ? "" : path15.basename(resolved);
3280
- const targetKind = await fs13.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");
3281
4144
  const listing = await resolveAbsolutePath(dir, "directory");
3282
4145
  if (!listing.ok) {
3283
4146
  if (listing.status === 403) return replyWith(reply, listing);
@@ -3286,7 +4149,7 @@ function registerFsRoutes(app) {
3286
4149
  const realDir = listing.real;
3287
4150
  let entries = [];
3288
4151
  try {
3289
- const dirents = await fs13.readdir(realDir, { withFileTypes: true });
4152
+ const dirents = await fs15.readdir(realDir, { withFileTypes: true });
3290
4153
  const checks2 = await Promise.all(
3291
4154
  dirents.map(async (d) => {
3292
4155
  const target = await classifyDirent(realDir, d, absoluteRoots());
@@ -3297,7 +4160,7 @@ function registerFsRoutes(app) {
3297
4160
  entries = checks2.filter((name) => name !== null).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase())).sort((a, b) => {
3298
4161
  if (isDot(a) !== isDot(b)) return isDot(a) ? 1 : -1;
3299
4162
  return a.localeCompare(b);
3300
- }).slice(0, 500).map((name) => path15.join(dir, name));
4163
+ }).slice(0, 500).map((name) => path17.join(dir, name));
3301
4164
  } catch {
3302
4165
  entries = [];
3303
4166
  }
@@ -3312,7 +4175,7 @@ function registerFsRoutes(app) {
3312
4175
  reply.code(400);
3313
4176
  return { error: "path parameter is required" };
3314
4177
  }
3315
- if (!path15.isAbsolute(filePath)) {
4178
+ if (!path17.isAbsolute(filePath)) {
3316
4179
  reply.code(400);
3317
4180
  return { error: "path must be absolute" };
3318
4181
  }
@@ -3322,7 +4185,7 @@ function registerFsRoutes(app) {
3322
4185
  reply.header("Content-Type", "application/octet-stream");
3323
4186
  reply.header(
3324
4187
  "Content-Disposition",
3325
- `attachment; filename="${path15.basename(resolved.real).replace(/"/g, "")}"`
4188
+ `attachment; filename="${path17.basename(resolved.real).replace(/"/g, "")}"`
3326
4189
  );
3327
4190
  reply.header("Content-Length", resolved.stat.size);
3328
4191
  return reply.send(createReadStream2(resolved.real));
@@ -3352,8 +4215,8 @@ function registerChatRoutes(app, manager) {
3352
4215
  cwd: body.cwd,
3353
4216
  agentId: body.agentId,
3354
4217
  modelId: body.modelId,
4218
+ effortLevel: body.effortLevel,
3355
4219
  freshWorkspace: body.freshWorkspace,
3356
- title: body.title,
3357
4220
  // Not optional in practice: the client's chat already owns any file uploaded before
3358
4221
  // the session existed, so dropping it here strands those files under an id nothing
3359
4222
  // refers to again.
@@ -3412,6 +4275,47 @@ function registerChatRoutes(app, manager) {
3412
4275
  return { ok: true };
3413
4276
  }
3414
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
+ });
3415
4319
  app.post(
3416
4320
  "/api/chats/:id/mode",
3417
4321
  async (req) => {
@@ -3426,6 +4330,22 @@ function registerChatRoutes(app, manager) {
3426
4330
  return { ok: true };
3427
4331
  }
3428
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
+ );
3429
4349
  app.post(
3430
4350
  "/api/chats/:id/cwd",
3431
4351
  async (req, reply) => {
@@ -3493,9 +4413,9 @@ var init_subagents = __esm({
3493
4413
  });
3494
4414
 
3495
4415
  // server/src/routes/workspace.ts
3496
- import fs14 from "node:fs/promises";
4416
+ import fs16 from "node:fs/promises";
3497
4417
  import { createReadStream as createReadStream3 } from "node:fs";
3498
- import path16 from "node:path";
4418
+ import path18 from "node:path";
3499
4419
  function registerWorkspaceRoutes(app, manager) {
3500
4420
  app.get(
3501
4421
  "/api/chats/:id/tree",
@@ -3510,7 +4430,7 @@ function registerWorkspaceRoutes(app, manager) {
3510
4430
  const { cwd, relative, real: realTarget } = resolved;
3511
4431
  let dirents;
3512
4432
  try {
3513
- dirents = await fs14.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
4433
+ dirents = await fs16.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
3514
4434
  } catch {
3515
4435
  return replyWith(reply, await workspaceNotFound(cwd));
3516
4436
  }
@@ -3524,7 +4444,7 @@ function registerWorkspaceRoutes(app, manager) {
3524
4444
  continue;
3525
4445
  }
3526
4446
  try {
3527
- const stat = await fs14.stat(target.real);
4447
+ const stat = await fs16.stat(target.real);
3528
4448
  entries.push({
3529
4449
  name: d.name,
3530
4450
  path: entryRelative,
@@ -3553,8 +4473,8 @@ function registerWorkspaceRoutes(app, manager) {
3553
4473
  reply.code(413);
3554
4474
  return { error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 100 MB)` };
3555
4475
  }
3556
- const ext = path16.extname(realTarget);
3557
- const filename = path16.basename(realTarget);
4476
+ const ext = path18.extname(realTarget);
4477
+ const filename = path18.basename(realTarget);
3558
4478
  const asciiName = filename.replace(/[^\w.\-]/g, "_");
3559
4479
  reply.header("Content-Type", mimeForExt(ext));
3560
4480
  reply.header(
@@ -3589,27 +4509,27 @@ var init_workspace = __esm({
3589
4509
 
3590
4510
  // server/src/routes/uploads.ts
3591
4511
  import { createReadStream as createReadStream4, createWriteStream } from "node:fs";
3592
- import fs15 from "node:fs/promises";
3593
- import path17 from "node:path";
4512
+ import fs17 from "node:fs/promises";
4513
+ import path19 from "node:path";
3594
4514
  import crypto4 from "node:crypto";
3595
- import { execFile as execFile3 } from "node:child_process";
4515
+ import { execFile as execFile4 } from "node:child_process";
3596
4516
  import { pipeline } from "node:stream/promises";
3597
4517
  function sanitizeName(raw) {
3598
- const base = path17.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
4518
+ const base = path19.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
3599
4519
  if (!base || base === "." || base === "..") return `upload-${Date.now()}`;
3600
4520
  return base.replace(/^\.+/, "") || `upload-${Date.now()}`;
3601
4521
  }
3602
4522
  async function uniquePath(dir, name) {
3603
- const ext = path17.extname(name);
4523
+ const ext = path19.extname(name);
3604
4524
  const stem = name.slice(0, name.length - ext.length);
3605
4525
  let candidate = name;
3606
4526
  let n = 1;
3607
4527
  while (true) {
3608
4528
  try {
3609
- await fs15.access(path17.join(dir, candidate));
4529
+ await fs17.access(path19.join(dir, candidate));
3610
4530
  candidate = `${stem} (${n++})${ext}`;
3611
4531
  } catch {
3612
- return path17.join(dir, candidate);
4532
+ return path19.join(dir, candidate);
3613
4533
  }
3614
4534
  }
3615
4535
  }
@@ -3621,7 +4541,7 @@ function sha256File(absPath) {
3621
4541
  }
3622
4542
  function fileType(absPath) {
3623
4543
  return new Promise((resolve) => {
3624
- execFile3("file", ["-b", absPath], { timeout: 5e3 }, (err, stdout) => {
4544
+ execFile4("file", ["-b", absPath], { timeout: 5e3 }, (err, stdout) => {
3625
4545
  resolve(err ? "" : stdout.trim());
3626
4546
  });
3627
4547
  });
@@ -3629,7 +4549,7 @@ function fileType(absPath) {
3629
4549
  async function sampleStrings(absPath, limit = 40) {
3630
4550
  let fh;
3631
4551
  try {
3632
- fh = await fs15.open(absPath, "r");
4552
+ fh = await fs17.open(absPath, "r");
3633
4553
  const buf = Buffer.alloc(STRINGS_SCAN_BYTES);
3634
4554
  const { bytesRead } = await fh.read(buf, 0, STRINGS_SCAN_BYTES, 0);
3635
4555
  const out = [];
@@ -3661,7 +4581,7 @@ function registerUploadRoutes(app) {
3661
4581
  return { error: "Invalid chat id" };
3662
4582
  }
3663
4583
  const uploadDir = chatUploadsDir(req.params.chatId);
3664
- await fs15.mkdir(uploadDir, { recursive: true, mode: 448 });
4584
+ await fs17.mkdir(uploadDir, { recursive: true, mode: 448 });
3665
4585
  const results = [];
3666
4586
  const parts = req.files();
3667
4587
  for await (const part of parts) {
@@ -3675,24 +4595,24 @@ function registerUploadRoutes(app) {
3675
4595
  return { error: `Failed to store ${name}` };
3676
4596
  }
3677
4597
  if (part.file.truncated) {
3678
- await fs15.rm(dest, { force: true });
4598
+ await fs17.rm(dest, { force: true });
3679
4599
  reply.code(413);
3680
4600
  return {
3681
4601
  error: `${name} exceeds the max upload size (${Math.round(config.maxUploadBytes / 1024 / 1024)} MB)`
3682
4602
  };
3683
4603
  }
3684
- const stat = await fs15.stat(dest);
3685
- const ext = path17.extname(dest);
4604
+ const stat = await fs17.stat(dest);
4605
+ const ext = path19.extname(dest);
3686
4606
  const kind = classifyKind(dest);
3687
4607
  const uploaded = {
3688
- name: path17.basename(dest),
4608
+ name: path19.basename(dest),
3689
4609
  path: dest,
3690
4610
  size: stat.size,
3691
4611
  mimeType: mimeForExt(ext),
3692
4612
  kind
3693
4613
  };
3694
4614
  if (kind === "binary") {
3695
- const [type, sha2562, strings] = await Promise.all([
4615
+ const [type, sha2562, strings2] = await Promise.all([
3696
4616
  fileType(dest),
3697
4617
  sha256File(dest),
3698
4618
  sampleStrings(dest)
@@ -3700,7 +4620,7 @@ function registerUploadRoutes(app) {
3700
4620
  uploaded.triage = {
3701
4621
  fileType: type || void 0,
3702
4622
  sha256: sha2562,
3703
- strings: strings.length ? strings : void 0
4623
+ strings: strings2.length ? strings2 : void 0
3704
4624
  };
3705
4625
  }
3706
4626
  results.push(uploaded);
@@ -3722,6 +4642,78 @@ var init_uploads = __esm({
3722
4642
  }
3723
4643
  });
3724
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
+
3725
4717
  // server/src/routes/health.ts
3726
4718
  function registerHealthRoute(app, manager, startedAt) {
3727
4719
  app.get("/api/health", async () => {
@@ -3741,7 +4733,7 @@ var init_health = __esm({
3741
4733
  });
3742
4734
 
3743
4735
  // server/src/ws/dirWatchers.ts
3744
- import fs16 from "node:fs";
4736
+ import fs18 from "node:fs";
3745
4737
  function diffWatchSet(current, next) {
3746
4738
  const have = new Set(current);
3747
4739
  const want = new Set(next.slice(0, MAX_WATCHES));
@@ -3765,7 +4757,7 @@ function createDirWatchers(opts) {
3765
4757
  const target = await opts.resolve(relative);
3766
4758
  if (!target || closed) return;
3767
4759
  try {
3768
- const watcher = fs16.watch(target, { persistent: false }, () => {
4760
+ const watcher = fs18.watch(target, { persistent: false }, () => {
3769
4761
  const existing = timers.get(relative);
3770
4762
  if (existing) clearTimeout(existing);
3771
4763
  timers.set(
@@ -3823,7 +4815,7 @@ function handleConnection(socket, manager, chatId, startCursor) {
3823
4815
  return null;
3824
4816
  }
3825
4817
  },
3826
- onChange: (path20) => send(socket, { type: "fs_changed", path: path20 })
4818
+ onChange: (path22) => send(socket, { type: "fs_changed", path: path22 })
3827
4819
  });
3828
4820
  const forward = (event) => {
3829
4821
  if (event.seq <= cursor) return;
@@ -3874,13 +4866,20 @@ function handleConnection(socket, manager, chatId, startCursor) {
3874
4866
  case "watch_paths":
3875
4867
  return watchers.sync(msg.paths);
3876
4868
  case "prompt":
3877
- 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
+ );
3878
4875
  case "cancel":
3879
4876
  return ack("cancel", () => manager.cancel(chatId));
3880
4877
  case "set_mode":
3881
4878
  return ack("set_mode", () => manager.setMode(chatId, msg.modeId));
3882
4879
  case "set_model":
3883
4880
  return ack("set_model", () => manager.setModel(chatId, msg.modelId));
4881
+ case "set_effort":
4882
+ return ack("set_effort", () => manager.setEffort(chatId, msg.effortLevel));
3884
4883
  case "exec_command":
3885
4884
  return ack(
3886
4885
  "exec_command",
@@ -3956,7 +4955,7 @@ var init_gateway = __esm({
3956
4955
  });
3957
4956
 
3958
4957
  // server/src/app.ts
3959
- import fs17 from "node:fs";
4958
+ import fs19 from "node:fs";
3960
4959
  import Fastify from "fastify";
3961
4960
  import websocket from "@fastify/websocket";
3962
4961
  import fastifyStatic from "@fastify/static";
@@ -3975,7 +4974,11 @@ async function buildApp() {
3975
4974
  transport: isProd ? void 0 : { target: "pino/file", options: { destination: 2 } }
3976
4975
  }
3977
4976
  });
3978
- const manager = new SessionManager(logger);
4977
+ const manager = new SessionManager(logger, {
4978
+ defaultModel: () => defaultModelId(logger),
4979
+ defaultEffort: (modelId) => defaultEffortFor(modelId, logger),
4980
+ defaultAgent: configuredDefaultAgent
4981
+ });
3979
4982
  const startedAt = Date.now();
3980
4983
  await app.register(websocket, {
3981
4984
  options: { maxPayload: 16 * 1024 * 1024 }
@@ -3986,6 +4989,7 @@ async function buildApp() {
3986
4989
  await registerAuth(app);
3987
4990
  registerHealthRoute(app, manager, startedAt);
3988
4991
  registerModelRoutes(app);
4992
+ registerSettingsRoutes(app, manager, startedAt);
3989
4993
  registerAgentRoutes(app);
3990
4994
  registerFsRoutes(app);
3991
4995
  registerChatRoutes(app, manager);
@@ -3993,20 +4997,20 @@ async function buildApp() {
3993
4997
  registerWorkspaceRoutes(app, manager);
3994
4998
  registerUploadRoutes(app);
3995
4999
  registerWsGateway(app, manager);
3996
- if (fs17.existsSync(config.webDist)) {
5000
+ if (fs19.existsSync(config.webDist)) {
3997
5001
  await app.register(fastifyStatic, {
3998
5002
  root: config.webDist
3999
5003
  });
4000
5004
  app.addHook("onSend", async (req, reply) => {
4001
- const path20 = req.url.split("?")[0] ?? "";
4002
- if (path20 === "/manifest.json" || path20.startsWith("/icons/")) {
5005
+ const path22 = req.url.split("?")[0] ?? "";
5006
+ if (path22 === "/manifest.json" || path22.startsWith("/icons/")) {
4003
5007
  reply.header("cache-control", "public, max-age=300");
4004
5008
  }
4005
5009
  });
4006
5010
  app.setNotFoundHandler((req, reply) => {
4007
- const path20 = req.url.split("?")[0] ?? "";
4008
- const looksLikeAsset = path20.startsWith("/assets/") || /\.[a-zA-Z0-9]+$/.test(path20);
4009
- if (path20.startsWith("/api/") || path20.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) {
4010
5014
  reply.code(404).send({ error: "Not found" });
4011
5015
  return;
4012
5016
  }
@@ -4021,6 +5025,7 @@ var init_app = __esm({
4021
5025
  init_config();
4022
5026
  init_logger();
4023
5027
  init_SessionManager();
5028
+ init_models();
4024
5029
  init_auth();
4025
5030
  init_models2();
4026
5031
  init_agents2();
@@ -4029,6 +5034,8 @@ var init_app = __esm({
4029
5034
  init_subagents();
4030
5035
  init_workspace();
4031
5036
  init_uploads();
5037
+ init_settings2();
5038
+ init_kiroTools();
4032
5039
  init_health();
4033
5040
  init_gateway();
4034
5041
  }
@@ -4077,19 +5084,22 @@ var init_server = __esm({
4077
5084
  // server/src/cli/doctor.ts
4078
5085
  var doctor_exports = {};
4079
5086
  __export(doctor_exports, {
5087
+ agentFormat: () => agentFormat,
4080
5088
  doctor: () => doctor,
5089
+ kiroMajor: () => kiroMajor,
4081
5090
  mcpWiring: () => mcpWiring
4082
5091
  });
4083
5092
  import { execFileSync as execFileSync3 } from "node:child_process";
4084
- import fs18 from "node:fs";
4085
- import path18 from "node:path";
5093
+ import fs20 from "node:fs";
5094
+ import os4 from "node:os";
5095
+ import path20 from "node:path";
4086
5096
  function which(bin) {
4087
- if (bin.includes("/")) return fs18.existsSync(bin) ? bin : void 0;
4088
- for (const dir of (process.env.PATH ?? "").split(path18.delimiter)) {
5097
+ if (bin.includes("/")) return fs20.existsSync(bin) ? bin : void 0;
5098
+ for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
4089
5099
  if (dir === "") continue;
4090
- const candidate = path18.join(dir, bin);
5100
+ const candidate = path20.join(dir, bin);
4091
5101
  try {
4092
- fs18.accessSync(candidate, fs18.constants.X_OK);
5102
+ fs20.accessSync(candidate, fs20.constants.X_OK);
4093
5103
  return candidate;
4094
5104
  } catch {
4095
5105
  }
@@ -4113,11 +5123,28 @@ function mcpWiring(agentJson, resolve) {
4113
5123
  return { ok: false, detail: `${command || "(no command)"} not found - widgets are unavailable` };
4114
5124
  }
4115
5125
  const script = args.find((a) => a.endsWith(".js"));
4116
- if (script && !fs18.existsSync(script)) {
5126
+ if (script && !fs20.existsSync(script)) {
4117
5127
  return { ok: false, detail: `${script} is gone - delete the agent file and restart to rewrite it` };
4118
5128
  }
4119
5129
  return { ok: true, detail: script ?? `${command} ${args.join(" ")}` };
4120
5130
  }
5131
+ function agentFormat(agentJson) {
5132
+ let agent;
5133
+ try {
5134
+ agent = JSON.parse(agentJson);
5135
+ } catch {
5136
+ return { ok: false, detail: "the agent file is not valid JSON" };
5137
+ }
5138
+ const v2Fields = ["allowedTools", "toolsSettings"].filter((f) => f in agent);
5139
+ return v2Fields.length === 0 ? { ok: true, detail: "in the kiro V3 format" } : {
5140
+ ok: false,
5141
+ detail: `has ${v2Fields.join(" and ")}, so kiro V3 skips it - delete the file and restart to rewrite it`
5142
+ };
5143
+ }
5144
+ function kiroMajor(versionOutput) {
5145
+ const m = /(\d+)\.\d+\.\d+/.exec(versionOutput);
5146
+ return m ? Number(m[1]) : void 0;
5147
+ }
4121
5148
  function checks() {
4122
5149
  const out = [];
4123
5150
  out.push({
@@ -4133,7 +5160,10 @@ function checks() {
4133
5160
  } catch {
4134
5161
  version = "(version unavailable)";
4135
5162
  }
4136
- out.push({ ok: true, name: "kiro-cli", detail: `${version} at ${kiro}` });
5163
+ const major = kiroMajor(version);
5164
+ out.push(
5165
+ major === void 0 || major >= 3 ? { ok: true, name: "kiro-cli", detail: `${version} at ${kiro}` } : { ok: false, name: "kiro-cli", detail: `${version} at ${kiro} - Casper needs kiro-cli 3.0 or later` }
5166
+ );
4137
5167
  } else {
4138
5168
  out.push({
4139
5169
  ok: false,
@@ -4145,7 +5175,7 @@ function checks() {
4145
5175
  const settings = readSettings(config.configFile);
4146
5176
  const hasToken = typeof settings.token === "string" && settings.token !== "";
4147
5177
  out.push(
4148
- fs18.existsSync(config.configFile) ? {
5178
+ fs20.existsSync(config.configFile) ? {
4149
5179
  ok: hasToken,
4150
5180
  name: "settings",
4151
5181
  detail: hasToken ? `${config.configFile} (token set)` : `${config.configFile} exists but has no token - auth is DISABLED`
@@ -4155,8 +5185,8 @@ function checks() {
4155
5185
  detail: `${config.configFile} not found; a token is generated on first run`
4156
5186
  }
4157
5187
  );
4158
- if (fs18.existsSync(config.configFile)) {
4159
- const mode = fs18.statSync(config.configFile).mode & 511;
5188
+ if (fs20.existsSync(config.configFile)) {
5189
+ const mode = fs20.statSync(config.configFile).mode & 511;
4160
5190
  out.push({
4161
5191
  ok: mode === 384,
4162
5192
  name: "settings mode",
@@ -4164,10 +5194,10 @@ function checks() {
4164
5194
  });
4165
5195
  }
4166
5196
  try {
4167
- fs18.mkdirSync(config.casperDataDir, { recursive: true });
4168
- fs18.accessSync(config.casperDataDir, fs18.constants.W_OK);
4169
- const db2 = path18.join(config.casperDataDir, "casper.db");
4170
- const size = fs18.existsSync(db2) ? `, casper.db ${(fs18.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` : "";
4171
5201
  out.push({ ok: true, name: "data dir", detail: `${config.casperDataDir} writable${size}` });
4172
5202
  } catch (err) {
4173
5203
  out.push({
@@ -4178,22 +5208,25 @@ function checks() {
4178
5208
  });
4179
5209
  }
4180
5210
  out.push(
4181
- fs18.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
5211
+ fs20.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
4182
5212
  ok: false,
4183
5213
  fatal: true,
4184
5214
  name: "web app",
4185
5215
  detail: `${config.webDist} not found - the package is incomplete or CASPER_WEB_DIST is wrong`
4186
5216
  }
4187
5217
  );
4188
- const agent = path18.join(config.kiroSessionsDir, "..", "..", "agents", "casper.json");
4189
- const hasAgent = fs18.existsSync(agent);
5218
+ const agent = path20.join(os4.homedir(), ".kiro", "agents", "casper.json");
5219
+ const hasAgent = fs20.existsSync(agent);
4190
5220
  out.push({
4191
5221
  ok: hasAgent,
4192
5222
  name: "casper agent",
4193
5223
  detail: hasAgent ? agent : `${agent} missing - it is installed on first run`
4194
5224
  });
4195
5225
  if (hasAgent) {
4196
- const wiring = mcpWiring(fs18.readFileSync(agent, "utf8"), which);
5226
+ const json = fs20.readFileSync(agent, "utf8");
5227
+ const format = agentFormat(json);
5228
+ out.push({ ok: format.ok, name: "agent format", detail: format.detail });
5229
+ const wiring = mcpWiring(json, which);
4197
5230
  out.push({ ok: wiring.ok, name: "casper mcp", detail: wiring.detail });
4198
5231
  }
4199
5232
  return out;
@@ -4506,7 +5539,7 @@ function handleMessage(req, version) {
4506
5539
  return ok(id, {
4507
5540
  protocolVersion: asked ?? DEFAULT_PROTOCOL_VERSION,
4508
5541
  capabilities: { tools: { listChanged: false } },
4509
- serverInfo: { name: SERVER_NAME, version }
5542
+ serverInfo: { name: SERVER_NAME2, version }
4510
5543
  });
4511
5544
  }
4512
5545
  case "ping":
@@ -4526,14 +5559,14 @@ function handleMessage(req, version) {
4526
5559
  return { jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown method: ${method}` } };
4527
5560
  }
4528
5561
  }
4529
- var DEFAULT_PROTOCOL_VERSION, SERVER_NAME, MAX_WIDGET_CHARS, TOOLS;
5562
+ var DEFAULT_PROTOCOL_VERSION, SERVER_NAME2, MAX_WIDGET_CHARS, TOOLS;
4530
5563
  var init_protocol = __esm({
4531
5564
  "server/src/mcp/protocol.ts"() {
4532
5565
  "use strict";
4533
5566
  init_guidelines();
4534
5567
  init_choice();
4535
5568
  DEFAULT_PROTOCOL_VERSION = "2025-06-18";
4536
- SERVER_NAME = "casper-generative-ui";
5569
+ SERVER_NAME2 = "casper-generative-ui";
4537
5570
  MAX_WIDGET_CHARS = 256 * 1024;
4538
5571
  TOOLS = [
4539
5572
  {
@@ -4634,7 +5667,7 @@ function runMcpServer() {
4634
5667
  return;
4635
5668
  }
4636
5669
  try {
4637
- const res = handleMessage(req, VERSION);
5670
+ const res = handleMessage(req, VERSION2);
4638
5671
  if (res) process.stdout.write(JSON.stringify(res) + "\n");
4639
5672
  } catch (err) {
4640
5673
  process.stderr.write(`casper-mcp: ${err.message}
@@ -4652,19 +5685,19 @@ function runMcpServer() {
4652
5685
  });
4653
5686
  rl.on("close", () => process.exit(0));
4654
5687
  }
4655
- var VERSION;
5688
+ var VERSION2;
4656
5689
  var init_server2 = __esm({
4657
5690
  "server/src/mcp/server.ts"() {
4658
5691
  "use strict";
4659
5692
  init_protocol();
4660
- VERSION = true ? "0.15.7" : "dev";
5693
+ VERSION2 = true ? "0.17.0" : "dev";
4661
5694
  }
4662
5695
  });
4663
5696
 
4664
5697
  // server/src/cli/program.ts
4665
- import fs19 from "node:fs";
4666
- import os4 from "node:os";
4667
- import path19 from "node:path";
5698
+ import fs21 from "node:fs";
5699
+ import os5 from "node:os";
5700
+ import path21 from "node:path";
4668
5701
 
4669
5702
  // server/node_modules/commander/lib/error.js
4670
5703
  var CommanderError = class extends Error {
@@ -5255,8 +6288,8 @@ var Help = class {
5255
6288
  * @param {string} str
5256
6289
  * @returns {number}
5257
6290
  */
5258
- displayWidth(str2) {
5259
- return stripVTControlCharacters(str2).length;
6291
+ displayWidth(str3) {
6292
+ return stripVTControlCharacters(str3).length;
5260
6293
  }
5261
6294
  /**
5262
6295
  * Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.
@@ -5264,11 +6297,11 @@ var Help = class {
5264
6297
  * @param {string} str
5265
6298
  * @returns {string}
5266
6299
  */
5267
- styleTitle(str2) {
5268
- return str2;
6300
+ styleTitle(str3) {
6301
+ return str3;
5269
6302
  }
5270
- styleUsage(str2) {
5271
- return str2.split(" ").map((word) => {
6303
+ styleUsage(str3) {
6304
+ return str3.split(" ").map((word) => {
5272
6305
  if (word === "[options]") return this.styleOptionText(word);
5273
6306
  if (word === "[command]") return this.styleSubcommandText(word);
5274
6307
  if (word[0] === "[" || word[0] === "<")
@@ -5276,46 +6309,46 @@ var Help = class {
5276
6309
  return this.styleCommandText(word);
5277
6310
  }).join(" ");
5278
6311
  }
5279
- styleCommandDescription(str2) {
5280
- return this.styleDescriptionText(str2);
6312
+ styleCommandDescription(str3) {
6313
+ return this.styleDescriptionText(str3);
5281
6314
  }
5282
- styleOptionDescription(str2) {
5283
- return this.styleDescriptionText(str2);
6315
+ styleOptionDescription(str3) {
6316
+ return this.styleDescriptionText(str3);
5284
6317
  }
5285
- styleSubcommandDescription(str2) {
5286
- return this.styleDescriptionText(str2);
6318
+ styleSubcommandDescription(str3) {
6319
+ return this.styleDescriptionText(str3);
5287
6320
  }
5288
- styleArgumentDescription(str2) {
5289
- return this.styleDescriptionText(str2);
6321
+ styleArgumentDescription(str3) {
6322
+ return this.styleDescriptionText(str3);
5290
6323
  }
5291
- styleDescriptionText(str2) {
5292
- return str2;
6324
+ styleDescriptionText(str3) {
6325
+ return str3;
5293
6326
  }
5294
- styleOptionTerm(str2) {
5295
- return this.styleOptionText(str2);
6327
+ styleOptionTerm(str3) {
6328
+ return this.styleOptionText(str3);
5296
6329
  }
5297
- styleSubcommandTerm(str2) {
5298
- return str2.split(" ").map((word) => {
6330
+ styleSubcommandTerm(str3) {
6331
+ return str3.split(" ").map((word) => {
5299
6332
  if (word === "[options]") return this.styleOptionText(word);
5300
6333
  if (word[0] === "[" || word[0] === "<")
5301
6334
  return this.styleArgumentText(word);
5302
6335
  return this.styleSubcommandText(word);
5303
6336
  }).join(" ");
5304
6337
  }
5305
- styleArgumentTerm(str2) {
5306
- return this.styleArgumentText(str2);
6338
+ styleArgumentTerm(str3) {
6339
+ return this.styleArgumentText(str3);
5307
6340
  }
5308
- styleOptionText(str2) {
5309
- return str2;
6341
+ styleOptionText(str3) {
6342
+ return str3;
5310
6343
  }
5311
- styleArgumentText(str2) {
5312
- return str2;
6344
+ styleArgumentText(str3) {
6345
+ return str3;
5313
6346
  }
5314
- styleSubcommandText(str2) {
5315
- return str2;
6347
+ styleSubcommandText(str3) {
6348
+ return str3;
5316
6349
  }
5317
- styleCommandText(str2) {
5318
- return str2;
6350
+ styleCommandText(str3) {
6351
+ return str3;
5319
6352
  }
5320
6353
  /**
5321
6354
  * Calculate the pad width from the maximum term length.
@@ -5338,8 +6371,8 @@ var Help = class {
5338
6371
  * @param {string} str
5339
6372
  * @returns {boolean}
5340
6373
  */
5341
- preformatted(str2) {
5342
- return /\n[^\S\r\n]/.test(str2);
6374
+ preformatted(str3) {
6375
+ return /\n[^\S\r\n]/.test(str3);
5343
6376
  }
5344
6377
  /**
5345
6378
  * Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
@@ -5385,9 +6418,9 @@ ${itemIndentStr}`);
5385
6418
  * @param {number} width
5386
6419
  * @returns {string}
5387
6420
  */
5388
- boxWrap(str2, width) {
5389
- if (width < this.minWidthToWrap) return str2;
5390
- const rawLines = str2.split(/\r\n|\n/);
6421
+ boxWrap(str3, width) {
6422
+ if (width < this.minWidthToWrap) return str3;
6423
+ const rawLines = str3.split(/\r\n|\n/);
5391
6424
  const chunkPattern = /[\s]*[^\s]+/g;
5392
6425
  const wrappedLines = [];
5393
6426
  rawLines.forEach((line) => {
@@ -5677,9 +6710,9 @@ var DualOptions = class {
5677
6710
  return option.negate === (negativeValue === value);
5678
6711
  }
5679
6712
  };
5680
- function camelcase(str2) {
5681
- return str2.split("-").reduce((str3, word) => {
5682
- return str3 + word[0].toUpperCase() + word.slice(1);
6713
+ function camelcase(str3) {
6714
+ return str3.split("-").reduce((str4, word) => {
6715
+ return str4 + word[0].toUpperCase() + word.slice(1);
5683
6716
  });
5684
6717
  }
5685
6718
  function splitOptionFlags(flags) {
@@ -5839,14 +6872,14 @@ var Command = class _Command extends EventEmitter {
5839
6872
  this._showSuggestionAfterError = true;
5840
6873
  this._savedState = null;
5841
6874
  this._outputConfiguration = {
5842
- writeOut: (str2) => process2.stdout.write(str2),
5843
- writeErr: (str2) => process2.stderr.write(str2),
5844
- outputError: (str2, write) => write(str2),
6875
+ writeOut: (str3) => process2.stdout.write(str3),
6876
+ writeErr: (str3) => process2.stderr.write(str3),
6877
+ outputError: (str3, write) => write(str3),
5845
6878
  getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : void 0,
5846
6879
  getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : void 0,
5847
6880
  getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
5848
6881
  getErrHasColors: () => useColor() ?? (process2.stderr.isTTY && process2.stderr.hasColors?.()),
5849
- stripColor: (str2) => stripVTControlCharacters2(str2)
6882
+ stripColor: (str3) => stripVTControlCharacters2(str3)
5850
6883
  };
5851
6884
  this._hidden = false;
5852
6885
  this._helpOption = void 0;
@@ -7574,18 +8607,18 @@ Expecting one of '${allowedValues.join("', '")}'`);
7574
8607
  * @param {string} [description]
7575
8608
  * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
7576
8609
  */
7577
- version(str2, flags, description) {
7578
- if (str2 === void 0) return this._version;
7579
- this._version = str2;
8610
+ version(str3, flags, description) {
8611
+ if (str3 === void 0) return this._version;
8612
+ this._version = str3;
7580
8613
  flags = flags || "-V, --version";
7581
8614
  description = description || "output the version number";
7582
8615
  const versionOption = this.createOption(flags, description);
7583
8616
  this._versionOptionName = versionOption.attributeName();
7584
8617
  this._registerOption(versionOption);
7585
8618
  this.on("option:" + versionOption.name(), () => {
7586
- this._outputConfiguration.writeOut(`${str2}
8619
+ this._outputConfiguration.writeOut(`${str3}
7587
8620
  `);
7588
- this._exit(0, "commander.version", str2);
8621
+ this._exit(0, "commander.version", str3);
7589
8622
  });
7590
8623
  return this;
7591
8624
  }
@@ -7596,10 +8629,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
7596
8629
  * @param {object} [argsDescription]
7597
8630
  * @return {(string|Command)}
7598
8631
  */
7599
- description(str2, argsDescription) {
7600
- if (str2 === void 0 && argsDescription === void 0)
8632
+ description(str3, argsDescription) {
8633
+ if (str3 === void 0 && argsDescription === void 0)
7601
8634
  return this._description;
7602
- this._description = str2;
8635
+ this._description = str3;
7603
8636
  if (argsDescription) {
7604
8637
  this._argsDescription = argsDescription;
7605
8638
  }
@@ -7611,9 +8644,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
7611
8644
  * @param {string} [str]
7612
8645
  * @return {(string|Command)}
7613
8646
  */
7614
- summary(str2) {
7615
- if (str2 === void 0) return this._summary;
7616
- this._summary = str2;
8647
+ summary(str3) {
8648
+ if (str3 === void 0) return this._summary;
8649
+ this._summary = str3;
7617
8650
  return this;
7618
8651
  }
7619
8652
  /**
@@ -7661,8 +8694,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
7661
8694
  * @param {string} [str]
7662
8695
  * @return {(string|Command)}
7663
8696
  */
7664
- usage(str2) {
7665
- if (str2 === void 0) {
8697
+ usage(str3) {
8698
+ if (str3 === void 0) {
7666
8699
  if (this._usage) return this._usage;
7667
8700
  const args = this.registeredArguments.map((arg) => {
7668
8701
  return humanReadableArgName(arg);
@@ -7673,7 +8706,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
7673
8706
  this.registeredArguments.length ? args : []
7674
8707
  ).join(" ");
7675
8708
  }
7676
- this._usage = str2;
8709
+ this._usage = str3;
7677
8710
  return this;
7678
8711
  }
7679
8712
  /**
@@ -7682,9 +8715,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
7682
8715
  * @param {string} [str]
7683
8716
  * @return {(string|Command)}
7684
8717
  */
7685
- name(str2) {
7686
- if (str2 === void 0) return this._name;
7687
- this._name = str2;
8718
+ name(str3) {
8719
+ if (str3 === void 0) return this._name;
8720
+ this._name = str3;
7688
8721
  return this;
7689
8722
  }
7690
8723
  /**
@@ -7777,9 +8810,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
7777
8810
  * @param {string} [path]
7778
8811
  * @return {(string|null|Command)}
7779
8812
  */
7780
- executableDir(path20) {
7781
- if (path20 === void 0) return this._executableDir;
7782
- this._executableDir = path20;
8813
+ executableDir(path22) {
8814
+ if (path22 === void 0) return this._executableDir;
8815
+ this._executableDir = path22;
7783
8816
  return this;
7784
8817
  }
7785
8818
  /**
@@ -7818,17 +8851,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
7818
8851
  let hasColors;
7819
8852
  let helpWidth;
7820
8853
  if (error) {
7821
- baseWrite = (str2) => this._outputConfiguration.writeErr(str2);
8854
+ baseWrite = (str3) => this._outputConfiguration.writeErr(str3);
7822
8855
  hasColors = this._outputConfiguration.getErrHasColors();
7823
8856
  helpWidth = this._outputConfiguration.getErrHelpWidth();
7824
8857
  } else {
7825
- baseWrite = (str2) => this._outputConfiguration.writeOut(str2);
8858
+ baseWrite = (str3) => this._outputConfiguration.writeOut(str3);
7826
8859
  hasColors = this._outputConfiguration.getOutHasColors();
7827
8860
  helpWidth = this._outputConfiguration.getOutHelpWidth();
7828
8861
  }
7829
- const write = (str2) => {
7830
- if (!hasColors) str2 = this._outputConfiguration.stripColor(str2);
7831
- return baseWrite(str2);
8862
+ const write = (str3) => {
8863
+ if (!hasColors) str3 = this._outputConfiguration.stripColor(str3);
8864
+ return baseWrite(str3);
7832
8865
  };
7833
8866
  return { error, write, hasColors, helpWidth };
7834
8867
  }
@@ -8037,7 +9070,7 @@ var program = new Command();
8037
9070
  // server/src/cli/program.ts
8038
9071
  init_paths();
8039
9072
  init_settings();
8040
- var VERSION2 = true ? "0.15.7" : "dev";
9073
+ var VERSION3 = true ? "0.17.0" : "dev";
8041
9074
  async function bootstrap() {
8042
9075
  const file = configFilePath();
8043
9076
  const settings = readSettings(file);
@@ -8050,7 +9083,7 @@ async function bootstrap() {
8050
9083
  `);
8051
9084
  }
8052
9085
  const { installAgentFile: installAgentFile2 } = await Promise.resolve().then(() => (init_agentFile(), agentFile_exports));
8053
- const agent = installAgentFile2(os4.homedir(), dataDirPath());
9086
+ const agent = installAgentFile2(os5.homedir(), dataDirPath());
8054
9087
  if (agent.action === "installed" || agent.action === "updated") {
8055
9088
  process.stdout.write(`casper agent ${agent.action}: ${agent.target}
8056
9089
  `);
@@ -8083,8 +9116,8 @@ async function resetToken(value) {
8083
9116
  updateSettings(file, { token });
8084
9117
  process.stdout.write(`wrote a new token to ${file}
8085
9118
  `);
8086
- const dbFile = path19.join(dataDirPath(), "casper.db");
8087
- if (fs19.existsSync(dbFile)) {
9119
+ const dbFile = path21.join(dataDirPath(), "casper.db");
9120
+ if (fs21.existsSync(dbFile)) {
8088
9121
  const { LoginStore: LoginStore2 } = await Promise.resolve().then(() => (init_logins(), logins_exports));
8089
9122
  new LoginStore2().revokeAll();
8090
9123
  process.stdout.write("revoked all device sessions\n");
@@ -8101,7 +9134,7 @@ async function resetToken(value) {
8101
9134
  }
8102
9135
  function buildProgram() {
8103
9136
  const program2 = new Command();
8104
- 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", `
8105
9138
  Settings live in ${configFilePath()}.
8106
9139
  Update with: npm install -g @joeyshi12/casper`);
8107
9140
  program2.command("start").description("run the server in the foreground").action(async () => {