demian-cli 1.0.9 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/tui.mjs +2236 -307
- package/dist/vscode-worker.mjs +654 -41
- package/docs/ko/README.md +5 -0
- package/package.json +1 -1
package/dist/vscode-worker.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/vscode-worker.ts
|
|
4
|
-
import { mkdir as
|
|
5
|
-
import
|
|
4
|
+
import { mkdir as mkdir16, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
|
|
5
|
+
import path36 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/permissions/presets.ts
|
|
8
8
|
var PERMISSION_PRESETS = ["deny", "ask", "auto", "full"];
|
|
@@ -67,6 +67,26 @@ function isCompactCommand(input2) {
|
|
|
67
67
|
function isRetryCommand(input2) {
|
|
68
68
|
return input2.trim().toLowerCase() === "/retry";
|
|
69
69
|
}
|
|
70
|
+
function isSessionCommand(input2) {
|
|
71
|
+
return /^\/(?:session|sessions|conversation|conversations|conv)(?:\s|$)/i.test(input2.trim());
|
|
72
|
+
}
|
|
73
|
+
function parseSessionCommand(input2) {
|
|
74
|
+
const raw = input2.trim();
|
|
75
|
+
if (!isSessionCommand(raw)) return void 0;
|
|
76
|
+
const rest = raw.replace(/^\/(?:session|sessions|conversation|conversations|conv)\b/i, "").trim();
|
|
77
|
+
if (!rest) return { action: "help" };
|
|
78
|
+
const tokens = shellishSplit(rest);
|
|
79
|
+
const action = tokens.shift()?.toLowerCase() ?? "help";
|
|
80
|
+
if (action === "help" || action === "-h" || action === "--help") return { action: "help" };
|
|
81
|
+
if (action === "list" || action === "ls") return { action: "list" };
|
|
82
|
+
if (action === "current" || action === "status") return { action: "current" };
|
|
83
|
+
if (action === "new" || action === "create") return { action: "new", title: tokens.join(" ").trim() || void 0 };
|
|
84
|
+
if (action === "switch" || action === "select" || action === "use" || action === "open") return { action: "switch", target: tokens.join(" ").trim() || void 0 };
|
|
85
|
+
if (action === "delete" || action === "remove" || action === "rm") return { action: "delete", target: tokens.join(" ").trim() || void 0 };
|
|
86
|
+
if (action === "rename" || action === "name") return { action: "rename", title: tokens.join(" ").trim() || void 0 };
|
|
87
|
+
if (action === "clear" || action === "reset") return { action: "clear" };
|
|
88
|
+
return { action: "switch", target: [action, ...tokens].join(" ").trim() };
|
|
89
|
+
}
|
|
70
90
|
function isCoworkCommand(input2) {
|
|
71
91
|
return /^\/cowork(?:\s|$)/i.test(input2.trim());
|
|
72
92
|
}
|
|
@@ -748,6 +768,8 @@ var TuiStore = class {
|
|
|
748
768
|
blocks: [],
|
|
749
769
|
activity: "starting",
|
|
750
770
|
inputMode: "starting",
|
|
771
|
+
sessionOptions: [],
|
|
772
|
+
sessionCursor: 0,
|
|
751
773
|
providerOptions: [],
|
|
752
774
|
providerCursor: 0,
|
|
753
775
|
agentOptions: [],
|
|
@@ -769,6 +791,7 @@ var TuiStore = class {
|
|
|
769
791
|
#blockId = 0;
|
|
770
792
|
#stopActiveTask;
|
|
771
793
|
#promptResolve;
|
|
794
|
+
#sessionResolve;
|
|
772
795
|
#queuedPrompt;
|
|
773
796
|
#exitRequested = false;
|
|
774
797
|
#promptHistory = [];
|
|
@@ -789,6 +812,7 @@ var TuiStore = class {
|
|
|
789
812
|
...this.#state,
|
|
790
813
|
status: { ...this.#state.status },
|
|
791
814
|
selection: this.#state.selection ? { ...this.#state.selection } : void 0,
|
|
815
|
+
sessionOptions: this.#state.sessionOptions.map((option) => ({ ...option })),
|
|
792
816
|
providerOptions: this.#state.providerOptions.map((option) => ({ ...option })),
|
|
793
817
|
agentOptions: this.#state.agentOptions.map((option) => ({ ...option })),
|
|
794
818
|
blocks: this.#state.blocks.map((block) => ({ ...block, lines: [...block.lines], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })),
|
|
@@ -827,6 +851,7 @@ var TuiStore = class {
|
|
|
827
851
|
requestExit() {
|
|
828
852
|
this.#exitRequested = true;
|
|
829
853
|
if (this.#promptResolve) this.#resolvePrompt("");
|
|
854
|
+
if (this.#sessionResolve) this.#resolveSession({ kind: "exit" });
|
|
830
855
|
this.#stopActiveTask?.();
|
|
831
856
|
this.#state.inputMode = "done";
|
|
832
857
|
this.#state.done = true;
|
|
@@ -850,7 +875,7 @@ var TuiStore = class {
|
|
|
850
875
|
}
|
|
851
876
|
markTaskFailed(message) {
|
|
852
877
|
const lines = [message];
|
|
853
|
-
if (this.#lastRetryPrompt()) lines.push("Press
|
|
878
|
+
if (this.#lastRetryPrompt()) lines.push("Press Esc then r or type /retry to run the last task again.");
|
|
854
879
|
this.#append({ kind: "warning", title: "Task Failed", lines });
|
|
855
880
|
this.#state.status.reason = "error";
|
|
856
881
|
this.#state.inputMode = this.#exitRequested ? "done" : "prompt";
|
|
@@ -906,6 +931,84 @@ var TuiStore = class {
|
|
|
906
931
|
this.#state.activity = `context compacted: ${summary.beforeTokenEstimate} -> ${summary.afterTokenEstimate}`;
|
|
907
932
|
this.#notify();
|
|
908
933
|
}
|
|
934
|
+
conversationSnapshot() {
|
|
935
|
+
const snapshot = this.snapshot();
|
|
936
|
+
return {
|
|
937
|
+
...snapshot,
|
|
938
|
+
inputMode: "prompt",
|
|
939
|
+
promptInput: "",
|
|
940
|
+
promptError: void 0,
|
|
941
|
+
streamingText: "",
|
|
942
|
+
streamingFinalized: true,
|
|
943
|
+
permission: void 0,
|
|
944
|
+
done: false
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
restoreConversationSnapshot(snapshot, fallback) {
|
|
948
|
+
this.#resetTransientConversationState();
|
|
949
|
+
const currentSettings = {
|
|
950
|
+
selection: this.#state.selection,
|
|
951
|
+
providerOptions: this.#state.providerOptions,
|
|
952
|
+
providerCursor: this.#state.providerCursor,
|
|
953
|
+
agentOptions: this.#state.agentOptions,
|
|
954
|
+
agentCursor: this.#state.agentCursor,
|
|
955
|
+
selectedAgent: this.#state.selectedAgent,
|
|
956
|
+
permissionPreset: this.#state.permissionPreset,
|
|
957
|
+
permissionPresetCursor: this.#state.permissionPresetCursor,
|
|
958
|
+
contextEfficiency: this.#state.contextEfficiency
|
|
959
|
+
};
|
|
960
|
+
const restoredSelection = snapshot?.selection ? { ...snapshot.selection } : currentSettings.selection;
|
|
961
|
+
const restoredAgent = snapshot?.selectedAgent ?? snapshot?.status?.agent ?? currentSettings.selectedAgent;
|
|
962
|
+
const restoredPermissionPreset = normalizePermissionPreset(snapshot?.permissionPreset, currentSettings.permissionPreset);
|
|
963
|
+
this.#state = {
|
|
964
|
+
...this.#state,
|
|
965
|
+
...safeConversationSnapshot(snapshot),
|
|
966
|
+
...currentSettings,
|
|
967
|
+
selection: restoredSelection,
|
|
968
|
+
providerCursor: Math.max(0, currentSettings.providerOptions.findIndex((option) => option.name === restoredSelection?.providerName)),
|
|
969
|
+
selectedAgent: restoredAgent,
|
|
970
|
+
agentCursor: Math.max(0, currentSettings.agentOptions.findIndex((option) => option.name === restoredAgent)),
|
|
971
|
+
permissionPreset: restoredPermissionPreset,
|
|
972
|
+
permissionPresetCursor: Math.max(0, PERMISSION_PRESETS.indexOf(restoredPermissionPreset)),
|
|
973
|
+
status: {
|
|
974
|
+
...snapshot?.status ?? this.#state.status,
|
|
975
|
+
sessionId: fallback.sessionId,
|
|
976
|
+
cwd: fallback.cwd,
|
|
977
|
+
provider: restoredSelection?.providerName ?? snapshot?.status?.provider ?? this.#state.status.provider,
|
|
978
|
+
model: restoredSelection?.model ?? snapshot?.status?.model ?? this.#state.status.model,
|
|
979
|
+
agent: restoredAgent ?? snapshot?.status?.agent ?? this.#state.status.agent
|
|
980
|
+
},
|
|
981
|
+
inputMode: "prompt",
|
|
982
|
+
promptInput: "",
|
|
983
|
+
promptError: void 0,
|
|
984
|
+
streamingText: "",
|
|
985
|
+
streamingFinalized: true,
|
|
986
|
+
permission: void 0,
|
|
987
|
+
done: false,
|
|
988
|
+
activity: `conversation: ${fallback.title}`
|
|
989
|
+
};
|
|
990
|
+
this.#blockId = nextBlockId(this.#state.blocks);
|
|
991
|
+
this.#notify();
|
|
992
|
+
}
|
|
993
|
+
clearConversation(title = "New session") {
|
|
994
|
+
this.#resetTransientConversationState();
|
|
995
|
+
this.#state.blocks = [];
|
|
996
|
+
this.#state.streamingText = "";
|
|
997
|
+
this.#state.streamingFinalized = true;
|
|
998
|
+
this.#state.permission = void 0;
|
|
999
|
+
this.#state.warnings = [];
|
|
1000
|
+
this.#state.done = false;
|
|
1001
|
+
this.#state.finalAnswer = void 0;
|
|
1002
|
+
this.#state.progressNotes = [];
|
|
1003
|
+
this.#state.turnDiff = void 0;
|
|
1004
|
+
this.#state.diffExpanded = false;
|
|
1005
|
+
this.#state.workPlan = void 0;
|
|
1006
|
+
this.#state.workPlanExpanded = false;
|
|
1007
|
+
this.#state.goal = void 0;
|
|
1008
|
+
this.#state.pendingClaudeCodePlan = void 0;
|
|
1009
|
+
this.#state.activity = `cleared conversation: ${title}`;
|
|
1010
|
+
this.prepareForPrompt("waiting for next message");
|
|
1011
|
+
}
|
|
909
1012
|
prepareForPrompt(message = "waiting for next message") {
|
|
910
1013
|
if (this.#exitRequested) return;
|
|
911
1014
|
this.#state.inputMode = "prompt";
|
|
@@ -963,7 +1066,45 @@ var TuiStore = class {
|
|
|
963
1066
|
currentPermissionPreset() {
|
|
964
1067
|
return this.#state.permissionPreset;
|
|
965
1068
|
}
|
|
1069
|
+
requestSessionSelection(options2) {
|
|
1070
|
+
if (this.#exitRequested) return Promise.resolve({ kind: "exit" });
|
|
1071
|
+
const normalized = options2.length > 0 ? options2.map((option) => ({ ...option })) : [{ kind: "new", title: "New session", status: "ready", currentWorkspace: true }];
|
|
1072
|
+
return new Promise((resolve) => {
|
|
1073
|
+
this.#sessionResolve = resolve;
|
|
1074
|
+
this.#state.inputMode = "session";
|
|
1075
|
+
this.#state.sessionOptions = normalized;
|
|
1076
|
+
this.#state.sessionCursor = 0;
|
|
1077
|
+
this.#state.promptInput = "";
|
|
1078
|
+
this.#state.promptError = void 0;
|
|
1079
|
+
this.#state.streamingText = "";
|
|
1080
|
+
this.#state.streamingFinalized = true;
|
|
1081
|
+
this.#state.done = false;
|
|
1082
|
+
this.#state.activity = "select session";
|
|
1083
|
+
this.#notify();
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
moveSessionCursor(delta) {
|
|
1087
|
+
if (this.#state.inputMode !== "session" || this.#state.sessionOptions.length === 0) return;
|
|
1088
|
+
const count = this.#state.sessionOptions.length;
|
|
1089
|
+
this.#state.sessionCursor = (this.#state.sessionCursor + delta + count) % count;
|
|
1090
|
+
this.#notify();
|
|
1091
|
+
}
|
|
1092
|
+
submitSessionSelection() {
|
|
1093
|
+
if (this.#state.inputMode !== "session") return;
|
|
1094
|
+
const option = this.#state.sessionOptions[this.#state.sessionCursor];
|
|
1095
|
+
if (!option) return;
|
|
1096
|
+
if (option.kind === "new") {
|
|
1097
|
+
this.#resolveSession({ kind: "new" });
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (option.id) this.#resolveSession({ kind: "session", id: option.id });
|
|
1101
|
+
}
|
|
1102
|
+
submitNewSessionSelection() {
|
|
1103
|
+
if (this.#state.inputMode !== "session") return;
|
|
1104
|
+
this.#resolveSession({ kind: "new" });
|
|
1105
|
+
}
|
|
966
1106
|
requestPrompt(initialPrompt) {
|
|
1107
|
+
if (this.#exitRequested) return Promise.resolve("");
|
|
967
1108
|
const prompt = (initialPrompt ?? "").trim();
|
|
968
1109
|
if (prompt) {
|
|
969
1110
|
this.#rememberPrompt(prompt);
|
|
@@ -1004,12 +1145,12 @@ var TuiStore = class {
|
|
|
1004
1145
|
this.#notify();
|
|
1005
1146
|
});
|
|
1006
1147
|
}
|
|
1007
|
-
appendPromptInput(input2) {
|
|
1148
|
+
appendPromptInput(input2, options2 = {}) {
|
|
1008
1149
|
if (this.#state.inputMode !== "prompt" && this.#state.inputMode !== "running") return;
|
|
1009
1150
|
if (this.#state.inputMode === "prompt") this.#detachPromptHistory();
|
|
1010
1151
|
this.#state.promptInput += input2;
|
|
1011
1152
|
this.#state.promptError = void 0;
|
|
1012
|
-
this.#notify();
|
|
1153
|
+
if (options2.notify !== false) this.#notify();
|
|
1013
1154
|
}
|
|
1014
1155
|
navigatePromptHistory(delta) {
|
|
1015
1156
|
if (this.#state.inputMode !== "prompt" || this.#promptHistory.length === 0) return;
|
|
@@ -1164,19 +1305,19 @@ var TuiStore = class {
|
|
|
1164
1305
|
this.#state.activity = "waiting for first message";
|
|
1165
1306
|
this.#notify();
|
|
1166
1307
|
}
|
|
1167
|
-
backspacePromptInput() {
|
|
1308
|
+
backspacePromptInput(options2 = {}) {
|
|
1168
1309
|
if (this.#state.inputMode !== "prompt" && this.#state.inputMode !== "running") return;
|
|
1169
1310
|
if (this.#state.inputMode === "prompt") this.#detachPromptHistory();
|
|
1170
1311
|
this.#state.promptInput = Array.from(this.#state.promptInput).slice(0, -1).join("");
|
|
1171
1312
|
this.#state.promptError = void 0;
|
|
1172
|
-
this.#notify();
|
|
1313
|
+
if (options2.notify !== false) this.#notify();
|
|
1173
1314
|
}
|
|
1174
|
-
clearPromptInput() {
|
|
1315
|
+
clearPromptInput(options2 = {}) {
|
|
1175
1316
|
if (this.#state.inputMode !== "prompt" && this.#state.inputMode !== "running") return;
|
|
1176
1317
|
if (this.#state.inputMode === "prompt") this.#detachPromptHistory();
|
|
1177
1318
|
this.#state.promptInput = "";
|
|
1178
1319
|
this.#state.promptError = void 0;
|
|
1179
|
-
this.#notify();
|
|
1320
|
+
if (options2.notify !== false) this.#notify();
|
|
1180
1321
|
}
|
|
1181
1322
|
usePendingClaudeCodePlan() {
|
|
1182
1323
|
if (this.#state.inputMode !== "prompt") return false;
|
|
@@ -2004,9 +2145,24 @@ var TuiStore = class {
|
|
|
2004
2145
|
resolve(prompt);
|
|
2005
2146
|
this.#notify();
|
|
2006
2147
|
}
|
|
2148
|
+
#resolveSession(selection) {
|
|
2149
|
+
const resolve = this.#sessionResolve;
|
|
2150
|
+
if (!resolve) return;
|
|
2151
|
+
this.#sessionResolve = void 0;
|
|
2152
|
+
if (selection.kind === "exit") {
|
|
2153
|
+
this.#state.inputMode = "done";
|
|
2154
|
+
this.#state.done = true;
|
|
2155
|
+
this.#state.activity = "exiting";
|
|
2156
|
+
} else {
|
|
2157
|
+
this.#state.inputMode = "starting";
|
|
2158
|
+
this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
|
|
2159
|
+
}
|
|
2160
|
+
this.#notify();
|
|
2161
|
+
resolve(selection);
|
|
2162
|
+
}
|
|
2007
2163
|
#rememberPrompt(prompt) {
|
|
2008
2164
|
const value = prompt.trim();
|
|
2009
|
-
if (!value || isExitCommand(value) || isStopCommand(value) || isCompactCommand(value) || isRetryCommand(value)) return;
|
|
2165
|
+
if (!value || isExitCommand(value) || isStopCommand(value) || isCompactCommand(value) || isRetryCommand(value) || isSessionCommand(value)) return;
|
|
2010
2166
|
if (this.#promptHistory.at(-1) === value) {
|
|
2011
2167
|
this.#state.canRetryLastPrompt = true;
|
|
2012
2168
|
return;
|
|
@@ -2018,6 +2174,20 @@ var TuiStore = class {
|
|
|
2018
2174
|
#lastRetryPrompt() {
|
|
2019
2175
|
return this.#promptHistory.at(-1);
|
|
2020
2176
|
}
|
|
2177
|
+
#resetTransientConversationState() {
|
|
2178
|
+
this.#toolRuns.clear();
|
|
2179
|
+
this.#goalWorkBlocks.clear();
|
|
2180
|
+
this.#coworkBlocks.clear();
|
|
2181
|
+
this.#activeGoalWorkBlockId = void 0;
|
|
2182
|
+
this.#currentGoalNarrationId = void 0;
|
|
2183
|
+
this.#timelineProgressIds.clear();
|
|
2184
|
+
this.#pendingPermissionCallId = void 0;
|
|
2185
|
+
if (this.#pendingPermissionTimeout) clearTimeout(this.#pendingPermissionTimeout);
|
|
2186
|
+
this.#pendingPermissionTimeout = void 0;
|
|
2187
|
+
this.#activeClaudeCodePlan = void 0;
|
|
2188
|
+
this.#promptHistoryCursor = void 0;
|
|
2189
|
+
this.#promptDraft = "";
|
|
2190
|
+
}
|
|
2021
2191
|
#detachPromptHistory() {
|
|
2022
2192
|
this.#promptHistoryCursor = void 0;
|
|
2023
2193
|
this.#promptDraft = "";
|
|
@@ -2309,6 +2479,36 @@ function cloneGoalToolRun(tool) {
|
|
|
2309
2479
|
details: cloneToolRunDetails(tool.details)
|
|
2310
2480
|
};
|
|
2311
2481
|
}
|
|
2482
|
+
function safeConversationSnapshot(snapshot) {
|
|
2483
|
+
if (!snapshot || typeof snapshot !== "object") return {};
|
|
2484
|
+
return {
|
|
2485
|
+
status: snapshot.status ? { ...snapshot.status } : void 0,
|
|
2486
|
+
blocks: Array.isArray(snapshot.blocks) ? snapshot.blocks.map((block) => ({ ...block, lines: [...block.lines ?? []], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })) : [],
|
|
2487
|
+
activity: typeof snapshot.activity === "string" ? snapshot.activity : "restored conversation",
|
|
2488
|
+
warnings: Array.isArray(snapshot.warnings) ? [...snapshot.warnings] : [],
|
|
2489
|
+
finalAnswer: snapshot.finalAnswer,
|
|
2490
|
+
progressNotes: Array.isArray(snapshot.progressNotes) ? snapshot.progressNotes.map((note) => ({ ...note })) : [],
|
|
2491
|
+
turnDiff: snapshot.turnDiff ? cloneDiffSummary(snapshot.turnDiff) : void 0,
|
|
2492
|
+
diffExpanded: snapshot.diffExpanded === true,
|
|
2493
|
+
workPlan: snapshot.workPlan ? {
|
|
2494
|
+
...snapshot.workPlan,
|
|
2495
|
+
steps: snapshot.workPlan.steps.map((step) => ({ ...step }))
|
|
2496
|
+
} : void 0,
|
|
2497
|
+
workPlanExpanded: snapshot.workPlanExpanded === true,
|
|
2498
|
+
goal: snapshot.goal ? { ...snapshot.goal } : void 0,
|
|
2499
|
+
contextEfficiency: snapshot.contextEfficiency ? cloneContextEfficiency(snapshot.contextEfficiency) : void 0,
|
|
2500
|
+
canRetryLastPrompt: snapshot.canRetryLastPrompt === true,
|
|
2501
|
+
pendingClaudeCodePlan: snapshot.pendingClaudeCodePlan ? { ...snapshot.pendingClaudeCodePlan } : void 0
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
function nextBlockId(blocks) {
|
|
2505
|
+
let max = 0;
|
|
2506
|
+
for (const block of blocks ?? []) {
|
|
2507
|
+
const match = /^block_(\d+)$/.exec(block.id);
|
|
2508
|
+
if (match) max = Math.max(max, Number(match[1]));
|
|
2509
|
+
}
|
|
2510
|
+
return max;
|
|
2511
|
+
}
|
|
2312
2512
|
function formatDuration2(durationMs) {
|
|
2313
2513
|
if (durationMs < 1e3) return `${durationMs}ms`;
|
|
2314
2514
|
return `${Math.round(durationMs / 1e3)}s`;
|
|
@@ -2327,7 +2527,7 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
|
|
|
2327
2527
|
}
|
|
2328
2528
|
|
|
2329
2529
|
// src/ui/tui/controller.ts
|
|
2330
|
-
import
|
|
2530
|
+
import path33 from "node:path";
|
|
2331
2531
|
|
|
2332
2532
|
// src/agents/types.ts
|
|
2333
2533
|
function normalizeAgent(input2) {
|
|
@@ -25421,8 +25621,8 @@ function permissionLabel(req) {
|
|
|
25421
25621
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
25422
25622
|
return `${req.tool}: ${inputObject.path}`;
|
|
25423
25623
|
}
|
|
25424
|
-
const
|
|
25425
|
-
if (
|
|
25624
|
+
const path37 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
|
|
25625
|
+
if (path37) return `${tool}: ${path37}`;
|
|
25426
25626
|
return tool;
|
|
25427
25627
|
}
|
|
25428
25628
|
|
|
@@ -30046,8 +30246,8 @@ async function summarizeGoalTitleWithProvider(options2) {
|
|
|
30046
30246
|
}
|
|
30047
30247
|
function normalizeGoalTitle(value, objective) {
|
|
30048
30248
|
const raw = typeof value === "string" ? value : "";
|
|
30049
|
-
const
|
|
30050
|
-
const cleaned = stripDecorations(
|
|
30249
|
+
const firstLine2 = raw.split(/\r?\n/).find((line) => line.trim()) ?? "";
|
|
30250
|
+
const cleaned = stripDecorations(firstLine2);
|
|
30051
30251
|
return truncateGoalTitle(cleaned || fallbackGoalTitle(objective));
|
|
30052
30252
|
}
|
|
30053
30253
|
function fallbackGoalTitle(objective) {
|
|
@@ -31646,17 +31846,17 @@ function findDependencyCycle(group) {
|
|
|
31646
31846
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
31647
31847
|
const visiting = /* @__PURE__ */ new Set();
|
|
31648
31848
|
const visited = /* @__PURE__ */ new Set();
|
|
31649
|
-
const
|
|
31849
|
+
const path37 = [];
|
|
31650
31850
|
const visit = (id) => {
|
|
31651
|
-
if (visiting.has(id)) return [...
|
|
31851
|
+
if (visiting.has(id)) return [...path37.slice(path37.indexOf(id)), id];
|
|
31652
31852
|
if (visited.has(id)) return void 0;
|
|
31653
31853
|
visiting.add(id);
|
|
31654
|
-
|
|
31854
|
+
path37.push(id);
|
|
31655
31855
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
31656
31856
|
const cycle = visit(dep);
|
|
31657
31857
|
if (cycle) return cycle;
|
|
31658
31858
|
}
|
|
31659
|
-
|
|
31859
|
+
path37.pop();
|
|
31660
31860
|
visiting.delete(id);
|
|
31661
31861
|
visited.add(id);
|
|
31662
31862
|
return void 0;
|
|
@@ -32477,9 +32677,212 @@ function errorMessage3(error) {
|
|
|
32477
32677
|
return error instanceof Error ? error.message : String(error);
|
|
32478
32678
|
}
|
|
32479
32679
|
|
|
32680
|
+
// src/ui/conversations.ts
|
|
32681
|
+
import { mkdir as mkdir14, readFile as readFile16, readdir as readdir4, rm as rm4, writeFile as writeFile13 } from "node:fs/promises";
|
|
32682
|
+
import path32 from "node:path";
|
|
32683
|
+
var CONVERSATIONS_DIR = "conversations";
|
|
32684
|
+
var CONVERSATION_INDEX_FILE = "conversations.json";
|
|
32685
|
+
var CONVERSATION_FILE = "conversation.json";
|
|
32686
|
+
var MAX_STORED_CONVERSATIONS = 40;
|
|
32687
|
+
var MAX_MODEL_HISTORY = 80;
|
|
32688
|
+
function createConversationRecord(input2) {
|
|
32689
|
+
const timestamp = input2.now ?? Date.now();
|
|
32690
|
+
return {
|
|
32691
|
+
id: sanitizeConversationId(input2.id),
|
|
32692
|
+
title: input2.title?.trim() || "New session",
|
|
32693
|
+
createdAt: timestamp,
|
|
32694
|
+
updatedAt: timestamp,
|
|
32695
|
+
cwd: path32.resolve(input2.cwd),
|
|
32696
|
+
modelHistory: cleanModelHistory(input2.history ?? []),
|
|
32697
|
+
snapshot: input2.snapshot
|
|
32698
|
+
};
|
|
32699
|
+
}
|
|
32700
|
+
async function loadConversationIndex(storageDir = defaultDemianStorageDir()) {
|
|
32701
|
+
const index = await readJson(path32.join(storageDir, CONVERSATION_INDEX_FILE));
|
|
32702
|
+
const conversations = Array.isArray(index?.conversations) ? index.conversations.map((item) => {
|
|
32703
|
+
if (!item || typeof item !== "object") return void 0;
|
|
32704
|
+
const object2 = item;
|
|
32705
|
+
const id = typeof object2.id === "string" ? sanitizeConversationId(object2.id) : "";
|
|
32706
|
+
if (!id) return void 0;
|
|
32707
|
+
return {
|
|
32708
|
+
id,
|
|
32709
|
+
title: typeof object2.title === "string" && object2.title.trim() ? object2.title.trim() : "New session",
|
|
32710
|
+
updatedAt: numberOrNow(object2.updatedAt),
|
|
32711
|
+
cwd: typeof object2.cwd === "string" ? object2.cwd : void 0
|
|
32712
|
+
};
|
|
32713
|
+
}).filter((item) => Boolean(item)) : [];
|
|
32714
|
+
return {
|
|
32715
|
+
selectedSessionId: typeof index?.selectedSessionId === "string" ? sanitizeConversationId(index.selectedSessionId) : void 0,
|
|
32716
|
+
conversations
|
|
32717
|
+
};
|
|
32718
|
+
}
|
|
32719
|
+
async function loadConversationRecords(storageDir = defaultDemianStorageDir()) {
|
|
32720
|
+
const index = await loadConversationIndex(storageDir);
|
|
32721
|
+
const ids = new Set(index.conversations.map((item) => item.id));
|
|
32722
|
+
for (const id of await listConversationRecordIds(storageDir)) ids.add(id);
|
|
32723
|
+
const records = (await Promise.all(
|
|
32724
|
+
[...ids].map(async (id) => {
|
|
32725
|
+
const item = await readJson(conversationRecordPath(storageDir, id));
|
|
32726
|
+
return normalizeConversationRecord(item, id);
|
|
32727
|
+
})
|
|
32728
|
+
)).filter((item) => Boolean(item));
|
|
32729
|
+
records.sort((a, b2) => b2.updatedAt - a.updatedAt);
|
|
32730
|
+
return { selectedSessionId: index.selectedSessionId, records };
|
|
32731
|
+
}
|
|
32732
|
+
async function saveConversationRecords(storageDir, records, selectedSessionId) {
|
|
32733
|
+
const root = storageDir ?? defaultDemianStorageDir();
|
|
32734
|
+
const kept = [...records].sort((a, b2) => b2.updatedAt - a.updatedAt).slice(0, MAX_STORED_CONVERSATIONS).map((record) => ({ ...record, id: sanitizeConversationId(record.id), modelHistory: cleanModelHistory(record.modelHistory) }));
|
|
32735
|
+
await mkdir14(path32.join(root, CONVERSATIONS_DIR), { recursive: true });
|
|
32736
|
+
for (const record of kept) {
|
|
32737
|
+
await writeJson(conversationRecordPath(root, record.id), {
|
|
32738
|
+
version: 1,
|
|
32739
|
+
id: record.id,
|
|
32740
|
+
title: record.title,
|
|
32741
|
+
createdAt: record.createdAt,
|
|
32742
|
+
updatedAt: record.updatedAt,
|
|
32743
|
+
cwd: record.cwd,
|
|
32744
|
+
modelHistory: record.modelHistory,
|
|
32745
|
+
snapshot: record.snapshot
|
|
32746
|
+
});
|
|
32747
|
+
}
|
|
32748
|
+
await writeJson(path32.join(root, CONVERSATION_INDEX_FILE), {
|
|
32749
|
+
version: 1,
|
|
32750
|
+
selectedSessionId,
|
|
32751
|
+
conversations: kept.map((record) => ({
|
|
32752
|
+
id: record.id,
|
|
32753
|
+
title: record.title,
|
|
32754
|
+
updatedAt: record.updatedAt,
|
|
32755
|
+
cwd: record.cwd,
|
|
32756
|
+
path: conversationDir(root, record.id)
|
|
32757
|
+
}))
|
|
32758
|
+
});
|
|
32759
|
+
}
|
|
32760
|
+
async function deleteConversationRecord(storageDir, id) {
|
|
32761
|
+
const root = storageDir ?? defaultDemianStorageDir();
|
|
32762
|
+
await rm4(conversationDir(root, id), { recursive: true, force: true });
|
|
32763
|
+
}
|
|
32764
|
+
function findConversation(records, target) {
|
|
32765
|
+
const value = target?.trim();
|
|
32766
|
+
if (!value) return void 0;
|
|
32767
|
+
const index = Number(value);
|
|
32768
|
+
if (Number.isInteger(index) && index >= 1 && index <= records.length) return records[index - 1];
|
|
32769
|
+
const normalized = value.toLowerCase();
|
|
32770
|
+
return records.find((record) => record.id === value || record.id.startsWith(value)) ?? records.find((record) => record.title.toLowerCase() === normalized) ?? records.find((record) => record.title.toLowerCase().includes(normalized));
|
|
32771
|
+
}
|
|
32772
|
+
function sortConversationRecords(records, cwd) {
|
|
32773
|
+
const resolvedCwd = path32.resolve(cwd);
|
|
32774
|
+
return [...records].sort((a, b2) => {
|
|
32775
|
+
const aCurrent = isConversationForCwd(a, resolvedCwd) ? 0 : 1;
|
|
32776
|
+
const bCurrent = isConversationForCwd(b2, resolvedCwd) ? 0 : 1;
|
|
32777
|
+
if (aCurrent !== bCurrent) return aCurrent - bCurrent;
|
|
32778
|
+
const statusDelta = conversationStatusRank(conversationRuntimeStatus(a, resolvedCwd)) - conversationStatusRank(conversationRuntimeStatus(b2, resolvedCwd));
|
|
32779
|
+
if (statusDelta !== 0) return statusDelta;
|
|
32780
|
+
return b2.updatedAt - a.updatedAt;
|
|
32781
|
+
});
|
|
32782
|
+
}
|
|
32783
|
+
function conversationRuntimeStatus(record, cwd) {
|
|
32784
|
+
if (record.snapshot?.inputMode === "running") return "running";
|
|
32785
|
+
return isConversationForCwd(record, cwd) ? "ready" : "stored";
|
|
32786
|
+
}
|
|
32787
|
+
function isConversationForCwd(record, cwd) {
|
|
32788
|
+
return path32.resolve(record.cwd) === path32.resolve(cwd);
|
|
32789
|
+
}
|
|
32790
|
+
function conversationSummaryLine(record, index, activeId) {
|
|
32791
|
+
const active = record.id === activeId ? "*" : " ";
|
|
32792
|
+
const age = formatRelativeAge(Date.now() - record.updatedAt);
|
|
32793
|
+
return `${active} ${index + 1}. ${record.title} ${record.id} ${age} ${record.cwd}`;
|
|
32794
|
+
}
|
|
32795
|
+
function conversationStatusRank(status) {
|
|
32796
|
+
if (status === "running") return 0;
|
|
32797
|
+
if (status === "ready") return 1;
|
|
32798
|
+
return 2;
|
|
32799
|
+
}
|
|
32800
|
+
function titleFromHistory(history, fallback = "New session") {
|
|
32801
|
+
const firstUser = history.find((message) => message.role === "user" && typeof message.content === "string");
|
|
32802
|
+
return firstLine(firstUser?.content ?? fallback);
|
|
32803
|
+
}
|
|
32804
|
+
function cleanModelHistory(history) {
|
|
32805
|
+
return history.filter((message) => !isInternalModelHistoryMessage(message)).slice(-MAX_MODEL_HISTORY);
|
|
32806
|
+
}
|
|
32807
|
+
function conversationDir(storageDir, id) {
|
|
32808
|
+
return path32.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
|
|
32809
|
+
}
|
|
32810
|
+
function conversationRecordPath(storageDir, id) {
|
|
32811
|
+
return path32.join(conversationDir(storageDir, id), CONVERSATION_FILE);
|
|
32812
|
+
}
|
|
32813
|
+
async function listConversationRecordIds(storageDir) {
|
|
32814
|
+
try {
|
|
32815
|
+
const entries = await readdir4(path32.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
|
|
32816
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => sanitizeConversationId(entry.name)).filter(Boolean);
|
|
32817
|
+
} catch {
|
|
32818
|
+
return [];
|
|
32819
|
+
}
|
|
32820
|
+
}
|
|
32821
|
+
async function readJson(filePath) {
|
|
32822
|
+
try {
|
|
32823
|
+
return JSON.parse(await readFile16(filePath, "utf8"));
|
|
32824
|
+
} catch {
|
|
32825
|
+
return void 0;
|
|
32826
|
+
}
|
|
32827
|
+
}
|
|
32828
|
+
async function writeJson(filePath, value) {
|
|
32829
|
+
await mkdir14(path32.dirname(filePath), { recursive: true });
|
|
32830
|
+
await writeFile13(filePath, `${JSON.stringify(value, null, 2)}
|
|
32831
|
+
`, "utf8");
|
|
32832
|
+
}
|
|
32833
|
+
function normalizeConversationRecord(value, fallbackId) {
|
|
32834
|
+
if (!value || typeof value !== "object") return void 0;
|
|
32835
|
+
const object2 = value;
|
|
32836
|
+
const id = sanitizeConversationId(typeof object2.id === "string" ? object2.id : fallbackId);
|
|
32837
|
+
if (!id) return void 0;
|
|
32838
|
+
const history = Array.isArray(object2.modelHistory) ? object2.modelHistory.filter(isMessage) : [];
|
|
32839
|
+
const snapshot = object2.snapshot && typeof object2.snapshot === "object" && !Array.isArray(object2.snapshot) ? object2.snapshot : void 0;
|
|
32840
|
+
return {
|
|
32841
|
+
id,
|
|
32842
|
+
title: typeof object2.title === "string" && object2.title.trim() ? object2.title.trim() : titleFromHistory(history),
|
|
32843
|
+
createdAt: numberOrNow(object2.createdAt),
|
|
32844
|
+
updatedAt: numberOrNow(object2.updatedAt),
|
|
32845
|
+
cwd: typeof object2.cwd === "string" && object2.cwd.trim() ? object2.cwd : process.cwd(),
|
|
32846
|
+
modelHistory: cleanModelHistory(history),
|
|
32847
|
+
snapshot
|
|
32848
|
+
};
|
|
32849
|
+
}
|
|
32850
|
+
function isMessage(value) {
|
|
32851
|
+
if (!value || typeof value !== "object") return false;
|
|
32852
|
+
const message = value;
|
|
32853
|
+
if (message.role === "user") return typeof message.content === "string" || Array.isArray(message.content);
|
|
32854
|
+
if (message.role === "assistant") return message.content === void 0 || message.content === null || typeof message.content === "string" || Array.isArray(message.toolCalls);
|
|
32855
|
+
if (message.role === "tool") return typeof message.toolCallId === "string" && typeof message.name === "string" && typeof message.content === "string";
|
|
32856
|
+
if (message.role === "system") return typeof message.content === "string";
|
|
32857
|
+
return false;
|
|
32858
|
+
}
|
|
32859
|
+
function isInternalModelHistoryMessage(message) {
|
|
32860
|
+
return message.role === "user" && typeof message.content === "string" && /^You are running as sub agent:/i.test(message.content.trim());
|
|
32861
|
+
}
|
|
32862
|
+
function sanitizeConversationId(id) {
|
|
32863
|
+
return String(id || "conversation").replace(/[^a-zA-Z0-9._-]+/g, "_") || "conversation";
|
|
32864
|
+
}
|
|
32865
|
+
function numberOrNow(value) {
|
|
32866
|
+
return typeof value === "number" && Number.isFinite(value) ? value : Date.now();
|
|
32867
|
+
}
|
|
32868
|
+
function firstLine(value) {
|
|
32869
|
+
const line = value.replace(/\s+/g, " ").trim();
|
|
32870
|
+
if (!line) return "New session";
|
|
32871
|
+
return line.length > 60 ? `${line.slice(0, 57)}...` : line;
|
|
32872
|
+
}
|
|
32873
|
+
function formatRelativeAge(ms2) {
|
|
32874
|
+
const seconds = Math.max(0, Math.floor(ms2 / 1e3));
|
|
32875
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
32876
|
+
const minutes = Math.floor(seconds / 60);
|
|
32877
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
32878
|
+
const hours = Math.floor(minutes / 60);
|
|
32879
|
+
if (hours < 48) return `${hours}h ago`;
|
|
32880
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
32881
|
+
}
|
|
32882
|
+
|
|
32480
32883
|
// src/ui/tui/controller.ts
|
|
32481
32884
|
async function runTuiSession(flags, store2) {
|
|
32482
|
-
const cwd =
|
|
32885
|
+
const cwd = path33.resolve(flags.cwd ?? process.cwd());
|
|
32483
32886
|
const eventBus = new EventBus();
|
|
32484
32887
|
eventBus.subscribe((event) => store2.handleEvent(event));
|
|
32485
32888
|
const loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
@@ -32511,10 +32914,20 @@ async function runTuiSession(flags, store2) {
|
|
|
32511
32914
|
}
|
|
32512
32915
|
});
|
|
32513
32916
|
let nextPrompt = flags.prompt;
|
|
32514
|
-
|
|
32917
|
+
const conversationsEnabled = flags.conversationManagement === true;
|
|
32918
|
+
const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
|
|
32919
|
+
let currentConversation = conversationState?.current;
|
|
32920
|
+
const conversationRecords = conversationState?.records ?? /* @__PURE__ */ new Map();
|
|
32921
|
+
const shouldShowStartupSessionSelector = conversationsEnabled && !flags.prompt.trim() && !flags.sessionId;
|
|
32922
|
+
let history = flags.initialHistory ? [...flags.initialHistory] : currentConversation?.modelHistory ? [...currentConversation.modelHistory] : [];
|
|
32515
32923
|
let lastExternalSessionKey;
|
|
32516
|
-
|
|
32517
|
-
const
|
|
32924
|
+
let externalSessions = new ClaudeCodeSessionMap();
|
|
32925
|
+
const runtimeByConversation = /* @__PURE__ */ new Map();
|
|
32926
|
+
let rootSessionId = currentConversation?.id ?? flags.sessionId ?? createRootSessionId();
|
|
32927
|
+
if (currentConversation && !shouldShowStartupSessionSelector) {
|
|
32928
|
+
store2.restoreConversationSnapshot(currentConversation.snapshot, { sessionId: currentConversation.id, title: currentConversation.title, cwd: currentConversation.cwd });
|
|
32929
|
+
runtimeByConversation.set(currentConversation.id, { externalSessions, lastExternalSessionKey });
|
|
32930
|
+
}
|
|
32518
32931
|
const goalTitleGenerator = async (input2) => {
|
|
32519
32932
|
const selection = store2.currentSelection();
|
|
32520
32933
|
const runtime = resolveProviderRuntimeConfig(config, selection);
|
|
@@ -32532,6 +32945,14 @@ async function runTuiSession(flags, store2) {
|
|
|
32532
32945
|
if (resolved.kind === "external-agent") return input2.objective.trim().split(/\s+/).slice(0, 8).join(" ");
|
|
32533
32946
|
return summarizeGoalTitleWithProvider({ ...input2, provider: resolved.provider, model: resolved.model });
|
|
32534
32947
|
};
|
|
32948
|
+
if (shouldShowStartupSessionSelector) {
|
|
32949
|
+
const selected = await store2.requestSessionSelection(startupSessionOptions());
|
|
32950
|
+
if (selected.kind === "exit" || store2.exitRequested()) {
|
|
32951
|
+
await saveCurrentSelection();
|
|
32952
|
+
return 0;
|
|
32953
|
+
}
|
|
32954
|
+
await applyStartupSessionSelection(selected);
|
|
32955
|
+
}
|
|
32535
32956
|
for (; ; ) {
|
|
32536
32957
|
const prompt = await store2.requestPrompt(nextPrompt);
|
|
32537
32958
|
nextPrompt = void 0;
|
|
@@ -32540,11 +32961,17 @@ async function runTuiSession(flags, store2) {
|
|
|
32540
32961
|
return 0;
|
|
32541
32962
|
}
|
|
32542
32963
|
if (!prompt) return 0;
|
|
32964
|
+
const sessionCommand = parseSessionCommand(prompt);
|
|
32965
|
+
if (sessionCommand) {
|
|
32966
|
+
await handleSessionCommand(sessionCommand);
|
|
32967
|
+
continue;
|
|
32968
|
+
}
|
|
32543
32969
|
if (isCompactCommand(prompt)) {
|
|
32544
32970
|
const result = compactInteractiveHistory(history, config.context.main);
|
|
32545
32971
|
history = result.messages;
|
|
32546
32972
|
store2.prepareForPrompt("waiting for next message");
|
|
32547
32973
|
store2.markHistoryCompacted(result);
|
|
32974
|
+
await persistCurrentConversation();
|
|
32548
32975
|
continue;
|
|
32549
32976
|
}
|
|
32550
32977
|
const mode = resolveAgentMode(config, flags.mode);
|
|
@@ -32696,6 +33123,7 @@ async function runTuiSession(flags, store2) {
|
|
|
32696
33123
|
history = interactiveHistoryFromRunMessages(result.messages);
|
|
32697
33124
|
lastExternalSessionKey = externalSessionKey ?? lastExternalSessionKey;
|
|
32698
33125
|
}
|
|
33126
|
+
await persistCurrentConversation();
|
|
32699
33127
|
} catch (error) {
|
|
32700
33128
|
store2.setStopTask(void 0);
|
|
32701
33129
|
if (activeAbort?.signal.aborted) {
|
|
@@ -32708,6 +33136,7 @@ async function runTuiSession(flags, store2) {
|
|
|
32708
33136
|
store2.setStopTask(void 0);
|
|
32709
33137
|
}
|
|
32710
33138
|
if (store2.exitRequested()) {
|
|
33139
|
+
await persistCurrentConversation();
|
|
32711
33140
|
await saveCurrentSelection();
|
|
32712
33141
|
return 0;
|
|
32713
33142
|
}
|
|
@@ -32716,6 +33145,161 @@ async function runTuiSession(flags, store2) {
|
|
|
32716
33145
|
async function saveCurrentSelection() {
|
|
32717
33146
|
await saveSelection(store2.currentSelection());
|
|
32718
33147
|
}
|
|
33148
|
+
async function handleSessionCommand(command) {
|
|
33149
|
+
if (!conversationsEnabled) {
|
|
33150
|
+
store2.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
|
|
33151
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33152
|
+
return;
|
|
33153
|
+
}
|
|
33154
|
+
if (!currentConversation) {
|
|
33155
|
+
currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
|
|
33156
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
33157
|
+
}
|
|
33158
|
+
if (command.action === "help") {
|
|
33159
|
+
store2.showSystemMessage("Sessions", sessionUsage());
|
|
33160
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33161
|
+
return;
|
|
33162
|
+
}
|
|
33163
|
+
if (command.action === "list") {
|
|
33164
|
+
store2.showSystemMessage("Sessions", sessionListMessage());
|
|
33165
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33166
|
+
return;
|
|
33167
|
+
}
|
|
33168
|
+
if (command.action === "current") {
|
|
33169
|
+
store2.showSystemMessage("Current Session", conversationSummaryLine(currentConversation, sortedConversations().findIndex((item) => item.id === currentConversation?.id), currentConversation.id));
|
|
33170
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33171
|
+
return;
|
|
33172
|
+
}
|
|
33173
|
+
if (command.action === "new") {
|
|
33174
|
+
await persistCurrentConversation();
|
|
33175
|
+
const next = createConversationRecord({ id: createRootSessionId(), cwd, title: command.title });
|
|
33176
|
+
conversationRecords.set(next.id, next);
|
|
33177
|
+
await switchConversation(next);
|
|
33178
|
+
store2.prepareForPrompt(`new session: ${next.title}`);
|
|
33179
|
+
await persistCurrentConversation();
|
|
33180
|
+
return;
|
|
33181
|
+
}
|
|
33182
|
+
if (command.action === "switch") {
|
|
33183
|
+
const target = findConversation(sortedConversations(), command.target);
|
|
33184
|
+
if (!target) {
|
|
33185
|
+
store2.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".
|
|
33186
|
+
|
|
33187
|
+
${sessionListMessage()}`);
|
|
33188
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33189
|
+
return;
|
|
33190
|
+
}
|
|
33191
|
+
await persistCurrentConversation();
|
|
33192
|
+
await switchConversation(target);
|
|
33193
|
+
store2.prepareForPrompt(`switched session: ${target.title}`);
|
|
33194
|
+
await persistCurrentConversation();
|
|
33195
|
+
return;
|
|
33196
|
+
}
|
|
33197
|
+
if (command.action === "delete") {
|
|
33198
|
+
const target = findConversation(sortedConversations(), command.target);
|
|
33199
|
+
if (!target) {
|
|
33200
|
+
store2.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
|
|
33201
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33202
|
+
return;
|
|
33203
|
+
}
|
|
33204
|
+
conversationRecords.delete(target.id);
|
|
33205
|
+
runtimeByConversation.delete(target.id);
|
|
33206
|
+
await deleteConversationRecord(flags.conversationStorageDir, target.id);
|
|
33207
|
+
if (currentConversation?.id === target.id) {
|
|
33208
|
+
const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33209
|
+
conversationRecords.set(next.id, next);
|
|
33210
|
+
await switchConversation(next);
|
|
33211
|
+
}
|
|
33212
|
+
store2.prepareForPrompt(`deleted session: ${target.title}`);
|
|
33213
|
+
await persistCurrentConversation();
|
|
33214
|
+
return;
|
|
33215
|
+
}
|
|
33216
|
+
if (command.action === "rename") {
|
|
33217
|
+
const title = command.title?.trim();
|
|
33218
|
+
if (!title) {
|
|
33219
|
+
store2.showSystemMessage("Sessions", "Usage: /session rename <title>");
|
|
33220
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33221
|
+
return;
|
|
33222
|
+
}
|
|
33223
|
+
currentConversation.title = title;
|
|
33224
|
+
currentConversation.updatedAt = Date.now();
|
|
33225
|
+
store2.prepareForPrompt(`renamed session: ${title}`);
|
|
33226
|
+
await persistCurrentConversation();
|
|
33227
|
+
return;
|
|
33228
|
+
}
|
|
33229
|
+
if (command.action === "clear") {
|
|
33230
|
+
history = [];
|
|
33231
|
+
currentConversation.modelHistory = [];
|
|
33232
|
+
currentConversation.snapshot = void 0;
|
|
33233
|
+
currentConversation.title = "New session";
|
|
33234
|
+
currentConversation.updatedAt = Date.now();
|
|
33235
|
+
store2.clearConversation(currentConversation.title);
|
|
33236
|
+
store2.prepareForPrompt("cleared current conversation");
|
|
33237
|
+
await persistCurrentConversation();
|
|
33238
|
+
}
|
|
33239
|
+
}
|
|
33240
|
+
function startupSessionOptions() {
|
|
33241
|
+
const records = sortedConversations();
|
|
33242
|
+
const sessionOptions = records.map((record) => ({
|
|
33243
|
+
kind: "session",
|
|
33244
|
+
id: record.id,
|
|
33245
|
+
title: record.title,
|
|
33246
|
+
cwd: record.cwd,
|
|
33247
|
+
status: conversationRuntimeStatus(record, cwd),
|
|
33248
|
+
updatedAt: record.updatedAt,
|
|
33249
|
+
currentWorkspace: path33.resolve(record.cwd) === cwd
|
|
33250
|
+
}));
|
|
33251
|
+
return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
|
|
33252
|
+
}
|
|
33253
|
+
async function applyStartupSessionSelection(selection) {
|
|
33254
|
+
if (selection.kind === "new") {
|
|
33255
|
+
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33256
|
+
conversationRecords.set(next.id, next);
|
|
33257
|
+
await switchConversation(next);
|
|
33258
|
+
store2.prepareForPrompt("new session ready");
|
|
33259
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], next.id);
|
|
33260
|
+
return;
|
|
33261
|
+
}
|
|
33262
|
+
const target = conversationRecords.get(selection.id) ?? findConversation(sortedConversations(), selection.id);
|
|
33263
|
+
if (!target) {
|
|
33264
|
+
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33265
|
+
conversationRecords.set(next.id, next);
|
|
33266
|
+
await switchConversation(next);
|
|
33267
|
+
store2.prepareForPrompt("new session ready");
|
|
33268
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], next.id);
|
|
33269
|
+
return;
|
|
33270
|
+
}
|
|
33271
|
+
await switchConversation(target);
|
|
33272
|
+
store2.prepareForPrompt(`session ready: ${target.title}`);
|
|
33273
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
|
|
33274
|
+
}
|
|
33275
|
+
async function switchConversation(record) {
|
|
33276
|
+
currentConversation = record;
|
|
33277
|
+
rootSessionId = record.id;
|
|
33278
|
+
history = [...record.modelHistory];
|
|
33279
|
+
const runtime = runtimeByConversation.get(record.id) ?? { externalSessions: new ClaudeCodeSessionMap(), lastExternalSessionKey: void 0 };
|
|
33280
|
+
externalSessions = runtime.externalSessions;
|
|
33281
|
+
lastExternalSessionKey = runtime.lastExternalSessionKey;
|
|
33282
|
+
runtimeByConversation.set(record.id, runtime);
|
|
33283
|
+
store2.restoreConversationSnapshot(record.snapshot, { sessionId: record.id, title: record.title, cwd: record.cwd || cwd });
|
|
33284
|
+
}
|
|
33285
|
+
async function persistCurrentConversation() {
|
|
33286
|
+
if (!conversationsEnabled || !currentConversation) return;
|
|
33287
|
+
currentConversation.modelHistory = cleanModelHistory(history);
|
|
33288
|
+
if (currentConversation.title === "New session" && currentConversation.modelHistory.length > 0) currentConversation.title = titleFromHistory(currentConversation.modelHistory);
|
|
33289
|
+
currentConversation.snapshot = store2.conversationSnapshot();
|
|
33290
|
+
currentConversation.updatedAt = Date.now();
|
|
33291
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
33292
|
+
runtimeByConversation.set(currentConversation.id, { externalSessions, lastExternalSessionKey });
|
|
33293
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], currentConversation.id);
|
|
33294
|
+
}
|
|
33295
|
+
function sortedConversations() {
|
|
33296
|
+
return sortConversationRecords([...conversationRecords.values()], cwd);
|
|
33297
|
+
}
|
|
33298
|
+
function sessionListMessage() {
|
|
33299
|
+
const records = sortedConversations();
|
|
33300
|
+
if (!records.length) return "No saved sessions.";
|
|
33301
|
+
return ["Saved sessions:", ...records.map((record, index) => conversationSummaryLine(record, index, currentConversation?.id)), "", "Use /session switch <number|id|title> to open one."].join("\n");
|
|
33302
|
+
}
|
|
32719
33303
|
async function saveSelection(selection) {
|
|
32720
33304
|
if (!shouldPersistSelection(selection)) return;
|
|
32721
33305
|
const key = preferenceKey(selection);
|
|
@@ -32727,18 +33311,46 @@ async function runTuiSession(flags, store2) {
|
|
|
32727
33311
|
function isGoalStateClearingAction(action) {
|
|
32728
33312
|
return action === "status" || action === "pause" || action === "resume";
|
|
32729
33313
|
}
|
|
33314
|
+
async function initializeConversationState(cwd, flags) {
|
|
33315
|
+
const loaded = await loadConversationRecords(flags.conversationStorageDir);
|
|
33316
|
+
const records = new Map(loaded.records.map((record) => [record.id, record]));
|
|
33317
|
+
const shouldResumeSelected = !flags.prompt.trim();
|
|
33318
|
+
const orderedRecords = sortConversationRecords([...records.values()], cwd);
|
|
33319
|
+
const preferred = (flags.sessionId ? findConversation(orderedRecords, flags.sessionId) : void 0) ?? (shouldResumeSelected && loaded.selectedSessionId ? findConversation(orderedRecords, loaded.selectedSessionId) : void 0) ?? (shouldResumeSelected ? orderedRecords[0] : void 0);
|
|
33320
|
+
const current = preferred ?? (flags.prompt.trim() || flags.sessionId || flags.initialHistory?.length ? createConversationRecord({
|
|
33321
|
+
id: flags.sessionId ?? createRootSessionId(),
|
|
33322
|
+
cwd,
|
|
33323
|
+
history: flags.initialHistory
|
|
33324
|
+
}) : void 0);
|
|
33325
|
+
if (current && flags.initialHistory?.length) current.modelHistory = cleanModelHistory(flags.initialHistory);
|
|
33326
|
+
if (current) records.set(current.id, current);
|
|
33327
|
+
return { records, current };
|
|
33328
|
+
}
|
|
33329
|
+
function sessionUsage() {
|
|
33330
|
+
return [
|
|
33331
|
+
"Usage:",
|
|
33332
|
+
" /session list",
|
|
33333
|
+
" /session new [title]",
|
|
33334
|
+
" /session switch <number|id|title>",
|
|
33335
|
+
" /session rename <title>",
|
|
33336
|
+
" /session delete <number|id|title>",
|
|
33337
|
+
" /session clear",
|
|
33338
|
+
"",
|
|
33339
|
+
"Aliases: /sessions, /conversation, /conversations, /conv"
|
|
33340
|
+
].join("\n");
|
|
33341
|
+
}
|
|
32730
33342
|
|
|
32731
33343
|
// src/config-watcher.ts
|
|
32732
33344
|
import { EventEmitter } from "node:events";
|
|
32733
33345
|
import fs11 from "node:fs";
|
|
32734
|
-
import
|
|
33346
|
+
import path35 from "node:path";
|
|
32735
33347
|
|
|
32736
33348
|
// src/config-scaffold.ts
|
|
32737
|
-
import { chmod as chmod3, mkdir as
|
|
33349
|
+
import { chmod as chmod3, mkdir as mkdir15, readFile as readFile17, rename as rename6, stat as stat8, writeFile as writeFile14 } from "node:fs/promises";
|
|
32738
33350
|
import os13 from "node:os";
|
|
32739
|
-
import
|
|
33351
|
+
import path34 from "node:path";
|
|
32740
33352
|
function defaultUserConfigPath() {
|
|
32741
|
-
return
|
|
33353
|
+
return path34.join(os13.homedir(), ".demian", "config.json");
|
|
32742
33354
|
}
|
|
32743
33355
|
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
32744
33356
|
return {
|
|
@@ -32859,7 +33471,7 @@ async function createUserConfig(options2 = {}) {
|
|
|
32859
33471
|
`;
|
|
32860
33472
|
if (options2.print) return { path: filePath, created: false, content };
|
|
32861
33473
|
const existed = await exists(filePath);
|
|
32862
|
-
if (existed && !options2.force) return { path: filePath, created: false, content: await
|
|
33474
|
+
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile17(filePath, "utf8"), existed: true };
|
|
32863
33475
|
await writeJsonAtomic(filePath, content);
|
|
32864
33476
|
return { path: filePath, created: true, content, existed };
|
|
32865
33477
|
}
|
|
@@ -32922,7 +33534,7 @@ async function updateConfigDefaults(options2) {
|
|
|
32922
33534
|
}
|
|
32923
33535
|
async function readConfigObject(filePath) {
|
|
32924
33536
|
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
32925
|
-
const raw = JSON.parse(await
|
|
33537
|
+
const raw = JSON.parse(await readFile17(filePath, "utf8"));
|
|
32926
33538
|
raw.version ??= 2;
|
|
32927
33539
|
raw.providers = objectValue(raw.providers);
|
|
32928
33540
|
return raw;
|
|
@@ -32983,9 +33595,9 @@ function apiKeyAuthFields(options2, defaultApiKeyEnv) {
|
|
|
32983
33595
|
};
|
|
32984
33596
|
}
|
|
32985
33597
|
async function writeJsonAtomic(filePath, content) {
|
|
32986
|
-
await
|
|
33598
|
+
await mkdir15(path34.dirname(filePath), { recursive: true, mode: 448 });
|
|
32987
33599
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32988
|
-
await
|
|
33600
|
+
await writeFile14(temp, content, { mode: 384 });
|
|
32989
33601
|
await rename6(temp, filePath);
|
|
32990
33602
|
await chmod3(filePath, 384).catch(() => void 0);
|
|
32991
33603
|
}
|
|
@@ -33036,9 +33648,9 @@ var ConfigWatcher = class extends EventEmitter {
|
|
|
33036
33648
|
this.#started = true;
|
|
33037
33649
|
this.#lastSeen = this.#snapshot();
|
|
33038
33650
|
try {
|
|
33039
|
-
fs11.mkdirSync(
|
|
33040
|
-
this.#watcher = fs11.watch(
|
|
33041
|
-
if (filename && filename !==
|
|
33651
|
+
fs11.mkdirSync(path35.dirname(this.#filePath), { recursive: true, mode: 448 });
|
|
33652
|
+
this.#watcher = fs11.watch(path35.dirname(this.#filePath), { persistent: false }, (_eventType, filename) => {
|
|
33653
|
+
if (filename && filename !== path35.basename(this.#filePath)) return;
|
|
33042
33654
|
this.#schedule();
|
|
33043
33655
|
});
|
|
33044
33656
|
this.#watcher.on("error", () => void 0);
|
|
@@ -33180,6 +33792,7 @@ if (process.argv.includes("--config-path")) {
|
|
|
33180
33792
|
process.exit(0);
|
|
33181
33793
|
}
|
|
33182
33794
|
var options = parseOptions();
|
|
33795
|
+
var SNAPSHOT_DEBOUNCE_MS = 80;
|
|
33183
33796
|
var store = new TuiStore();
|
|
33184
33797
|
var diffState = normalizeDiffState(options.initialSnapshot?.diff);
|
|
33185
33798
|
var restoredSnapshot = normalizeInitialPublicState(options.initialSnapshot);
|
|
@@ -33305,14 +33918,14 @@ async function undoDiffAction(actionId) {
|
|
|
33305
33918
|
if (!action) throw new Error("No diff action is available to undo.");
|
|
33306
33919
|
if (!canUndoDiffAction(diffState, action.id)) throw new Error("Only the latest change for a file can be undone.");
|
|
33307
33920
|
if (!hasBeforeSnapshot(action)) throw new Error("This diff was not recorded with enough content to undo safely.");
|
|
33308
|
-
const target =
|
|
33309
|
-
const relative =
|
|
33310
|
-
if (relative.startsWith("..") ||
|
|
33921
|
+
const target = path36.resolve(options.cwd, action.path);
|
|
33922
|
+
const relative = path36.relative(options.cwd, target);
|
|
33923
|
+
if (relative.startsWith("..") || path36.isAbsolute(relative)) throw new Error("Refusing to undo a file outside the workspace.");
|
|
33311
33924
|
if (!action.beforeExists) {
|
|
33312
|
-
await
|
|
33925
|
+
await rm5(target, { force: true });
|
|
33313
33926
|
} else {
|
|
33314
|
-
await
|
|
33315
|
-
await
|
|
33927
|
+
await mkdir16(path36.dirname(target), { recursive: true });
|
|
33928
|
+
await writeFile15(target, action.beforeContent ?? "", "utf8");
|
|
33316
33929
|
}
|
|
33317
33930
|
markDiffActionUndone(action.id);
|
|
33318
33931
|
send({ type: "diffUndoCompleted", actionId: action.id, path: action.path });
|
|
@@ -33405,7 +34018,7 @@ function scheduleSnapshot() {
|
|
|
33405
34018
|
snapshotTimer = setTimeout(() => {
|
|
33406
34019
|
snapshotTimer = void 0;
|
|
33407
34020
|
send({ type: "snapshot", state: publicState(store.snapshot()) });
|
|
33408
|
-
},
|
|
34021
|
+
}, SNAPSHOT_DEBOUNCE_MS);
|
|
33409
34022
|
}
|
|
33410
34023
|
function sendSnapshotNow() {
|
|
33411
34024
|
if (snapshotTimer) {
|