@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.
- package/README.md +39 -104
- package/dist/mcp.js +1 -1
- package/dist/server.js +1807 -774
- package/dist/web/assets/{abnfDiagram-N423BO3Z-jvSYyvNw.js → abnfDiagram-N423BO3Z-Bo2zpD_r.js} +1 -1
- package/dist/web/assets/{arc-BiHYHdUQ.js → arc-eMfj-v5r.js} +1 -1
- package/dist/web/assets/{architectureDiagram-T3A2C74G-4Eqc-u3C.js → architectureDiagram-T3A2C74G-SMny_PQB.js} +1 -1
- package/dist/web/assets/{blockDiagram-VBNYF7ZC-BcOcmlBN.js → blockDiagram-VBNYF7ZC-DSEqDPwv.js} +1 -1
- package/dist/web/assets/{c4Diagram-5PPSVZJV-BUudyYqU.js → c4Diagram-5PPSVZJV-CpqtaVod.js} +1 -1
- package/dist/web/assets/channel-BsxsiUCc.js +1 -0
- package/dist/web/assets/{chunk-2GRJ4B5K-bTMhoJSQ.js → chunk-2GRJ4B5K-AuFfmAbN.js} +1 -1
- package/dist/web/assets/{chunk-2Q5K7J3B-BYp6dfdp.js → chunk-2Q5K7J3B-iOn5T_gD.js} +1 -1
- package/dist/web/assets/{chunk-5RXB4S5H-Dg3ASlI5.js → chunk-5RXB4S5H-4T8n_Vjj.js} +1 -1
- package/dist/web/assets/{chunk-5VM5RSS4-B7uwpCHf.js → chunk-5VM5RSS4-BXZGGB8b.js} +1 -1
- package/dist/web/assets/{chunk-6Q2QTUOP-DVTiWdGW.js → chunk-6Q2QTUOP-CaHKdpZm.js} +1 -1
- package/dist/web/assets/{chunk-GF5L2VYU-C2LdpbyU.js → chunk-GF5L2VYU-JzGg4vgu.js} +1 -1
- package/dist/web/assets/{chunk-JWPE2WC7-CMX4-S8P.js → chunk-JWPE2WC7-CsXwzKxD.js} +1 -1
- package/dist/web/assets/{chunk-KBJHAD2P-D92STFuF.js → chunk-KBJHAD2P-4iRbBEYU.js} +1 -1
- package/dist/web/assets/{chunk-RYQCIY6F-B2PLxj_Q.js → chunk-RYQCIY6F-D-VbfyMB.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-DCpI_0Ix.js → chunk-XXDRQBXY-CeFbm4g1.js} +1 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-CRUWBeef.js +1 -0
- package/dist/web/assets/classDiagram-v2-OCEON4UE-CRUWBeef.js +1 -0
- package/dist/web/assets/{cose-bilkent-JH36ORCC-KtY-8vxF.js → cose-bilkent-JH36ORCC-CYNfxa8A.js} +1 -1
- package/dist/web/assets/{cynefin-VYW2F7L2-DkTsGV_4.js → cynefin-VYW2F7L2-rvO1KlAd.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-MW4NZA55-BVPMHgGu.js → cynefinDiagram-MW4NZA55-DvzcG3NL.js} +1 -1
- package/dist/web/assets/{dagre-VZM6K2ZE-DT-S1NOU.js → dagre-VZM6K2ZE-BgutGA8k.js} +1 -1
- package/dist/web/assets/{diagram-7IWD3JNH-DUJvV7wV.js → diagram-7IWD3JNH-BoLPEgBs.js} +1 -1
- package/dist/web/assets/{diagram-B4RE2ZJO-DmpnY7ob.js → diagram-B4RE2ZJO-BaaHgQLy.js} +1 -1
- package/dist/web/assets/{diagram-LBJQPF4R-nEMZNwen.js → diagram-LBJQPF4R-Bc7jkgiW.js} +1 -1
- package/dist/web/assets/{diagram-Q27KOJAE-Dr-59ElI.js → diagram-Q27KOJAE-p311g83v.js} +1 -1
- package/dist/web/assets/{diagram-UB23O5K3-4wvmRauY.js → diagram-UB23O5K3-ALwvcWzK.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-BXEA7PRR-055xAlHi.js → ebnfDiagram-BXEA7PRR-DveTtKRe.js} +1 -1
- package/dist/web/assets/{erDiagram-JOGREHBK-DqWTDG8u.js → erDiagram-JOGREHBK-D3kM5uf0.js} +1 -1
- package/dist/web/assets/{flowDiagram-UKHOOZJN-D3lUh0yb.js → flowDiagram-UKHOOZJN-h_YgCdts.js} +1 -1
- package/dist/web/assets/{ganttDiagram-PKOTCBZU-aV5l2zrT.js → ganttDiagram-PKOTCBZU-DpMVjZz3.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-BK2KCI35.js → gitGraphDiagram-DS77QQ5N-OJ0-BsVV.js} +1 -1
- package/dist/web/assets/index-BYjBvJ2T.css +1 -0
- package/dist/web/assets/index-C0MiltSe.js +245 -0
- package/dist/web/assets/{index-Dv1My9Ob.js → index-DHJZ0QFt.js} +1 -1
- package/dist/web/assets/{infoDiagram-6WML65LV-BcSXYnWm.js → infoDiagram-6WML65LV-C3dDMfD2.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-C0LFAfcz.js → ishikawaDiagram-WSZJBQD7-QSccwWZc.js} +1 -1
- package/dist/web/assets/{journeyDiagram-NVQOT4AX-1Zs22Gpd.js → journeyDiagram-NVQOT4AX-BBse3ziF.js} +1 -1
- package/dist/web/assets/{kanban-definition-27J2QSJJ-DW6RwD7S.js → kanban-definition-27J2QSJJ-DrbZwvww.js} +1 -1
- package/dist/web/assets/{linear-Cfzhrbed.js → linear-DSPm6cs7.js} +1 -1
- package/dist/web/assets/{mermaid.core-D0VPssrU.js → mermaid.core-DXBmt4hx.js} +4 -4
- package/dist/web/assets/{mindmap-definition-FAOFIHXS-BeD5KiWO.js → mindmap-definition-FAOFIHXS-DnF7VGZ2.js} +1 -1
- package/dist/web/assets/{pegDiagram-VL7TDLO6-JDLyI4Gc.js → pegDiagram-VL7TDLO6-DU79M4ax.js} +1 -1
- package/dist/web/assets/{pieDiagram-7S7Q4E2Y-BY3XpjEU.js → pieDiagram-7S7Q4E2Y-D_kGXiwY.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-C4oT4Lqc.js → quadrantDiagram-CIZ2JOQS-Sy59mKJK.js} +1 -1
- package/dist/web/assets/{railroadDiagram-AXF67PYL-lfzvVyK7.js → railroadDiagram-AXF67PYL-i-7HN_g3.js} +1 -1
- package/dist/web/assets/{requirementDiagram-LRYGKXZP-wGnOD5SS.js → requirementDiagram-LRYGKXZP-MbXlWZ5N.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-W5VNT64P-DJQ2VW0G.js → sankeyDiagram-W5VNT64P-BhU2pRtd.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-SI44F4Z6-Bz_rjvWZ.js → sequenceDiagram-SI44F4Z6-ju70uzxa.js} +1 -1
- package/dist/web/assets/{sizeCapture-X5ZJPWSS-BgxIA36S.js → sizeCapture-X5ZJPWSS-Oy3UrbXL.js} +1 -1
- package/dist/web/assets/{stateDiagram-OKZ733FA-DpL_B9Mu.js → stateDiagram-OKZ733FA-DzSL7BOW.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-TGjHDp18.js +1 -0
- package/dist/web/assets/{swimlanes-SLNWSIFB-B6uTv46X.js → swimlanes-SLNWSIFB-a_3Pai9c.js} +2 -2
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-Dq3BU0bp.js +8 -0
- package/dist/web/assets/{timeline-definition-Z64GVDOM-DvMUcNNs.js → timeline-definition-Z64GVDOM-CdYiXKKc.js} +1 -1
- package/dist/web/assets/{vennDiagram-T6HMQDX7-K_9U-J2A.js → vennDiagram-T6HMQDX7-CNquHYyi.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-T6FBY63Y-DwMi4kCd.js → wardleyDiagram-T6FBY63Y-D-dCSbF5.js} +1 -1
- package/dist/web/assets/{xychartDiagram-ELKLHX3M-BkcnShpk.js → xychartDiagram-ELKLHX3M-BZCQNI1R.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/dist/web/assets/channel-BueZNEdT.js +0 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-CSEqsvyn.js +0 -1
- package/dist/web/assets/classDiagram-v2-OCEON4UE-CSEqsvyn.js +0 -1
- package/dist/web/assets/index-B5ZDbS-R.js +0 -244
- package/dist/web/assets/index-CRxbcY2R.css +0 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-DsD9j9Fu.js +0 -1
- 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
|
-
|
|
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.
|
|
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"
|
|
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
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
682
|
+
sessionSetConfigOption: "session/set_config_option",
|
|
683
|
+
sessionFork: "session/fork",
|
|
654
684
|
sessionUpdate: "session/update",
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
|
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,}(
|
|
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
|
-
|
|
856
|
-
cache = { at: Date.now(), agents };
|
|
887
|
+
cache = { at: Date.now(), agents: [...found.values()] };
|
|
857
888
|
return cache.agents;
|
|
858
889
|
}
|
|
859
|
-
var execFileAsync,
|
|
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
|
-
|
|
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
|
-
|
|
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", "--
|
|
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
|
-
|
|
1128
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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
|
|
1624
|
+
return false;
|
|
1458
1625
|
}
|
|
1459
|
-
/** Called when the parent turn ends. The
|
|
1460
|
-
*
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
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
|
-
/**
|
|
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.
|
|
1641
|
+
return [...this.byId.values()].map((s) => ({ ...s })).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
1494
1642
|
}
|
|
1495
|
-
get(
|
|
1496
|
-
|
|
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/
|
|
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
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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
|
|
1604
|
-
|
|
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
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
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
|
|
1622
|
-
if (!isValidSessionId(sessionId)) return;
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
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
|
-
|
|
1633
|
-
|
|
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
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
)
|
|
1639
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1721
|
+
return facts;
|
|
1651
1722
|
}
|
|
1652
|
-
}
|
|
1653
|
-
async function readChildSession(childSessionId, parentSessionId) {
|
|
1654
|
-
if (!isValidSessionId(childSessionId)) return null;
|
|
1655
1723
|
try {
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
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
|
|
1664
|
-
|
|
1665
|
-
let names;
|
|
1742
|
+
async function readV3Session(dir) {
|
|
1743
|
+
let j;
|
|
1666
1744
|
try {
|
|
1667
|
-
|
|
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
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
|
1691
|
-
if (!isValidSessionId(sessionId)) return [];
|
|
1766
|
+
async function readLines(file, filter) {
|
|
1692
1767
|
let raw;
|
|
1693
1768
|
try {
|
|
1694
|
-
raw = await fs9.readFile(
|
|
1769
|
+
raw = await fs9.readFile(file, "utf8");
|
|
1695
1770
|
} catch {
|
|
1696
1771
|
return [];
|
|
1697
1772
|
}
|
|
1698
|
-
const
|
|
1773
|
+
const out = [];
|
|
1699
1774
|
for (const line of raw.split("\n")) {
|
|
1700
|
-
|
|
1701
|
-
if (!trimmed) continue;
|
|
1775
|
+
if (!line.trim() || filter && !line.includes(filter)) continue;
|
|
1702
1776
|
try {
|
|
1703
|
-
|
|
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
|
|
1782
|
+
return out;
|
|
1708
1783
|
}
|
|
1709
|
-
async function
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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
|
-
|
|
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
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
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
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
2127
|
+
return db().prepare(`SELECT ${COLUMNS} FROM chats`).all().map(rowOf);
|
|
1828
2128
|
}
|
|
1829
2129
|
get(chatId) {
|
|
1830
|
-
const r = db().prepare(
|
|
1831
|
-
return r ?
|
|
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
|
|
1970
|
-
import
|
|
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 ?
|
|
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 =
|
|
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
|
-
|
|
2281
|
+
fs11.mkdirSync(abs, { recursive: true });
|
|
1988
2282
|
}
|
|
1989
2283
|
return abs;
|
|
1990
2284
|
}
|
|
1991
|
-
function
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
case
|
|
1998
|
-
|
|
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
|
|
2004
|
-
|
|
2005
|
-
return
|
|
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
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
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
|
-
|
|
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 :
|
|
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
|
|
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
|
-
|
|
2247
|
-
if (
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
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
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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
|
|
2346
|
-
const
|
|
2812
|
+
const modelId = opts.modelId ?? await this.defaultModel();
|
|
2813
|
+
const s = await this.startSession(new EventStore(chatId), cwd, {
|
|
2814
|
+
agentId: opts.agentId ?? await this.defaultAgent() ?? config.defaultAgent,
|
|
2815
|
+
modelId,
|
|
2816
|
+
effortLevel: opts.effortLevel ?? await this.defaultEffort(modelId)
|
|
2817
|
+
});
|
|
2818
|
+
this.store.create(chatId);
|
|
2819
|
+
this.store.bindSession(chatId, s.sessionId);
|
|
2820
|
+
return this.buildDetail(this.store.get(chatId), s, []);
|
|
2821
|
+
}
|
|
2822
|
+
/** Starts a kiro session, new or forked, and holds it in memory under kiro's id. */
|
|
2823
|
+
async startSession(store, cwd, init) {
|
|
2824
|
+
const tempId = `pending-${crypto.randomUUID()}`;
|
|
2347
2825
|
const s = new Session(tempId, store, cwd);
|
|
2348
|
-
s.agentId =
|
|
2349
|
-
s.currentModeId =
|
|
2350
|
-
s.modelId =
|
|
2826
|
+
s.agentId = init.agentId;
|
|
2827
|
+
s.currentModeId = init.agentId;
|
|
2828
|
+
s.modelId = init.modelId;
|
|
2829
|
+
s.effortLevel = init.effortLevel;
|
|
2830
|
+
s.forkOf = init.forkOf;
|
|
2351
2831
|
this.sessions.set(tempId, s);
|
|
2352
2832
|
try {
|
|
2353
2833
|
await this.ensureProc(s);
|
|
2354
2834
|
} catch (err) {
|
|
2355
|
-
this.
|
|
2835
|
+
this.sessions.delete(s.sessionId);
|
|
2836
|
+
s.proc?.dispose();
|
|
2356
2837
|
throw err;
|
|
2357
2838
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
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
|
-
|
|
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 (
|
|
2381
|
-
|
|
2382
|
-
|
|
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.
|
|
2398
|
-
|
|
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
|
-
|
|
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
|
|
2504
|
-
rows.map((r) => r.sessionId
|
|
3172
|
+
const files = await readPersistedSessions(
|
|
3173
|
+
rows.map((r) => r.sessionId).filter((id) => !!id)
|
|
2505
3174
|
);
|
|
2506
3175
|
const out = [];
|
|
2507
|
-
rows.forEach((row
|
|
3176
|
+
rows.forEach((row) => {
|
|
2508
3177
|
const live = row.sessionId ? this.sessions.get(row.sessionId) : void 0;
|
|
2509
|
-
const persisted = files
|
|
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
|
|
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
|
|
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
|
-
|
|
2577
|
-
|
|
2578
|
-
byId.set(
|
|
2579
|
-
|
|
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
|
-
|
|
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
|
|
2593
|
-
async getSubagentDetail(chatId,
|
|
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
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
const
|
|
2599
|
-
const
|
|
2600
|
-
|
|
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,
|
|
3247
|
+
buildDetail(chat, s, hydrated, persisted) {
|
|
3248
|
+
const { transcript, head } = this.resumePoint(s, hydrated);
|
|
2612
3249
|
return {
|
|
2613
|
-
summary: this.summaryOf(chat, s, persisted,
|
|
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
|
|
3259
|
+
head
|
|
2623
3260
|
};
|
|
2624
3261
|
}
|
|
2625
3262
|
/**
|
|
2626
|
-
*
|
|
2627
|
-
* turn
|
|
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
|
-
|
|
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)
|
|
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
|
|
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/
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
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
|
|
2837
|
-
|
|
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
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
);
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
3738
|
+
return [];
|
|
2876
3739
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
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
|
|
2896
|
-
|
|
3751
|
+
var execFileAsync3, mcpFile, agentCache, strings, SERVER_NAME;
|
|
3752
|
+
var init_kiroTools = __esm({
|
|
3753
|
+
"server/src/session/kiroTools.ts"() {
|
|
2897
3754
|
"use strict";
|
|
2898
|
-
|
|
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
|
|
2918
|
-
import
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
4012
|
+
import fs14 from "node:fs/promises";
|
|
3150
4013
|
import { createReadStream } from "node:fs";
|
|
3151
|
-
import
|
|
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 =
|
|
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
|
|
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
|
|
4130
|
+
import fs15 from "node:fs/promises";
|
|
3268
4131
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
3269
|
-
import
|
|
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 ?
|
|
3278
|
-
const dir = endsWithSep || !input ? resolved :
|
|
3279
|
-
const prefix = endsWithSep || !input ? "" :
|
|
3280
|
-
const targetKind = await
|
|
4140
|
+
const resolved = input ? path17.resolve(base, input) : base;
|
|
4141
|
+
const dir = endsWithSep || !input ? resolved : path17.dirname(resolved);
|
|
4142
|
+
const prefix = endsWithSep || !input ? "" : path17.basename(resolved);
|
|
4143
|
+
const targetKind = await fs15.stat(resolved).then((s) => s.isDirectory() ? "directory" : "file").catch(() => "missing");
|
|
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
|
|
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) =>
|
|
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 (!
|
|
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="${
|
|
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
|
|
4416
|
+
import fs16 from "node:fs/promises";
|
|
3497
4417
|
import { createReadStream as createReadStream3 } from "node:fs";
|
|
3498
|
-
import
|
|
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
|
|
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
|
|
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 =
|
|
3557
|
-
const filename =
|
|
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
|
|
3593
|
-
import
|
|
4512
|
+
import fs17 from "node:fs/promises";
|
|
4513
|
+
import path19 from "node:path";
|
|
3594
4514
|
import crypto4 from "node:crypto";
|
|
3595
|
-
import { execFile as
|
|
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 =
|
|
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 =
|
|
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
|
|
4529
|
+
await fs17.access(path19.join(dir, candidate));
|
|
3610
4530
|
candidate = `${stem} (${n++})${ext}`;
|
|
3611
4531
|
} catch {
|
|
3612
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3685
|
-
const ext =
|
|
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:
|
|
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,
|
|
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:
|
|
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
|
|
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 =
|
|
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: (
|
|
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
|
|
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 (
|
|
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
|
|
4002
|
-
if (
|
|
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
|
|
4008
|
-
const looksLikeAsset =
|
|
4009
|
-
if (
|
|
5011
|
+
const path22 = req.url.split("?")[0] ?? "";
|
|
5012
|
+
const looksLikeAsset = path22.startsWith("/assets/") || /\.[a-zA-Z0-9]+$/.test(path22);
|
|
5013
|
+
if (path22.startsWith("/api/") || path22.startsWith("/ws") || looksLikeAsset) {
|
|
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
|
|
4085
|
-
import
|
|
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
|
|
4088
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
5097
|
+
if (bin.includes("/")) return fs20.existsSync(bin) ? bin : void 0;
|
|
5098
|
+
for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
|
|
4089
5099
|
if (dir === "") continue;
|
|
4090
|
-
const candidate =
|
|
5100
|
+
const candidate = path20.join(dir, bin);
|
|
4091
5101
|
try {
|
|
4092
|
-
|
|
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 && !
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
4159
|
-
const mode =
|
|
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
|
-
|
|
4168
|
-
|
|
4169
|
-
const db2 =
|
|
4170
|
-
const size =
|
|
5197
|
+
fs20.mkdirSync(config.casperDataDir, { recursive: true });
|
|
5198
|
+
fs20.accessSync(config.casperDataDir, fs20.constants.W_OK);
|
|
5199
|
+
const db2 = path20.join(config.casperDataDir, "casper.db");
|
|
5200
|
+
const size = fs20.existsSync(db2) ? `, casper.db ${(fs20.statSync(db2).size / 1024).toFixed(0)} KB` : "";
|
|
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
|
-
|
|
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 =
|
|
4189
|
-
const hasAgent =
|
|
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
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
5693
|
+
VERSION2 = true ? "0.17.0" : "dev";
|
|
4661
5694
|
}
|
|
4662
5695
|
});
|
|
4663
5696
|
|
|
4664
5697
|
// server/src/cli/program.ts
|
|
4665
|
-
import
|
|
4666
|
-
import
|
|
4667
|
-
import
|
|
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(
|
|
5259
|
-
return stripVTControlCharacters(
|
|
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(
|
|
5268
|
-
return
|
|
6300
|
+
styleTitle(str3) {
|
|
6301
|
+
return str3;
|
|
5269
6302
|
}
|
|
5270
|
-
styleUsage(
|
|
5271
|
-
return
|
|
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(
|
|
5280
|
-
return this.styleDescriptionText(
|
|
6312
|
+
styleCommandDescription(str3) {
|
|
6313
|
+
return this.styleDescriptionText(str3);
|
|
5281
6314
|
}
|
|
5282
|
-
styleOptionDescription(
|
|
5283
|
-
return this.styleDescriptionText(
|
|
6315
|
+
styleOptionDescription(str3) {
|
|
6316
|
+
return this.styleDescriptionText(str3);
|
|
5284
6317
|
}
|
|
5285
|
-
styleSubcommandDescription(
|
|
5286
|
-
return this.styleDescriptionText(
|
|
6318
|
+
styleSubcommandDescription(str3) {
|
|
6319
|
+
return this.styleDescriptionText(str3);
|
|
5287
6320
|
}
|
|
5288
|
-
styleArgumentDescription(
|
|
5289
|
-
return this.styleDescriptionText(
|
|
6321
|
+
styleArgumentDescription(str3) {
|
|
6322
|
+
return this.styleDescriptionText(str3);
|
|
5290
6323
|
}
|
|
5291
|
-
styleDescriptionText(
|
|
5292
|
-
return
|
|
6324
|
+
styleDescriptionText(str3) {
|
|
6325
|
+
return str3;
|
|
5293
6326
|
}
|
|
5294
|
-
styleOptionTerm(
|
|
5295
|
-
return this.styleOptionText(
|
|
6327
|
+
styleOptionTerm(str3) {
|
|
6328
|
+
return this.styleOptionText(str3);
|
|
5296
6329
|
}
|
|
5297
|
-
styleSubcommandTerm(
|
|
5298
|
-
return
|
|
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(
|
|
5306
|
-
return this.styleArgumentText(
|
|
6338
|
+
styleArgumentTerm(str3) {
|
|
6339
|
+
return this.styleArgumentText(str3);
|
|
5307
6340
|
}
|
|
5308
|
-
styleOptionText(
|
|
5309
|
-
return
|
|
6341
|
+
styleOptionText(str3) {
|
|
6342
|
+
return str3;
|
|
5310
6343
|
}
|
|
5311
|
-
styleArgumentText(
|
|
5312
|
-
return
|
|
6344
|
+
styleArgumentText(str3) {
|
|
6345
|
+
return str3;
|
|
5313
6346
|
}
|
|
5314
|
-
styleSubcommandText(
|
|
5315
|
-
return
|
|
6347
|
+
styleSubcommandText(str3) {
|
|
6348
|
+
return str3;
|
|
5316
6349
|
}
|
|
5317
|
-
styleCommandText(
|
|
5318
|
-
return
|
|
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(
|
|
5342
|
-
return /\n[^\S\r\n]/.test(
|
|
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(
|
|
5389
|
-
if (width < this.minWidthToWrap) return
|
|
5390
|
-
const rawLines =
|
|
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(
|
|
5681
|
-
return
|
|
5682
|
-
return
|
|
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: (
|
|
5843
|
-
writeErr: (
|
|
5844
|
-
outputError: (
|
|
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: (
|
|
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(
|
|
7578
|
-
if (
|
|
7579
|
-
this._version =
|
|
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(`${
|
|
8619
|
+
this._outputConfiguration.writeOut(`${str3}
|
|
7587
8620
|
`);
|
|
7588
|
-
this._exit(0, "commander.version",
|
|
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(
|
|
7600
|
-
if (
|
|
8632
|
+
description(str3, argsDescription) {
|
|
8633
|
+
if (str3 === void 0 && argsDescription === void 0)
|
|
7601
8634
|
return this._description;
|
|
7602
|
-
this._description =
|
|
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(
|
|
7615
|
-
if (
|
|
7616
|
-
this._summary =
|
|
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(
|
|
7665
|
-
if (
|
|
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 =
|
|
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(
|
|
7686
|
-
if (
|
|
7687
|
-
this._name =
|
|
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(
|
|
7781
|
-
if (
|
|
7782
|
-
this._executableDir =
|
|
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 = (
|
|
8854
|
+
baseWrite = (str3) => this._outputConfiguration.writeErr(str3);
|
|
7822
8855
|
hasColors = this._outputConfiguration.getErrHasColors();
|
|
7823
8856
|
helpWidth = this._outputConfiguration.getErrHelpWidth();
|
|
7824
8857
|
} else {
|
|
7825
|
-
baseWrite = (
|
|
8858
|
+
baseWrite = (str3) => this._outputConfiguration.writeOut(str3);
|
|
7826
8859
|
hasColors = this._outputConfiguration.getOutHasColors();
|
|
7827
8860
|
helpWidth = this._outputConfiguration.getOutHelpWidth();
|
|
7828
8861
|
}
|
|
7829
|
-
const write = (
|
|
7830
|
-
if (!hasColors)
|
|
7831
|
-
return baseWrite(
|
|
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
|
|
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(
|
|
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 =
|
|
8087
|
-
if (
|
|
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(
|
|
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 () => {
|