demian-cli 1.1.0 → 1.1.2
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 +46 -3
- package/dist/cli.mjs +146 -3
- package/dist/tui.mjs +3192 -588
- package/dist/vscode-worker.mjs +1600 -439
- package/docs/ko/README.md +44 -3
- package/package.json +1 -1
package/dist/vscode-worker.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/vscode-worker.ts
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
5
|
+
import { mkdir as mkdir16, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
|
|
6
|
+
import path36 from "node:path";
|
|
6
7
|
|
|
7
8
|
// src/permissions/presets.ts
|
|
8
9
|
var PERMISSION_PRESETS = ["deny", "ask", "auto", "full"];
|
|
@@ -67,6 +68,26 @@ function isCompactCommand(input2) {
|
|
|
67
68
|
function isRetryCommand(input2) {
|
|
68
69
|
return input2.trim().toLowerCase() === "/retry";
|
|
69
70
|
}
|
|
71
|
+
function isSessionCommand(input2) {
|
|
72
|
+
return /^\/(?:session|sessions|conversation|conversations|conv)(?:\s|$)/i.test(input2.trim());
|
|
73
|
+
}
|
|
74
|
+
function parseSessionCommand(input2) {
|
|
75
|
+
const raw = input2.trim();
|
|
76
|
+
if (!isSessionCommand(raw)) return void 0;
|
|
77
|
+
const rest = raw.replace(/^\/(?:session|sessions|conversation|conversations|conv)\b/i, "").trim();
|
|
78
|
+
if (!rest) return { action: "help" };
|
|
79
|
+
const tokens = shellishSplit(rest);
|
|
80
|
+
const action = tokens.shift()?.toLowerCase() ?? "help";
|
|
81
|
+
if (action === "help" || action === "-h" || action === "--help") return { action: "help" };
|
|
82
|
+
if (action === "list" || action === "ls") return { action: "list" };
|
|
83
|
+
if (action === "current" || action === "status") return { action: "current" };
|
|
84
|
+
if (action === "new" || action === "create") return { action: "new", title: tokens.join(" ").trim() || void 0 };
|
|
85
|
+
if (action === "switch" || action === "select" || action === "use" || action === "open") return { action: "switch", target: tokens.join(" ").trim() || void 0 };
|
|
86
|
+
if (action === "delete" || action === "remove" || action === "rm") return { action: "delete", target: tokens.join(" ").trim() || void 0 };
|
|
87
|
+
if (action === "rename" || action === "name") return { action: "rename", title: tokens.join(" ").trim() || void 0 };
|
|
88
|
+
if (action === "clear" || action === "reset") return { action: "clear" };
|
|
89
|
+
return { action: "switch", target: [action, ...tokens].join(" ").trim() };
|
|
90
|
+
}
|
|
70
91
|
function isCoworkCommand(input2) {
|
|
71
92
|
return /^\/cowork(?:\s|$)/i.test(input2.trim());
|
|
72
93
|
}
|
|
@@ -742,14 +763,40 @@ function isPathLikeKey(key) {
|
|
|
742
763
|
}
|
|
743
764
|
|
|
744
765
|
// src/ui/tui/store.ts
|
|
766
|
+
var TUI_CONFIG_ACTION_PREFIX = "demian-config:";
|
|
767
|
+
var TUI_SESSION_ACTION_PREFIX = "demian-session:";
|
|
768
|
+
function parseTuiConfigAction(prompt) {
|
|
769
|
+
if (!prompt.startsWith(TUI_CONFIG_ACTION_PREFIX)) return void 0;
|
|
770
|
+
try {
|
|
771
|
+
return JSON.parse(prompt.slice(TUI_CONFIG_ACTION_PREFIX.length));
|
|
772
|
+
} catch {
|
|
773
|
+
return void 0;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function parseTuiSessionAction(prompt) {
|
|
777
|
+
if (!prompt.startsWith(TUI_SESSION_ACTION_PREFIX)) return void 0;
|
|
778
|
+
try {
|
|
779
|
+
return JSON.parse(prompt.slice(TUI_SESSION_ACTION_PREFIX.length));
|
|
780
|
+
} catch {
|
|
781
|
+
return void 0;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
745
784
|
var TuiStore = class {
|
|
746
785
|
#state = {
|
|
747
786
|
status: {},
|
|
748
787
|
blocks: [],
|
|
749
788
|
activity: "starting",
|
|
750
789
|
inputMode: "starting",
|
|
790
|
+
sessionOptions: [],
|
|
791
|
+
sessionCursor: 0,
|
|
792
|
+
sessionCancelBehavior: "exit",
|
|
751
793
|
providerOptions: [],
|
|
752
794
|
providerCursor: 0,
|
|
795
|
+
configModelCursor: 0,
|
|
796
|
+
configAddProviderCursor: 0,
|
|
797
|
+
configModelInput: "",
|
|
798
|
+
configProviderInput: "",
|
|
799
|
+
configProviderPresets: defaultConfigProviderPresets(),
|
|
753
800
|
agentOptions: [],
|
|
754
801
|
agentCursor: 0,
|
|
755
802
|
permissionPreset: "auto",
|
|
@@ -769,6 +816,7 @@ var TuiStore = class {
|
|
|
769
816
|
#blockId = 0;
|
|
770
817
|
#stopActiveTask;
|
|
771
818
|
#promptResolve;
|
|
819
|
+
#sessionResolve;
|
|
772
820
|
#queuedPrompt;
|
|
773
821
|
#exitRequested = false;
|
|
774
822
|
#promptHistory = [];
|
|
@@ -784,12 +832,15 @@ var TuiStore = class {
|
|
|
784
832
|
#pendingPermissionTimeout;
|
|
785
833
|
#syntheticPermissionId = 0;
|
|
786
834
|
#activeClaudeCodePlan;
|
|
835
|
+
#notifyTimer;
|
|
787
836
|
snapshot() {
|
|
788
837
|
return {
|
|
789
838
|
...this.#state,
|
|
790
839
|
status: { ...this.#state.status },
|
|
791
840
|
selection: this.#state.selection ? { ...this.#state.selection } : void 0,
|
|
841
|
+
sessionOptions: this.#state.sessionOptions.map((option) => ({ ...option })),
|
|
792
842
|
providerOptions: this.#state.providerOptions.map((option) => ({ ...option })),
|
|
843
|
+
configProviderPresets: this.#state.configProviderPresets.map((option) => ({ ...option })),
|
|
793
844
|
agentOptions: this.#state.agentOptions.map((option) => ({ ...option })),
|
|
794
845
|
blocks: this.#state.blocks.map((block) => ({ ...block, lines: [...block.lines], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })),
|
|
795
846
|
warnings: [...this.#state.warnings],
|
|
@@ -827,6 +878,7 @@ var TuiStore = class {
|
|
|
827
878
|
requestExit() {
|
|
828
879
|
this.#exitRequested = true;
|
|
829
880
|
if (this.#promptResolve) this.#resolvePrompt("");
|
|
881
|
+
if (this.#sessionResolve) this.#resolveSession({ kind: "exit" });
|
|
830
882
|
this.#stopActiveTask?.();
|
|
831
883
|
this.#state.inputMode = "done";
|
|
832
884
|
this.#state.done = true;
|
|
@@ -906,6 +958,84 @@ var TuiStore = class {
|
|
|
906
958
|
this.#state.activity = `context compacted: ${summary.beforeTokenEstimate} -> ${summary.afterTokenEstimate}`;
|
|
907
959
|
this.#notify();
|
|
908
960
|
}
|
|
961
|
+
conversationSnapshot() {
|
|
962
|
+
const snapshot = this.snapshot();
|
|
963
|
+
return {
|
|
964
|
+
...snapshot,
|
|
965
|
+
inputMode: "prompt",
|
|
966
|
+
promptInput: "",
|
|
967
|
+
promptError: void 0,
|
|
968
|
+
streamingText: "",
|
|
969
|
+
streamingFinalized: true,
|
|
970
|
+
permission: void 0,
|
|
971
|
+
done: false
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
restoreConversationSnapshot(snapshot, fallback) {
|
|
975
|
+
this.#resetTransientConversationState();
|
|
976
|
+
const currentSettings = {
|
|
977
|
+
selection: this.#state.selection,
|
|
978
|
+
providerOptions: this.#state.providerOptions,
|
|
979
|
+
providerCursor: this.#state.providerCursor,
|
|
980
|
+
agentOptions: this.#state.agentOptions,
|
|
981
|
+
agentCursor: this.#state.agentCursor,
|
|
982
|
+
selectedAgent: this.#state.selectedAgent,
|
|
983
|
+
permissionPreset: this.#state.permissionPreset,
|
|
984
|
+
permissionPresetCursor: this.#state.permissionPresetCursor,
|
|
985
|
+
contextEfficiency: this.#state.contextEfficiency
|
|
986
|
+
};
|
|
987
|
+
const restoredSelection = snapshot?.selection ? { ...snapshot.selection } : currentSettings.selection;
|
|
988
|
+
const restoredAgent = snapshot?.selectedAgent ?? snapshot?.status?.agent ?? currentSettings.selectedAgent;
|
|
989
|
+
const restoredPermissionPreset = normalizePermissionPreset(snapshot?.permissionPreset, currentSettings.permissionPreset);
|
|
990
|
+
this.#state = {
|
|
991
|
+
...this.#state,
|
|
992
|
+
...safeConversationSnapshot(snapshot),
|
|
993
|
+
...currentSettings,
|
|
994
|
+
selection: restoredSelection,
|
|
995
|
+
providerCursor: Math.max(0, currentSettings.providerOptions.findIndex((option) => option.name === restoredSelection?.providerName)),
|
|
996
|
+
selectedAgent: restoredAgent,
|
|
997
|
+
agentCursor: Math.max(0, currentSettings.agentOptions.findIndex((option) => option.name === restoredAgent)),
|
|
998
|
+
permissionPreset: restoredPermissionPreset,
|
|
999
|
+
permissionPresetCursor: Math.max(0, PERMISSION_PRESETS.indexOf(restoredPermissionPreset)),
|
|
1000
|
+
status: {
|
|
1001
|
+
...snapshot?.status ?? this.#state.status,
|
|
1002
|
+
sessionId: fallback.sessionId,
|
|
1003
|
+
cwd: fallback.cwd,
|
|
1004
|
+
provider: restoredSelection?.providerName ?? snapshot?.status?.provider ?? this.#state.status.provider,
|
|
1005
|
+
model: restoredSelection?.model ?? snapshot?.status?.model ?? this.#state.status.model,
|
|
1006
|
+
agent: restoredAgent ?? snapshot?.status?.agent ?? this.#state.status.agent
|
|
1007
|
+
},
|
|
1008
|
+
inputMode: "prompt",
|
|
1009
|
+
promptInput: "",
|
|
1010
|
+
promptError: void 0,
|
|
1011
|
+
streamingText: "",
|
|
1012
|
+
streamingFinalized: true,
|
|
1013
|
+
permission: void 0,
|
|
1014
|
+
done: false,
|
|
1015
|
+
activity: `conversation: ${fallback.title}`
|
|
1016
|
+
};
|
|
1017
|
+
this.#blockId = nextBlockId(this.#state.blocks);
|
|
1018
|
+
this.#notify();
|
|
1019
|
+
}
|
|
1020
|
+
clearConversation(title = "New session") {
|
|
1021
|
+
this.#resetTransientConversationState();
|
|
1022
|
+
this.#state.blocks = [];
|
|
1023
|
+
this.#state.streamingText = "";
|
|
1024
|
+
this.#state.streamingFinalized = true;
|
|
1025
|
+
this.#state.permission = void 0;
|
|
1026
|
+
this.#state.warnings = [];
|
|
1027
|
+
this.#state.done = false;
|
|
1028
|
+
this.#state.finalAnswer = void 0;
|
|
1029
|
+
this.#state.progressNotes = [];
|
|
1030
|
+
this.#state.turnDiff = void 0;
|
|
1031
|
+
this.#state.diffExpanded = false;
|
|
1032
|
+
this.#state.workPlan = void 0;
|
|
1033
|
+
this.#state.workPlanExpanded = false;
|
|
1034
|
+
this.#state.goal = void 0;
|
|
1035
|
+
this.#state.pendingClaudeCodePlan = void 0;
|
|
1036
|
+
this.#state.activity = `cleared conversation: ${title}`;
|
|
1037
|
+
this.prepareForPrompt("waiting for next message");
|
|
1038
|
+
}
|
|
909
1039
|
prepareForPrompt(message = "waiting for next message") {
|
|
910
1040
|
if (this.#exitRequested) return;
|
|
911
1041
|
this.#state.inputMode = "prompt";
|
|
@@ -932,6 +1062,10 @@ var TuiStore = class {
|
|
|
932
1062
|
agent: context.agent,
|
|
933
1063
|
cwd: context.cwd
|
|
934
1064
|
};
|
|
1065
|
+
this.#state.configPath = context.configPath;
|
|
1066
|
+
this.#state.configCreated = context.configCreated;
|
|
1067
|
+
this.#state.configDefaultProvider = context.configDefaultProvider;
|
|
1068
|
+
this.#state.configMessage = context.configMessage;
|
|
935
1069
|
if (context.contextEfficiency) {
|
|
936
1070
|
this.#state.contextEfficiency = {
|
|
937
1071
|
...this.#state.contextEfficiency ?? { selectedTools: [] },
|
|
@@ -963,7 +1097,58 @@ var TuiStore = class {
|
|
|
963
1097
|
currentPermissionPreset() {
|
|
964
1098
|
return this.#state.permissionPreset;
|
|
965
1099
|
}
|
|
1100
|
+
requestSessionSelection(options2, behavior = {}) {
|
|
1101
|
+
if (this.#exitRequested) return Promise.resolve({ kind: "exit" });
|
|
1102
|
+
const normalized = options2.length > 0 ? options2.map((option) => ({ ...option })) : [{ kind: "new", title: "New session", status: "ready", currentWorkspace: true }];
|
|
1103
|
+
return new Promise((resolve) => {
|
|
1104
|
+
this.#sessionResolve = resolve;
|
|
1105
|
+
this.#state.inputMode = "session";
|
|
1106
|
+
this.#state.sessionOptions = normalized;
|
|
1107
|
+
this.#state.sessionCursor = 0;
|
|
1108
|
+
this.#state.sessionCancelBehavior = behavior.cancel ?? "exit";
|
|
1109
|
+
this.#state.promptInput = "";
|
|
1110
|
+
this.#state.promptError = void 0;
|
|
1111
|
+
this.#state.streamingText = "";
|
|
1112
|
+
this.#state.streamingFinalized = true;
|
|
1113
|
+
this.#state.done = false;
|
|
1114
|
+
this.#state.activity = "select session";
|
|
1115
|
+
this.#notify();
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
cancelSessionSelection() {
|
|
1119
|
+
if (this.#state.inputMode !== "session") return false;
|
|
1120
|
+
if (this.#state.sessionCancelBehavior === "prompt") {
|
|
1121
|
+
this.#resolveSession({ kind: "cancel" });
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
return true;
|
|
1125
|
+
}
|
|
1126
|
+
moveSessionCursor(delta) {
|
|
1127
|
+
if (this.#state.inputMode !== "session" || this.#state.sessionOptions.length === 0) return;
|
|
1128
|
+
const count = this.#state.sessionOptions.length;
|
|
1129
|
+
this.#state.sessionCursor = (this.#state.sessionCursor + delta + count) % count;
|
|
1130
|
+
this.#notify();
|
|
1131
|
+
}
|
|
1132
|
+
submitSessionSelection() {
|
|
1133
|
+
if (this.#state.inputMode !== "session") return;
|
|
1134
|
+
const option = this.#state.sessionOptions[this.#state.sessionCursor];
|
|
1135
|
+
if (!option) return;
|
|
1136
|
+
if (option.kind === "new") {
|
|
1137
|
+
this.#resolveSession({ kind: "new" });
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (option.id) this.#resolveSession({ kind: "session", id: option.id });
|
|
1141
|
+
}
|
|
1142
|
+
submitNewSessionSelection() {
|
|
1143
|
+
if (this.#state.inputMode !== "session") return;
|
|
1144
|
+
this.#resolveSession({ kind: "new" });
|
|
1145
|
+
}
|
|
1146
|
+
submitSessionSelectionShortcut() {
|
|
1147
|
+
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
1148
|
+
this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
|
|
1149
|
+
}
|
|
966
1150
|
requestPrompt(initialPrompt) {
|
|
1151
|
+
if (this.#exitRequested) return Promise.resolve("");
|
|
967
1152
|
const prompt = (initialPrompt ?? "").trim();
|
|
968
1153
|
if (prompt) {
|
|
969
1154
|
this.#rememberPrompt(prompt);
|
|
@@ -1004,12 +1189,12 @@ var TuiStore = class {
|
|
|
1004
1189
|
this.#notify();
|
|
1005
1190
|
});
|
|
1006
1191
|
}
|
|
1007
|
-
appendPromptInput(input2) {
|
|
1192
|
+
appendPromptInput(input2, options2 = {}) {
|
|
1008
1193
|
if (this.#state.inputMode !== "prompt" && this.#state.inputMode !== "running") return;
|
|
1009
1194
|
if (this.#state.inputMode === "prompt") this.#detachPromptHistory();
|
|
1010
1195
|
this.#state.promptInput += input2;
|
|
1011
1196
|
this.#state.promptError = void 0;
|
|
1012
|
-
this.#notify();
|
|
1197
|
+
if (options2.notify !== false) this.#notify();
|
|
1013
1198
|
}
|
|
1014
1199
|
navigatePromptHistory(delta) {
|
|
1015
1200
|
if (this.#state.inputMode !== "prompt" || this.#promptHistory.length === 0) return;
|
|
@@ -1059,6 +1244,143 @@ var TuiStore = class {
|
|
|
1059
1244
|
this.#state.activity = `provider ${option.name} selected`;
|
|
1060
1245
|
this.#notify();
|
|
1061
1246
|
}
|
|
1247
|
+
openConfigManager(message) {
|
|
1248
|
+
const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
|
|
1249
|
+
if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
|
|
1250
|
+
this.#state.inputMode = "config";
|
|
1251
|
+
const current = this.#state.selection?.providerName ?? this.#state.status.provider;
|
|
1252
|
+
this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
|
|
1253
|
+
this.#state.configMessage = message ?? this.#state.configMessage;
|
|
1254
|
+
this.#state.settingsError = void 0;
|
|
1255
|
+
this.#state.activity = "configure providers and models";
|
|
1256
|
+
this.#notify();
|
|
1257
|
+
}
|
|
1258
|
+
openConfigProviderInput(field) {
|
|
1259
|
+
if (this.#state.inputMode !== "config") return;
|
|
1260
|
+
const provider = this.#selectedConfigProvider();
|
|
1261
|
+
if (!provider) return;
|
|
1262
|
+
this.#state.inputMode = "configProviderInput";
|
|
1263
|
+
this.#state.configProviderInputField = field;
|
|
1264
|
+
this.#state.configProviderInput = configProviderFieldValue(provider, field);
|
|
1265
|
+
this.#state.settingsError = void 0;
|
|
1266
|
+
this.#state.activity = `edit ${field} for ${provider.name}`;
|
|
1267
|
+
this.#notify();
|
|
1268
|
+
}
|
|
1269
|
+
appendConfigProviderInput(input2) {
|
|
1270
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1271
|
+
this.#state.configProviderInput += input2;
|
|
1272
|
+
this.#state.settingsError = void 0;
|
|
1273
|
+
this.#notify();
|
|
1274
|
+
}
|
|
1275
|
+
backspaceConfigProviderInput() {
|
|
1276
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1277
|
+
this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
|
|
1278
|
+
this.#state.settingsError = void 0;
|
|
1279
|
+
this.#notify();
|
|
1280
|
+
}
|
|
1281
|
+
submitConfigProviderInput() {
|
|
1282
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1283
|
+
const provider = this.#selectedConfigProvider();
|
|
1284
|
+
const field = this.#state.configProviderInputField;
|
|
1285
|
+
if (!provider || !field) return;
|
|
1286
|
+
this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
|
|
1287
|
+
}
|
|
1288
|
+
moveConfigProviderCursor(delta) {
|
|
1289
|
+
if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
|
|
1290
|
+
const count = this.#state.providerOptions.length;
|
|
1291
|
+
this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
|
|
1292
|
+
this.#notify();
|
|
1293
|
+
}
|
|
1294
|
+
submitConfigDefaultProvider() {
|
|
1295
|
+
if (this.#state.inputMode !== "config") return;
|
|
1296
|
+
const option = this.#selectedConfigProvider();
|
|
1297
|
+
if (!option) return;
|
|
1298
|
+
this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
|
|
1299
|
+
}
|
|
1300
|
+
openConfigModelSelector() {
|
|
1301
|
+
if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
|
|
1302
|
+
const option = this.#selectedConfigProvider();
|
|
1303
|
+
if (!option) return;
|
|
1304
|
+
this.#state.inputMode = "configModel";
|
|
1305
|
+
this.#state.configModelCursor = 0;
|
|
1306
|
+
this.#state.settingsError = void 0;
|
|
1307
|
+
this.#state.activity = `choose default model for ${option.name}`;
|
|
1308
|
+
this.#notify();
|
|
1309
|
+
}
|
|
1310
|
+
moveConfigModelCursor(delta) {
|
|
1311
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
1312
|
+
const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
|
|
1313
|
+
if (profiles.length === 0) return;
|
|
1314
|
+
this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
|
|
1315
|
+
this.#notify();
|
|
1316
|
+
}
|
|
1317
|
+
submitConfigDefaultModel() {
|
|
1318
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
1319
|
+
const provider = this.#selectedConfigProvider();
|
|
1320
|
+
const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
|
|
1321
|
+
if (!provider || !profile?.model) return;
|
|
1322
|
+
this.#submitConfigAction({
|
|
1323
|
+
type: "setDefaultModel",
|
|
1324
|
+
provider: provider.name,
|
|
1325
|
+
profileName: profile.name,
|
|
1326
|
+
name: profile.name,
|
|
1327
|
+
displayName: profile.displayName,
|
|
1328
|
+
model: profile.model
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
openConfigModelInput() {
|
|
1332
|
+
if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
|
|
1333
|
+
if (!this.#selectedConfigProvider()) return;
|
|
1334
|
+
this.#state.inputMode = "configModelInput";
|
|
1335
|
+
this.#state.configModelInput = "";
|
|
1336
|
+
this.#state.settingsError = void 0;
|
|
1337
|
+
this.#state.activity = "add a model profile";
|
|
1338
|
+
this.#notify();
|
|
1339
|
+
}
|
|
1340
|
+
appendConfigModelInput(input2) {
|
|
1341
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1342
|
+
this.#state.configModelInput += input2;
|
|
1343
|
+
this.#state.settingsError = void 0;
|
|
1344
|
+
this.#notify();
|
|
1345
|
+
}
|
|
1346
|
+
backspaceConfigModelInput() {
|
|
1347
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1348
|
+
this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
|
|
1349
|
+
this.#state.settingsError = void 0;
|
|
1350
|
+
this.#notify();
|
|
1351
|
+
}
|
|
1352
|
+
submitConfigModelInput() {
|
|
1353
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1354
|
+
const provider = this.#selectedConfigProvider();
|
|
1355
|
+
const model = this.#state.configModelInput.trim();
|
|
1356
|
+
if (!provider || !model) {
|
|
1357
|
+
this.#state.settingsError = "Model ID is required.";
|
|
1358
|
+
this.#notify();
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
|
|
1362
|
+
}
|
|
1363
|
+
openConfigAddProviderSelector() {
|
|
1364
|
+
if (this.#state.inputMode !== "config") return;
|
|
1365
|
+
this.#state.inputMode = "configAddProvider";
|
|
1366
|
+
this.#state.configAddProviderCursor = 0;
|
|
1367
|
+
this.#state.settingsError = void 0;
|
|
1368
|
+
this.#state.activity = "add provider preset";
|
|
1369
|
+
this.#notify();
|
|
1370
|
+
}
|
|
1371
|
+
moveConfigAddProviderCursor(delta) {
|
|
1372
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
1373
|
+
const count = this.#state.configProviderPresets.length;
|
|
1374
|
+
if (count === 0) return;
|
|
1375
|
+
this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
|
|
1376
|
+
this.#notify();
|
|
1377
|
+
}
|
|
1378
|
+
submitConfigAddProvider() {
|
|
1379
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
1380
|
+
const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
|
|
1381
|
+
if (!preset) return;
|
|
1382
|
+
this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
|
|
1383
|
+
}
|
|
1062
1384
|
openAgentSelector() {
|
|
1063
1385
|
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
1064
1386
|
if (this.#state.agentOptions.length === 0) {
|
|
@@ -1157,26 +1479,29 @@ var TuiStore = class {
|
|
|
1157
1479
|
this.#notify();
|
|
1158
1480
|
}
|
|
1159
1481
|
closeSettings() {
|
|
1160
|
-
if (
|
|
1482
|
+
if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
|
|
1161
1483
|
this.#state.inputMode = "prompt";
|
|
1162
1484
|
this.#state.modelInput = "";
|
|
1485
|
+
this.#state.configModelInput = "";
|
|
1486
|
+
this.#state.configProviderInput = "";
|
|
1487
|
+
this.#state.configProviderInputField = void 0;
|
|
1163
1488
|
this.#state.settingsError = void 0;
|
|
1164
1489
|
this.#state.activity = "waiting for first message";
|
|
1165
1490
|
this.#notify();
|
|
1166
1491
|
}
|
|
1167
|
-
backspacePromptInput() {
|
|
1492
|
+
backspacePromptInput(options2 = {}) {
|
|
1168
1493
|
if (this.#state.inputMode !== "prompt" && this.#state.inputMode !== "running") return;
|
|
1169
1494
|
if (this.#state.inputMode === "prompt") this.#detachPromptHistory();
|
|
1170
1495
|
this.#state.promptInput = Array.from(this.#state.promptInput).slice(0, -1).join("");
|
|
1171
1496
|
this.#state.promptError = void 0;
|
|
1172
|
-
this.#notify();
|
|
1497
|
+
if (options2.notify !== false) this.#notify();
|
|
1173
1498
|
}
|
|
1174
|
-
clearPromptInput() {
|
|
1499
|
+
clearPromptInput(options2 = {}) {
|
|
1175
1500
|
if (this.#state.inputMode !== "prompt" && this.#state.inputMode !== "running") return;
|
|
1176
1501
|
if (this.#state.inputMode === "prompt") this.#detachPromptHistory();
|
|
1177
1502
|
this.#state.promptInput = "";
|
|
1178
1503
|
this.#state.promptError = void 0;
|
|
1179
|
-
this.#notify();
|
|
1504
|
+
if (options2.notify !== false) this.#notify();
|
|
1180
1505
|
}
|
|
1181
1506
|
usePendingClaudeCodePlan() {
|
|
1182
1507
|
if (this.#state.inputMode !== "prompt") return false;
|
|
@@ -1288,9 +1613,12 @@ var TuiStore = class {
|
|
|
1288
1613
|
this.#state.streamingFinalized = false;
|
|
1289
1614
|
this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
|
|
1290
1615
|
}
|
|
1291
|
-
if (event.type === "model.text.delta"
|
|
1616
|
+
if (event.type === "model.text.delta") {
|
|
1617
|
+
if (isNestedInvocationEvent(event)) return;
|
|
1292
1618
|
this.#state.streamingText += event.text;
|
|
1293
1619
|
this.#state.streamingFinalized = false;
|
|
1620
|
+
this.#notify({ deferMs: 50 });
|
|
1621
|
+
return;
|
|
1294
1622
|
}
|
|
1295
1623
|
if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
|
|
1296
1624
|
this.#state.streamingText = event.text;
|
|
@@ -2004,9 +2332,38 @@ var TuiStore = class {
|
|
|
2004
2332
|
resolve(prompt);
|
|
2005
2333
|
this.#notify();
|
|
2006
2334
|
}
|
|
2335
|
+
#selectedConfigProvider() {
|
|
2336
|
+
return this.#state.providerOptions[this.#state.providerCursor];
|
|
2337
|
+
}
|
|
2338
|
+
#submitConfigAction(action) {
|
|
2339
|
+
this.#state.settingsError = void 0;
|
|
2340
|
+
this.#state.configModelInput = "";
|
|
2341
|
+
this.#state.configProviderInput = "";
|
|
2342
|
+
this.#state.configProviderInputField = void 0;
|
|
2343
|
+
this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
|
|
2344
|
+
}
|
|
2345
|
+
#resolveSession(selection) {
|
|
2346
|
+
const resolve = this.#sessionResolve;
|
|
2347
|
+
if (!resolve) return;
|
|
2348
|
+
this.#sessionResolve = void 0;
|
|
2349
|
+
this.#state.sessionCancelBehavior = "exit";
|
|
2350
|
+
if (selection.kind === "exit") {
|
|
2351
|
+
this.#state.inputMode = "done";
|
|
2352
|
+
this.#state.done = true;
|
|
2353
|
+
this.#state.activity = "exiting";
|
|
2354
|
+
} else if (selection.kind === "cancel") {
|
|
2355
|
+
this.#state.inputMode = "prompt";
|
|
2356
|
+
this.#state.activity = "waiting for next message";
|
|
2357
|
+
} else {
|
|
2358
|
+
this.#state.inputMode = "starting";
|
|
2359
|
+
this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
|
|
2360
|
+
}
|
|
2361
|
+
this.#notify();
|
|
2362
|
+
resolve(selection);
|
|
2363
|
+
}
|
|
2007
2364
|
#rememberPrompt(prompt) {
|
|
2008
2365
|
const value = prompt.trim();
|
|
2009
|
-
if (!value || isExitCommand(value) || isStopCommand(value) || isCompactCommand(value) || isRetryCommand(value)) return;
|
|
2366
|
+
if (!value || isExitCommand(value) || isStopCommand(value) || isCompactCommand(value) || isRetryCommand(value) || isSessionCommand(value)) return;
|
|
2010
2367
|
if (this.#promptHistory.at(-1) === value) {
|
|
2011
2368
|
this.#state.canRetryLastPrompt = true;
|
|
2012
2369
|
return;
|
|
@@ -2018,6 +2375,20 @@ var TuiStore = class {
|
|
|
2018
2375
|
#lastRetryPrompt() {
|
|
2019
2376
|
return this.#promptHistory.at(-1);
|
|
2020
2377
|
}
|
|
2378
|
+
#resetTransientConversationState() {
|
|
2379
|
+
this.#toolRuns.clear();
|
|
2380
|
+
this.#goalWorkBlocks.clear();
|
|
2381
|
+
this.#coworkBlocks.clear();
|
|
2382
|
+
this.#activeGoalWorkBlockId = void 0;
|
|
2383
|
+
this.#currentGoalNarrationId = void 0;
|
|
2384
|
+
this.#timelineProgressIds.clear();
|
|
2385
|
+
this.#pendingPermissionCallId = void 0;
|
|
2386
|
+
if (this.#pendingPermissionTimeout) clearTimeout(this.#pendingPermissionTimeout);
|
|
2387
|
+
this.#pendingPermissionTimeout = void 0;
|
|
2388
|
+
this.#activeClaudeCodePlan = void 0;
|
|
2389
|
+
this.#promptHistoryCursor = void 0;
|
|
2390
|
+
this.#promptDraft = "";
|
|
2391
|
+
}
|
|
2021
2392
|
#detachPromptHistory() {
|
|
2022
2393
|
this.#promptHistoryCursor = void 0;
|
|
2023
2394
|
this.#promptDraft = "";
|
|
@@ -2094,7 +2465,23 @@ var TuiStore = class {
|
|
|
2094
2465
|
}
|
|
2095
2466
|
};
|
|
2096
2467
|
}
|
|
2097
|
-
#notify() {
|
|
2468
|
+
#notify(options2 = {}) {
|
|
2469
|
+
const deferMs = options2.deferMs ?? 0;
|
|
2470
|
+
if (deferMs > 0) {
|
|
2471
|
+
if (this.#notifyTimer) return;
|
|
2472
|
+
this.#notifyTimer = setTimeout(() => {
|
|
2473
|
+
this.#notifyTimer = void 0;
|
|
2474
|
+
this.#emitNotify();
|
|
2475
|
+
}, deferMs);
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
if (this.#notifyTimer) {
|
|
2479
|
+
clearTimeout(this.#notifyTimer);
|
|
2480
|
+
this.#notifyTimer = void 0;
|
|
2481
|
+
}
|
|
2482
|
+
this.#emitNotify();
|
|
2483
|
+
}
|
|
2484
|
+
#emitNotify() {
|
|
2098
2485
|
for (const listener of this.#listeners) listener();
|
|
2099
2486
|
}
|
|
2100
2487
|
};
|
|
@@ -2257,6 +2644,25 @@ function cloneContextEfficiency(context) {
|
|
|
2257
2644
|
lastCompaction: context.lastCompaction ? { ...context.lastCompaction } : void 0
|
|
2258
2645
|
};
|
|
2259
2646
|
}
|
|
2647
|
+
function configProviderFieldValue(provider, field) {
|
|
2648
|
+
if (field === "baseURL") return provider.baseURL ?? "";
|
|
2649
|
+
if (field === "apiKeyEnv") return provider.apiKeyEnv ?? "";
|
|
2650
|
+
return provider.auth?.header ?? "";
|
|
2651
|
+
}
|
|
2652
|
+
function defaultConfigProviderPresets() {
|
|
2653
|
+
return [
|
|
2654
|
+
{ label: "OpenAI", preset: "openai", name: "openai", apiKeyEnv: "OPENAI_API_KEY", detail: "OpenAI API" },
|
|
2655
|
+
{ label: "Anthropic", preset: "anthropic", name: "anthropic", apiKeyEnv: "ANTHROPIC_API_KEY", detail: "Anthropic API" },
|
|
2656
|
+
{ label: "Gemini", preset: "gemini", name: "gemini", apiKeyEnv: "GEMINI_API_KEY", detail: "Google Gemini OpenAI-compatible API" },
|
|
2657
|
+
{ label: "Groq", preset: "groq", name: "groq", apiKeyEnv: "GROQ_API_KEY", detail: "Groq Cloud" },
|
|
2658
|
+
{ label: "Codex", preset: "codex", name: "codex", detail: "ChatGPT Codex OAuth" },
|
|
2659
|
+
{ label: "Claude Code", preset: "claudecode", name: "claudecode", detail: "Local Claude Code runtime" },
|
|
2660
|
+
{ label: "Ollama local", preset: "ollama-local", name: "ollama-local", detail: "http://localhost:11434" },
|
|
2661
|
+
{ label: "LM Studio", preset: "lmstudio", name: "lmstudio", detail: "http://localhost:1234" },
|
|
2662
|
+
{ label: "llama.cpp", preset: "llamacpp", name: "llamacpp", detail: "http://localhost:8080" },
|
|
2663
|
+
{ label: "vLLM", preset: "vllm", name: "vllm", detail: "http://localhost:8000" }
|
|
2664
|
+
];
|
|
2665
|
+
}
|
|
2260
2666
|
function cloneToolRunDetails(details) {
|
|
2261
2667
|
if (!details) return void 0;
|
|
2262
2668
|
return {
|
|
@@ -2309,6 +2715,36 @@ function cloneGoalToolRun(tool) {
|
|
|
2309
2715
|
details: cloneToolRunDetails(tool.details)
|
|
2310
2716
|
};
|
|
2311
2717
|
}
|
|
2718
|
+
function safeConversationSnapshot(snapshot) {
|
|
2719
|
+
if (!snapshot || typeof snapshot !== "object") return {};
|
|
2720
|
+
return {
|
|
2721
|
+
status: snapshot.status ? { ...snapshot.status } : void 0,
|
|
2722
|
+
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) })) : [],
|
|
2723
|
+
activity: typeof snapshot.activity === "string" ? snapshot.activity : "restored conversation",
|
|
2724
|
+
warnings: Array.isArray(snapshot.warnings) ? [...snapshot.warnings] : [],
|
|
2725
|
+
finalAnswer: snapshot.finalAnswer,
|
|
2726
|
+
progressNotes: Array.isArray(snapshot.progressNotes) ? snapshot.progressNotes.map((note) => ({ ...note })) : [],
|
|
2727
|
+
turnDiff: snapshot.turnDiff ? cloneDiffSummary(snapshot.turnDiff) : void 0,
|
|
2728
|
+
diffExpanded: snapshot.diffExpanded === true,
|
|
2729
|
+
workPlan: snapshot.workPlan ? {
|
|
2730
|
+
...snapshot.workPlan,
|
|
2731
|
+
steps: snapshot.workPlan.steps.map((step) => ({ ...step }))
|
|
2732
|
+
} : void 0,
|
|
2733
|
+
workPlanExpanded: snapshot.workPlanExpanded === true,
|
|
2734
|
+
goal: snapshot.goal ? { ...snapshot.goal } : void 0,
|
|
2735
|
+
contextEfficiency: snapshot.contextEfficiency ? cloneContextEfficiency(snapshot.contextEfficiency) : void 0,
|
|
2736
|
+
canRetryLastPrompt: snapshot.canRetryLastPrompt === true,
|
|
2737
|
+
pendingClaudeCodePlan: snapshot.pendingClaudeCodePlan ? { ...snapshot.pendingClaudeCodePlan } : void 0
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
function nextBlockId(blocks) {
|
|
2741
|
+
let max = 0;
|
|
2742
|
+
for (const block of blocks ?? []) {
|
|
2743
|
+
const match = /^block_(\d+)$/.exec(block.id);
|
|
2744
|
+
if (match) max = Math.max(max, Number(match[1]));
|
|
2745
|
+
}
|
|
2746
|
+
return max;
|
|
2747
|
+
}
|
|
2312
2748
|
function formatDuration2(durationMs) {
|
|
2313
2749
|
if (durationMs < 1e3) return `${durationMs}ms`;
|
|
2314
2750
|
return `${Math.round(durationMs / 1e3)}s`;
|
|
@@ -2327,7 +2763,8 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
|
|
|
2327
2763
|
}
|
|
2328
2764
|
|
|
2329
2765
|
// src/ui/tui/controller.ts
|
|
2330
|
-
import
|
|
2766
|
+
import path34 from "node:path";
|
|
2767
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2331
2768
|
|
|
2332
2769
|
// src/agents/types.ts
|
|
2333
2770
|
function normalizeAgent(input2) {
|
|
@@ -25421,8 +25858,8 @@ function permissionLabel(req) {
|
|
|
25421
25858
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
25422
25859
|
return `${req.tool}: ${inputObject.path}`;
|
|
25423
25860
|
}
|
|
25424
|
-
const
|
|
25425
|
-
if (
|
|
25861
|
+
const path37 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
|
|
25862
|
+
if (path37) return `${tool}: ${path37}`;
|
|
25426
25863
|
return tool;
|
|
25427
25864
|
}
|
|
25428
25865
|
|
|
@@ -26766,10 +27203,380 @@ function claudeCodeRuntimePolicyHash(config) {
|
|
|
26766
27203
|
}
|
|
26767
27204
|
var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision", "allowedTools", "disallowedTools", "tools", "allowSubagents"];
|
|
26768
27205
|
|
|
26769
|
-
// src/
|
|
26770
|
-
import { mkdir as mkdir6,
|
|
27206
|
+
// src/config-scaffold.ts
|
|
27207
|
+
import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
26771
27208
|
import os8 from "node:os";
|
|
26772
27209
|
import path14 from "node:path";
|
|
27210
|
+
function defaultUserConfigPath() {
|
|
27211
|
+
return path14.join(os8.homedir(), ".demian", "config.json");
|
|
27212
|
+
}
|
|
27213
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
27214
|
+
return {
|
|
27215
|
+
version: 2,
|
|
27216
|
+
defaultProvider,
|
|
27217
|
+
providers: {
|
|
27218
|
+
openai: {
|
|
27219
|
+
type: "openai-compatible",
|
|
27220
|
+
baseURL: "https://api.openai.com/v1",
|
|
27221
|
+
apiKey: "",
|
|
27222
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
27223
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
27224
|
+
},
|
|
27225
|
+
anthropic: {
|
|
27226
|
+
type: "anthropic",
|
|
27227
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
27228
|
+
apiKey: "",
|
|
27229
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
27230
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
27231
|
+
},
|
|
27232
|
+
gemini: {
|
|
27233
|
+
type: "openai-compatible",
|
|
27234
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
27235
|
+
apiKey: "",
|
|
27236
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
27237
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
27238
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
27239
|
+
},
|
|
27240
|
+
groq: {
|
|
27241
|
+
type: "openai-compatible",
|
|
27242
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
27243
|
+
apiKey: "",
|
|
27244
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
27245
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
27246
|
+
},
|
|
27247
|
+
azure: {
|
|
27248
|
+
type: "openai-compatible",
|
|
27249
|
+
auth: { type: "api-key", header: "api-key" },
|
|
27250
|
+
modelProfiles: [
|
|
27251
|
+
{
|
|
27252
|
+
name: "azure-example",
|
|
27253
|
+
displayName: "Azure example",
|
|
27254
|
+
model: "azure-deployment-name",
|
|
27255
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
27256
|
+
apiKey: "",
|
|
27257
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
27258
|
+
}
|
|
27259
|
+
]
|
|
27260
|
+
},
|
|
27261
|
+
lmstudio: {
|
|
27262
|
+
type: "openai-compatible",
|
|
27263
|
+
baseURL: "http://localhost:1234/v1",
|
|
27264
|
+
apiKey: "lm-studio",
|
|
27265
|
+
modelProfiles: [],
|
|
27266
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
27267
|
+
},
|
|
27268
|
+
"ollama-local": {
|
|
27269
|
+
type: "openai-compatible",
|
|
27270
|
+
baseURL: "http://localhost:11434/v1",
|
|
27271
|
+
apiKey: "ollama",
|
|
27272
|
+
modelProfiles: [],
|
|
27273
|
+
quirks: { omitTemperature: true },
|
|
27274
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
27275
|
+
},
|
|
27276
|
+
"ollama-cloud": {
|
|
27277
|
+
type: "ollama",
|
|
27278
|
+
baseURL: "https://ollama.com/api",
|
|
27279
|
+
apiKey: "",
|
|
27280
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
27281
|
+
modelProfiles: [],
|
|
27282
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
27283
|
+
},
|
|
27284
|
+
llamacpp: {
|
|
27285
|
+
type: "openai-compatible",
|
|
27286
|
+
baseURL: "http://localhost:8080/v1",
|
|
27287
|
+
apiKey: "llama.cpp",
|
|
27288
|
+
modelProfiles: [],
|
|
27289
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
27290
|
+
},
|
|
27291
|
+
vllm: {
|
|
27292
|
+
type: "openai-compatible",
|
|
27293
|
+
baseURL: "http://localhost:8000/v1",
|
|
27294
|
+
apiKey: "vllm",
|
|
27295
|
+
modelProfiles: [],
|
|
27296
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
27297
|
+
},
|
|
27298
|
+
codex: {
|
|
27299
|
+
type: "codex",
|
|
27300
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
27301
|
+
authStore: "auto",
|
|
27302
|
+
allowApiKeyFallback: false,
|
|
27303
|
+
promptCacheKey: "root-session",
|
|
27304
|
+
catalog: { type: "codex-oauth-models" }
|
|
27305
|
+
},
|
|
27306
|
+
claudecode: {
|
|
27307
|
+
type: "claudecode",
|
|
27308
|
+
runtime: "agent-sdk",
|
|
27309
|
+
cliPath: "~/.local/bin/claude",
|
|
27310
|
+
cwdMode: "session",
|
|
27311
|
+
historyPolicy: "passthrough-resume",
|
|
27312
|
+
onInvalidResume: "fresh",
|
|
27313
|
+
attachmentFallback: "block",
|
|
27314
|
+
allowSubagents: false,
|
|
27315
|
+
sanitizeApiKeyEnv: true,
|
|
27316
|
+
authPreflight: true,
|
|
27317
|
+
useBareMode: false,
|
|
27318
|
+
usageLedgerScope: "process",
|
|
27319
|
+
sessionLock: true,
|
|
27320
|
+
abortPolicy: "record-only",
|
|
27321
|
+
catalog: { type: "claudecode-supported-models" }
|
|
27322
|
+
}
|
|
27323
|
+
}
|
|
27324
|
+
};
|
|
27325
|
+
}
|
|
27326
|
+
async function createUserConfig(options2 = {}) {
|
|
27327
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27328
|
+
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
27329
|
+
`;
|
|
27330
|
+
if (options2.print) return { path: filePath, created: false, content };
|
|
27331
|
+
const existed = await exists(filePath);
|
|
27332
|
+
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile7(filePath, "utf8"), existed: true };
|
|
27333
|
+
await writeJsonAtomic(filePath, content);
|
|
27334
|
+
return { path: filePath, created: true, content, existed };
|
|
27335
|
+
}
|
|
27336
|
+
async function addProvider(options2) {
|
|
27337
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27338
|
+
const config = await readConfigObject(filePath);
|
|
27339
|
+
const providers = objectValue(config.providers);
|
|
27340
|
+
const name = options2.name;
|
|
27341
|
+
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
27342
|
+
providers[name] = providerPreset(options2);
|
|
27343
|
+
config.providers = providers;
|
|
27344
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27345
|
+
`;
|
|
27346
|
+
await writeJsonAtomic(filePath, content);
|
|
27347
|
+
return { path: filePath, created: true, content };
|
|
27348
|
+
}
|
|
27349
|
+
async function addModelProfile(options2) {
|
|
27350
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27351
|
+
const config = await readConfigObject(filePath);
|
|
27352
|
+
const providers = objectValue(config.providers);
|
|
27353
|
+
const provider = objectValue(providers[options2.provider]);
|
|
27354
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
27355
|
+
const displayName = options2.displayName ?? options2.name;
|
|
27356
|
+
const previousName = normalizeOptionalName(options2.previousName);
|
|
27357
|
+
const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
|
|
27358
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
27359
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
27360
|
+
const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
|
|
27361
|
+
if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Pick a different profile name.`);
|
|
27362
|
+
if (existingByName >= 0 && existingByPreviousName < 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
27363
|
+
if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options2.force) {
|
|
27364
|
+
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options2.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
27365
|
+
}
|
|
27366
|
+
const next = {
|
|
27367
|
+
name: options2.name,
|
|
27368
|
+
displayName,
|
|
27369
|
+
model: options2.model,
|
|
27370
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27371
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27372
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27373
|
+
};
|
|
27374
|
+
if (updateIndex >= 0) profiles[updateIndex] = next;
|
|
27375
|
+
else profiles.push(next);
|
|
27376
|
+
provider.modelProfiles = profiles;
|
|
27377
|
+
providers[options2.provider] = provider;
|
|
27378
|
+
config.providers = providers;
|
|
27379
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27380
|
+
`;
|
|
27381
|
+
await writeJsonAtomic(filePath, content);
|
|
27382
|
+
return { path: filePath, created: true, content };
|
|
27383
|
+
}
|
|
27384
|
+
async function updateConfigDefaults(options2) {
|
|
27385
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27386
|
+
const config = await readConfigObject(filePath);
|
|
27387
|
+
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
27388
|
+
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
27389
|
+
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
27390
|
+
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
27391
|
+
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
27392
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27393
|
+
`;
|
|
27394
|
+
await writeJsonAtomic(filePath, content);
|
|
27395
|
+
return { path: filePath, created: true, content };
|
|
27396
|
+
}
|
|
27397
|
+
async function setDefaultModelProfile(options2) {
|
|
27398
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27399
|
+
const config = await readConfigObject(filePath);
|
|
27400
|
+
const providers = objectValue(config.providers);
|
|
27401
|
+
const provider = providers[options2.provider];
|
|
27402
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
|
|
27403
|
+
const providerConfig = { ...provider };
|
|
27404
|
+
const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
|
|
27405
|
+
const requestedName = normalizeOptionalName(options2.profileName ?? options2.name);
|
|
27406
|
+
const requestedModel = normalizeOptionalName(options2.model);
|
|
27407
|
+
const requestedDisplayName = normalizeOptionalName(options2.displayName) ?? requestedModel ?? requestedName;
|
|
27408
|
+
let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
|
|
27409
|
+
if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
|
|
27410
|
+
if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
|
|
27411
|
+
const profile = index >= 0 ? {
|
|
27412
|
+
...profiles[index],
|
|
27413
|
+
...requestedName ? { name: requestedName } : {},
|
|
27414
|
+
...requestedDisplayName ? { displayName: requestedDisplayName } : {},
|
|
27415
|
+
...requestedModel ? { model: requestedModel } : {},
|
|
27416
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27417
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27418
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27419
|
+
} : newModelProfile({
|
|
27420
|
+
name: requestedName,
|
|
27421
|
+
displayName: requestedDisplayName,
|
|
27422
|
+
model: requestedModel ?? requestedName,
|
|
27423
|
+
baseURL: options2.baseURL,
|
|
27424
|
+
apiKey: options2.apiKey,
|
|
27425
|
+
apiKeyEnv: options2.apiKeyEnv
|
|
27426
|
+
});
|
|
27427
|
+
if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
|
|
27428
|
+
if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
|
|
27429
|
+
if (index >= 0) profiles.splice(index, 1);
|
|
27430
|
+
profiles.unshift(profile);
|
|
27431
|
+
providerConfig.modelProfiles = profiles;
|
|
27432
|
+
providers[options2.provider] = providerConfig;
|
|
27433
|
+
config.providers = providers;
|
|
27434
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27435
|
+
`;
|
|
27436
|
+
await writeJsonAtomic(filePath, content);
|
|
27437
|
+
return { path: filePath, created: true, content };
|
|
27438
|
+
}
|
|
27439
|
+
async function updateProviderSettings(options2) {
|
|
27440
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27441
|
+
const config = await readConfigObject(filePath);
|
|
27442
|
+
const providers = objectValue(config.providers);
|
|
27443
|
+
const provider = providers[options2.provider];
|
|
27444
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
|
|
27445
|
+
const providerConfig = { ...provider };
|
|
27446
|
+
if (options2.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options2.baseURL);
|
|
27447
|
+
if (options2.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options2.apiKey);
|
|
27448
|
+
if (options2.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options2.apiKeyEnv);
|
|
27449
|
+
if (options2.authHeader !== void 0) {
|
|
27450
|
+
const header = options2.authHeader.trim();
|
|
27451
|
+
if (header) providerConfig.auth = { type: "api-key", header };
|
|
27452
|
+
else delete providerConfig.auth;
|
|
27453
|
+
}
|
|
27454
|
+
providers[options2.provider] = providerConfig;
|
|
27455
|
+
config.providers = providers;
|
|
27456
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27457
|
+
`;
|
|
27458
|
+
await writeJsonAtomic(filePath, content);
|
|
27459
|
+
return { path: filePath, created: true, content };
|
|
27460
|
+
}
|
|
27461
|
+
async function readConfigObject(filePath) {
|
|
27462
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
27463
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
27464
|
+
raw.version ??= 2;
|
|
27465
|
+
raw.providers = objectValue(raw.providers);
|
|
27466
|
+
return raw;
|
|
27467
|
+
}
|
|
27468
|
+
function providerPreset(options2) {
|
|
27469
|
+
const preset = options2.preset ?? options2.name;
|
|
27470
|
+
const auth = apiKeyAuthFields(options2);
|
|
27471
|
+
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
27472
|
+
if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options2, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
27473
|
+
if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options2, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
27474
|
+
if (preset === "gemini") {
|
|
27475
|
+
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
27476
|
+
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options2, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
27477
|
+
}
|
|
27478
|
+
if (preset === "groq") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options2, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options2.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
27479
|
+
if (preset === "azure") {
|
|
27480
|
+
return {
|
|
27481
|
+
type: "openai-compatible",
|
|
27482
|
+
auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
|
|
27483
|
+
...auth,
|
|
27484
|
+
modelProfiles: [
|
|
27485
|
+
{
|
|
27486
|
+
name: "azure-example",
|
|
27487
|
+
displayName: "Azure example",
|
|
27488
|
+
model: "azure-deployment-name",
|
|
27489
|
+
baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
27490
|
+
...options2.apiKey ? {} : { apiKey: "" },
|
|
27491
|
+
apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
27492
|
+
}
|
|
27493
|
+
]
|
|
27494
|
+
};
|
|
27495
|
+
}
|
|
27496
|
+
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:1234/v1", apiKey: options2.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
27497
|
+
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:11434/v1", apiKey: options2.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
27498
|
+
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options2.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options2, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options2.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
27499
|
+
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8080/v1", apiKey: options2.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
27500
|
+
if (preset === "vllm") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8000/v1", apiKey: options2.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
27501
|
+
if (preset === "codex") return { type: "codex", baseURL: options2.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
|
|
27502
|
+
if (preset === "claudecode") return { type: "claudecode", runtime: "agent-sdk", cliPath: "~/.local/bin/claude", cwdMode: "session", historyPolicy: "passthrough-resume", onInvalidResume: "fresh", attachmentFallback: "block", allowSubagents: false, sanitizeApiKeyEnv: true, authPreflight: true, useBareMode: false, usageLedgerScope: "process", sessionLock: true, abortPolicy: "record-only", catalog: { type: "claudecode-supported-models" } };
|
|
27503
|
+
if ((options2.type ?? preset) === "openai-compatible") {
|
|
27504
|
+
const baseURL = options2.baseURL;
|
|
27505
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
27506
|
+
return {
|
|
27507
|
+
type: "openai-compatible",
|
|
27508
|
+
baseURL,
|
|
27509
|
+
...auth,
|
|
27510
|
+
...openAIAuth,
|
|
27511
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
27512
|
+
};
|
|
27513
|
+
}
|
|
27514
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
27515
|
+
}
|
|
27516
|
+
function apiKeyAuthFields(options2, defaultApiKeyEnv) {
|
|
27517
|
+
const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
|
|
27518
|
+
return {
|
|
27519
|
+
...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27520
|
+
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
27521
|
+
};
|
|
27522
|
+
}
|
|
27523
|
+
async function writeJsonAtomic(filePath, content) {
|
|
27524
|
+
await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
27525
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
27526
|
+
await writeFile5(temp, content, { mode: 384 });
|
|
27527
|
+
await rename4(temp, filePath);
|
|
27528
|
+
await chmod3(filePath, 384).catch(() => void 0);
|
|
27529
|
+
}
|
|
27530
|
+
function detectDefaultProvider() {
|
|
27531
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
27532
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
27533
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
27534
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
27535
|
+
return "openai";
|
|
27536
|
+
}
|
|
27537
|
+
function objectValue(value) {
|
|
27538
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
27539
|
+
}
|
|
27540
|
+
function normalizeOptionalName(value) {
|
|
27541
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
27542
|
+
}
|
|
27543
|
+
function newModelProfile(options2) {
|
|
27544
|
+
const model = options2.model?.trim();
|
|
27545
|
+
if (!model) throw new Error("Model ID is required.");
|
|
27546
|
+
const name = options2.name?.trim() || modelProfileNameFromModel(model);
|
|
27547
|
+
return {
|
|
27548
|
+
name,
|
|
27549
|
+
displayName: options2.displayName?.trim() || name,
|
|
27550
|
+
model,
|
|
27551
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27552
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27553
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27554
|
+
};
|
|
27555
|
+
}
|
|
27556
|
+
function modelProfileNameFromModel(model) {
|
|
27557
|
+
return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
|
|
27558
|
+
}
|
|
27559
|
+
function setOrDelete(target, key, value) {
|
|
27560
|
+
const trimmed = value.trim();
|
|
27561
|
+
if (trimmed) target[key] = trimmed;
|
|
27562
|
+
else delete target[key];
|
|
27563
|
+
}
|
|
27564
|
+
async function exists(filePath) {
|
|
27565
|
+
try {
|
|
27566
|
+
await stat3(filePath);
|
|
27567
|
+
return true;
|
|
27568
|
+
} catch {
|
|
27569
|
+
return false;
|
|
27570
|
+
}
|
|
27571
|
+
}
|
|
27572
|
+
function expandHome2(value) {
|
|
27573
|
+
return resolveExpandedPath(value);
|
|
27574
|
+
}
|
|
27575
|
+
|
|
27576
|
+
// src/transcript.ts
|
|
27577
|
+
import { mkdir as mkdir7, appendFile, writeFile as writeFile6 } from "node:fs/promises";
|
|
27578
|
+
import os9 from "node:os";
|
|
27579
|
+
import path15 from "node:path";
|
|
26773
27580
|
var TranscriptWriter = class {
|
|
26774
27581
|
filePath;
|
|
26775
27582
|
#enabled;
|
|
@@ -26777,9 +27584,9 @@ var TranscriptWriter = class {
|
|
|
26777
27584
|
#queue = Promise.resolve();
|
|
26778
27585
|
constructor(options2) {
|
|
26779
27586
|
this.#enabled = options2.enabled !== false;
|
|
26780
|
-
const dir =
|
|
26781
|
-
this.filePath =
|
|
26782
|
-
this.#ready = this.#enabled ?
|
|
27587
|
+
const dir = path15.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
|
|
27588
|
+
this.filePath = path15.join(dir, "session.jsonl");
|
|
27589
|
+
this.#ready = this.#enabled ? mkdir7(dir, { recursive: true }).then(() => writeFile6(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
26783
27590
|
}
|
|
26784
27591
|
write(event) {
|
|
26785
27592
|
if (!this.#enabled) return Promise.resolve();
|
|
@@ -26795,14 +27602,14 @@ var TranscriptWriter = class {
|
|
|
26795
27602
|
}
|
|
26796
27603
|
};
|
|
26797
27604
|
function defaultDemianStorageDir() {
|
|
26798
|
-
return
|
|
27605
|
+
return path15.join(os9.homedir(), ".demian");
|
|
26799
27606
|
}
|
|
26800
27607
|
|
|
26801
27608
|
// src/external-runtime/snapshot-diff.ts
|
|
26802
27609
|
import { createHash as createHash2 } from "node:crypto";
|
|
26803
27610
|
import { createReadStream } from "node:fs";
|
|
26804
|
-
import { readdir, readFile as
|
|
26805
|
-
import
|
|
27611
|
+
import { readdir, readFile as readFile8, stat as stat4 } from "node:fs/promises";
|
|
27612
|
+
import path16 from "node:path";
|
|
26806
27613
|
|
|
26807
27614
|
// src/workspace/diff.ts
|
|
26808
27615
|
var CONTEXT_LINES = 3;
|
|
@@ -27012,7 +27819,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
27012
27819
|
}
|
|
27013
27820
|
async function walk(root, relativeDir, entries, options2) {
|
|
27014
27821
|
if (entries.size >= options2.maxFiles) return;
|
|
27015
|
-
const absoluteDir =
|
|
27822
|
+
const absoluteDir = path16.join(root, relativeDir);
|
|
27016
27823
|
let items;
|
|
27017
27824
|
try {
|
|
27018
27825
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -27023,8 +27830,8 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
27023
27830
|
if (entries.size >= options2.maxFiles) return;
|
|
27024
27831
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
27025
27832
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
27026
|
-
const relativePath = normalizeRelativePath(
|
|
27027
|
-
const absolutePath =
|
|
27833
|
+
const relativePath = normalizeRelativePath(path16.join(relativeDir, item.name));
|
|
27834
|
+
const absolutePath = path16.join(root, relativePath);
|
|
27028
27835
|
if (item.isDirectory()) {
|
|
27029
27836
|
await walk(root, relativePath, entries, options2);
|
|
27030
27837
|
continue;
|
|
@@ -27035,7 +27842,7 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
27035
27842
|
}
|
|
27036
27843
|
}
|
|
27037
27844
|
async function snapshotFile(filePath, relativePath, maxTextBytes) {
|
|
27038
|
-
const info = await
|
|
27845
|
+
const info = await stat4(filePath);
|
|
27039
27846
|
const sha2562 = await sha256File(filePath);
|
|
27040
27847
|
const content = info.size <= maxTextBytes ? await readTextContent(filePath) : void 0;
|
|
27041
27848
|
return { path: relativePath, size: info.size, sha256: sha2562, content };
|
|
@@ -27046,7 +27853,7 @@ function snapshotDiffText(entry) {
|
|
|
27046
27853
|
`;
|
|
27047
27854
|
}
|
|
27048
27855
|
async function readTextContent(filePath) {
|
|
27049
|
-
const buffer = await
|
|
27856
|
+
const buffer = await readFile8(filePath);
|
|
27050
27857
|
if (buffer.includes(0)) return void 0;
|
|
27051
27858
|
return buffer.toString("utf8");
|
|
27052
27859
|
}
|
|
@@ -27060,7 +27867,7 @@ function sha256File(filePath) {
|
|
|
27060
27867
|
});
|
|
27061
27868
|
}
|
|
27062
27869
|
function normalizeRelativePath(value) {
|
|
27063
|
-
return value.split(
|
|
27870
|
+
return value.split(path16.sep).join("/");
|
|
27064
27871
|
}
|
|
27065
27872
|
|
|
27066
27873
|
// src/external-runtime/session-runner.ts
|
|
@@ -27495,7 +28302,7 @@ function safeJson(value) {
|
|
|
27495
28302
|
}
|
|
27496
28303
|
|
|
27497
28304
|
// src/hooks/dispatcher.ts
|
|
27498
|
-
import
|
|
28305
|
+
import path18 from "node:path";
|
|
27499
28306
|
|
|
27500
28307
|
// src/hooks/command.ts
|
|
27501
28308
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -27588,7 +28395,7 @@ var blockDangerousBashHook = {
|
|
|
27588
28395
|
};
|
|
27589
28396
|
|
|
27590
28397
|
// src/hooks/builtin/protect-env-files.ts
|
|
27591
|
-
import
|
|
28398
|
+
import path17 from "node:path";
|
|
27592
28399
|
var protectEnvFilesHook = {
|
|
27593
28400
|
name: "protect-env-files",
|
|
27594
28401
|
event: "PreToolUse",
|
|
@@ -27596,7 +28403,7 @@ var protectEnvFilesHook = {
|
|
|
27596
28403
|
run(ctx) {
|
|
27597
28404
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
27598
28405
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27599
|
-
const filePath = typeof input2.path === "string" ?
|
|
28406
|
+
const filePath = typeof input2.path === "string" ? path17.resolve(ctx.cwd, input2.path) : "";
|
|
27600
28407
|
if (filePath && isEnvFile(filePath)) {
|
|
27601
28408
|
return {
|
|
27602
28409
|
decision: "block",
|
|
@@ -27633,7 +28440,7 @@ var maskSecretsHook = {
|
|
|
27633
28440
|
};
|
|
27634
28441
|
|
|
27635
28442
|
// src/hooks/builtin/inject-env-info.ts
|
|
27636
|
-
import
|
|
28443
|
+
import os10 from "node:os";
|
|
27637
28444
|
var injectEnvInfoHook = {
|
|
27638
28445
|
name: "inject-env-info",
|
|
27639
28446
|
event: "SessionStart",
|
|
@@ -27642,7 +28449,7 @@ var injectEnvInfoHook = {
|
|
|
27642
28449
|
decision: "allow",
|
|
27643
28450
|
patch: {
|
|
27644
28451
|
systemNote: [
|
|
27645
|
-
`Environment: ${
|
|
28452
|
+
`Environment: ${os10.type()} ${os10.release()} (${os10.platform()}/${os10.arch()})`,
|
|
27646
28453
|
`cwd: ${ctx.cwd}`,
|
|
27647
28454
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
27648
28455
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -27711,14 +28518,14 @@ function matchesHook(match, ctx) {
|
|
|
27711
28518
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27712
28519
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
27713
28520
|
if (!filePath) return false;
|
|
27714
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
28521
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path18.resolve(ctx.cwd, filePath)))) return false;
|
|
27715
28522
|
}
|
|
27716
28523
|
return true;
|
|
27717
28524
|
}
|
|
27718
28525
|
|
|
27719
28526
|
// src/multimodal.ts
|
|
27720
|
-
import { readFile as
|
|
27721
|
-
import
|
|
28527
|
+
import { readFile as readFile9, stat as stat5 } from "node:fs/promises";
|
|
28528
|
+
import path19 from "node:path";
|
|
27722
28529
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
27723
28530
|
async function buildUserContent(prompt, images = [], options2) {
|
|
27724
28531
|
if (images.length === 0) return prompt;
|
|
@@ -27737,16 +28544,16 @@ async function buildUserContent(prompt, images = [], options2) {
|
|
|
27737
28544
|
async function imageToUrl(input2, options2) {
|
|
27738
28545
|
if (/^https?:\/\//i.test(input2) || /^data:image\//i.test(input2)) return input2;
|
|
27739
28546
|
const filePath = resolveInsideCwd(options2.cwd, input2);
|
|
27740
|
-
const info = await
|
|
28547
|
+
const info = await stat5(filePath);
|
|
27741
28548
|
if (!info.isFile()) throw new Error(`Image input is not a file: ${input2}`);
|
|
27742
28549
|
const maxImageBytes = options2.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
27743
28550
|
if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
|
|
27744
28551
|
const mime = mimeFromPath(filePath);
|
|
27745
|
-
const bytes = await
|
|
28552
|
+
const bytes = await readFile9(filePath);
|
|
27746
28553
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
27747
28554
|
}
|
|
27748
28555
|
function mimeFromPath(filePath) {
|
|
27749
|
-
switch (
|
|
28556
|
+
switch (path19.extname(filePath).toLowerCase()) {
|
|
27750
28557
|
case ".jpg":
|
|
27751
28558
|
case ".jpeg":
|
|
27752
28559
|
return "image/jpeg";
|
|
@@ -27757,15 +28564,15 @@ function mimeFromPath(filePath) {
|
|
|
27757
28564
|
case ".webp":
|
|
27758
28565
|
return "image/webp";
|
|
27759
28566
|
default:
|
|
27760
|
-
throw new Error(`Unsupported image extension: ${
|
|
28567
|
+
throw new Error(`Unsupported image extension: ${path19.extname(filePath) || "(none)"}`);
|
|
27761
28568
|
}
|
|
27762
28569
|
}
|
|
27763
28570
|
|
|
27764
28571
|
// src/permissions/persistent-grants.ts
|
|
27765
|
-
import { mkdir as
|
|
28572
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
|
|
27766
28573
|
import fs8 from "node:fs";
|
|
27767
|
-
import
|
|
27768
|
-
import
|
|
28574
|
+
import os11 from "node:os";
|
|
28575
|
+
import path20 from "node:path";
|
|
27769
28576
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
27770
28577
|
var PersistentGrantStore = class {
|
|
27771
28578
|
filePath;
|
|
@@ -27796,22 +28603,22 @@ var PersistentGrantStore = class {
|
|
|
27796
28603
|
}
|
|
27797
28604
|
async #read() {
|
|
27798
28605
|
if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
|
|
27799
|
-
const data = JSON.parse(await
|
|
28606
|
+
const data = JSON.parse(await readFile10(this.filePath, "utf8"));
|
|
27800
28607
|
return {
|
|
27801
28608
|
version: 1,
|
|
27802
28609
|
grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
|
|
27803
28610
|
};
|
|
27804
28611
|
}
|
|
27805
28612
|
async #write(file) {
|
|
27806
|
-
await
|
|
27807
|
-
await
|
|
28613
|
+
await mkdir8(path20.dirname(this.filePath), { recursive: true });
|
|
28614
|
+
await writeFile7(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
27808
28615
|
`, { mode: 384 });
|
|
27809
28616
|
}
|
|
27810
28617
|
};
|
|
27811
28618
|
function resolveGrantPath(cwd, config) {
|
|
27812
|
-
if (config.path) return
|
|
27813
|
-
if ((config.scope ?? "project") === "user") return
|
|
27814
|
-
return
|
|
28619
|
+
if (config.path) return path20.resolve(cwd, config.path);
|
|
28620
|
+
if ((config.scope ?? "project") === "user") return path20.join(os11.homedir(), ".demian", "grants.json");
|
|
28621
|
+
return path20.join(cwd, ".demian", "grants.json");
|
|
27815
28622
|
}
|
|
27816
28623
|
function isGrantRecord(value) {
|
|
27817
28624
|
if (!value || typeof value !== "object") return false;
|
|
@@ -28244,25 +29051,25 @@ function fail(content, metadata) {
|
|
|
28244
29051
|
}
|
|
28245
29052
|
|
|
28246
29053
|
// src/tools/output.ts
|
|
28247
|
-
import { mkdir as
|
|
28248
|
-
import
|
|
29054
|
+
import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
|
|
29055
|
+
import path21 from "node:path";
|
|
28249
29056
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
28250
29057
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
28251
29058
|
async function capToolOutput(result, options2) {
|
|
28252
29059
|
const capBytes = options2.capBytes ?? DEFAULT_CAP_BYTES;
|
|
28253
29060
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
28254
29061
|
if (bytes <= capBytes) return result;
|
|
28255
|
-
const dir =
|
|
28256
|
-
await
|
|
28257
|
-
const outputPath =
|
|
28258
|
-
await
|
|
29062
|
+
const dir = path21.join(options2.cwd, ".demian", "tmp");
|
|
29063
|
+
await mkdir9(dir, { recursive: true });
|
|
29064
|
+
const outputPath = path21.join(dir, `output-${safeCallId(options2.callId)}.txt`);
|
|
29065
|
+
await writeFile8(outputPath, result.content, "utf8");
|
|
28259
29066
|
return {
|
|
28260
29067
|
...result,
|
|
28261
29068
|
content: [
|
|
28262
29069
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
28263
29070
|
`
|
|
28264
29071
|
|
|
28265
|
-
[Full output saved to ${
|
|
29072
|
+
[Full output saved to ${path21.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
28266
29073
|
|
|
28267
29074
|
`,
|
|
28268
29075
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -28287,8 +29094,8 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
28287
29094
|
}
|
|
28288
29095
|
|
|
28289
29096
|
// src/tools/read-file.ts
|
|
28290
|
-
import { open as open2, stat as
|
|
28291
|
-
import
|
|
29097
|
+
import { open as open2, stat as stat6 } from "node:fs/promises";
|
|
29098
|
+
import path22 from "node:path";
|
|
28292
29099
|
|
|
28293
29100
|
// src/tools/validation.ts
|
|
28294
29101
|
function assertObject(input2, toolName) {
|
|
@@ -28358,7 +29165,7 @@ var readFileTool = {
|
|
|
28358
29165
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28359
29166
|
const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
|
|
28360
29167
|
const limit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
|
|
28361
|
-
const info = await
|
|
29168
|
+
const info = await stat6(filePath);
|
|
28362
29169
|
if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
28363
29170
|
if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
28364
29171
|
const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
|
|
@@ -28372,7 +29179,7 @@ var readFileTool = {
|
|
|
28372
29179
|
const truncated = start + sliced.length < lines.length;
|
|
28373
29180
|
return ok(content + (truncated ? `
|
|
28374
29181
|
... (${lines.length - start - sliced.length} more lines)` : ""), {
|
|
28375
|
-
path:
|
|
29182
|
+
path: path22.relative(ctx.cwd, filePath),
|
|
28376
29183
|
lines: sliced.length,
|
|
28377
29184
|
totalLines: lines.length,
|
|
28378
29185
|
truncated
|
|
@@ -28410,8 +29217,8 @@ function looksBinary(buffer) {
|
|
|
28410
29217
|
}
|
|
28411
29218
|
|
|
28412
29219
|
// src/tools/write-file.ts
|
|
28413
|
-
import { mkdir as
|
|
28414
|
-
import
|
|
29220
|
+
import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
29221
|
+
import path23 from "node:path";
|
|
28415
29222
|
var writeFileTool = {
|
|
28416
29223
|
name: "write_file",
|
|
28417
29224
|
description: "Create or replace a text file inside the workspace.",
|
|
@@ -28429,8 +29236,8 @@ var writeFileTool = {
|
|
|
28429
29236
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28430
29237
|
const content = stringField(object2, "content", { allowEmpty: true });
|
|
28431
29238
|
const before = await readOptionalTextFile(filePath);
|
|
28432
|
-
await
|
|
28433
|
-
await
|
|
29239
|
+
await mkdir10(path23.dirname(filePath), { recursive: true });
|
|
29240
|
+
await writeFile9(filePath, content, "utf8");
|
|
28434
29241
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28435
29242
|
const diff = createTextDiff(before, content, relative);
|
|
28436
29243
|
return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
|
|
@@ -28443,7 +29250,7 @@ var writeFileTool = {
|
|
|
28443
29250
|
};
|
|
28444
29251
|
async function readOptionalTextFile(filePath) {
|
|
28445
29252
|
try {
|
|
28446
|
-
return await
|
|
29253
|
+
return await readFile11(filePath, "utf8");
|
|
28447
29254
|
} catch (error) {
|
|
28448
29255
|
if (isNotFound(error)) return void 0;
|
|
28449
29256
|
throw error;
|
|
@@ -28454,7 +29261,7 @@ function isNotFound(error) {
|
|
|
28454
29261
|
}
|
|
28455
29262
|
|
|
28456
29263
|
// src/tools/edit-file.ts
|
|
28457
|
-
import { readFile as
|
|
29264
|
+
import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
|
|
28458
29265
|
var editFileTool = {
|
|
28459
29266
|
name: "edit_file",
|
|
28460
29267
|
description: "Replace an exact string in a text file inside the workspace.",
|
|
@@ -28476,12 +29283,12 @@ var editFileTool = {
|
|
|
28476
29283
|
const newString = stringField(object2, "newString", { allowEmpty: true });
|
|
28477
29284
|
const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
|
|
28478
29285
|
if (oldString === newString) throw new Error("oldString and newString must be different");
|
|
28479
|
-
const before = await
|
|
29286
|
+
const before = await readFile12(filePath, "utf8");
|
|
28480
29287
|
const matches = countMatches(before, oldString);
|
|
28481
29288
|
if (matches === 0) throw new Error("oldString was not found");
|
|
28482
29289
|
if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
|
|
28483
29290
|
const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
28484
|
-
await
|
|
29291
|
+
await writeFile10(filePath, after, "utf8");
|
|
28485
29292
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28486
29293
|
const diff = createTextDiff(before, after, relative);
|
|
28487
29294
|
return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
|
|
@@ -28505,7 +29312,7 @@ function countMatches(text, needle) {
|
|
|
28505
29312
|
|
|
28506
29313
|
// src/tools/bash.ts
|
|
28507
29314
|
import { spawn as spawn5 } from "node:child_process";
|
|
28508
|
-
import
|
|
29315
|
+
import path25 from "node:path";
|
|
28509
29316
|
|
|
28510
29317
|
// src/sandbox/env-only.ts
|
|
28511
29318
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -28565,7 +29372,7 @@ function bwrapPath() {
|
|
|
28565
29372
|
|
|
28566
29373
|
// src/sandbox/macos.ts
|
|
28567
29374
|
import { existsSync as existsSync3 } from "node:fs";
|
|
28568
|
-
import
|
|
29375
|
+
import path24 from "node:path";
|
|
28569
29376
|
function canUseMacOSSandbox() {
|
|
28570
29377
|
return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
|
|
28571
29378
|
}
|
|
@@ -28591,7 +29398,7 @@ function macosProfile(cwd, config) {
|
|
|
28591
29398
|
}
|
|
28592
29399
|
if (mode === "workspace-write") {
|
|
28593
29400
|
lines.push("(deny file-write*)");
|
|
28594
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
29401
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path24.resolve(cwd))}"))`);
|
|
28595
29402
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
28596
29403
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
28597
29404
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -28653,7 +29460,7 @@ ${result.stderr}` : ""
|
|
|
28653
29460
|
].filter(Boolean).join("\n");
|
|
28654
29461
|
return ok(content, {
|
|
28655
29462
|
command,
|
|
28656
|
-
workdir:
|
|
29463
|
+
workdir: path25.relative(ctx.cwd, workdir) || ".",
|
|
28657
29464
|
exitCode: result.exitCode,
|
|
28658
29465
|
signal: result.signal,
|
|
28659
29466
|
timedOut: result.timedOut,
|
|
@@ -28705,8 +29512,8 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
28705
29512
|
|
|
28706
29513
|
// src/tools/grep.ts
|
|
28707
29514
|
import { spawn as spawn6 } from "node:child_process";
|
|
28708
|
-
import { readdir as readdir2, readFile as
|
|
28709
|
-
import
|
|
29515
|
+
import { readdir as readdir2, readFile as readFile13, stat as stat7 } from "node:fs/promises";
|
|
29516
|
+
import path26 from "node:path";
|
|
28710
29517
|
var MAX_MATCHES = 200;
|
|
28711
29518
|
var grepTool = {
|
|
28712
29519
|
name: "grep",
|
|
@@ -28781,7 +29588,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
28781
29588
|
}
|
|
28782
29589
|
function rewriteRgPath(cwd, line) {
|
|
28783
29590
|
const [file, rest] = splitFirst(line, ":");
|
|
28784
|
-
if (!
|
|
29591
|
+
if (!path26.isAbsolute(file)) return line;
|
|
28785
29592
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
28786
29593
|
}
|
|
28787
29594
|
function splitFirst(value, delimiter) {
|
|
@@ -28796,13 +29603,13 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28796
29603
|
if (out.length > MAX_MATCHES) return;
|
|
28797
29604
|
const relative = relativeToCwd(cwd, item);
|
|
28798
29605
|
if (isIgnoredPath(relative)) return;
|
|
28799
|
-
const info = await
|
|
29606
|
+
const info = await stat7(item);
|
|
28800
29607
|
if (info.isDirectory()) {
|
|
28801
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
29608
|
+
for (const entry of await readdir2(item)) await walk2(path26.join(item, entry));
|
|
28802
29609
|
return;
|
|
28803
29610
|
}
|
|
28804
29611
|
if (glob && !matchGlob(glob, relative)) return;
|
|
28805
|
-
const buffer = await
|
|
29612
|
+
const buffer = await readFile13(item);
|
|
28806
29613
|
if (buffer.includes(0)) return;
|
|
28807
29614
|
const lines = buffer.toString("utf8").split(/\r?\n/);
|
|
28808
29615
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -28816,8 +29623,8 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28816
29623
|
}
|
|
28817
29624
|
|
|
28818
29625
|
// src/tools/glob.ts
|
|
28819
|
-
import { readdir as readdir3, stat as
|
|
28820
|
-
import
|
|
29626
|
+
import { readdir as readdir3, stat as stat8 } from "node:fs/promises";
|
|
29627
|
+
import path27 from "node:path";
|
|
28821
29628
|
var MAX_PATHS = 1e3;
|
|
28822
29629
|
var globTool = {
|
|
28823
29630
|
name: "glob",
|
|
@@ -28849,10 +29656,10 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
28849
29656
|
async function walk2(dir) {
|
|
28850
29657
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
28851
29658
|
for (const entry of entries) {
|
|
28852
|
-
const absolute =
|
|
29659
|
+
const absolute = path27.join(dir, entry.name);
|
|
28853
29660
|
const relative = relativeToCwd(cwd, absolute);
|
|
28854
29661
|
if (isIgnoredPath(relative)) continue;
|
|
28855
|
-
const info = await
|
|
29662
|
+
const info = await stat8(absolute);
|
|
28856
29663
|
if (matchGlob(pattern, relative)) out.push({ relative, mtimeMs: info.mtimeMs });
|
|
28857
29664
|
if (entry.isDirectory()) await walk2(absolute);
|
|
28858
29665
|
}
|
|
@@ -29851,14 +30658,14 @@ async function runExecutionSession(options2) {
|
|
|
29851
30658
|
}
|
|
29852
30659
|
|
|
29853
30660
|
// src/goals/lock.ts
|
|
29854
|
-
import { mkdir as
|
|
29855
|
-
import
|
|
30661
|
+
import { mkdir as mkdir12, open as open3, rm as rm2 } from "node:fs/promises";
|
|
30662
|
+
import path29 from "node:path";
|
|
29856
30663
|
|
|
29857
30664
|
// src/goals/storage.ts
|
|
29858
30665
|
import { createHash as createHash3 } from "node:crypto";
|
|
29859
|
-
import { mkdir as
|
|
30666
|
+
import { mkdir as mkdir11, readFile as readFile14, rename as rename5, writeFile as writeFile11 } from "node:fs/promises";
|
|
29860
30667
|
import fs9 from "node:fs";
|
|
29861
|
-
import
|
|
30668
|
+
import path28 from "node:path";
|
|
29862
30669
|
var GoalStore = class {
|
|
29863
30670
|
cwd;
|
|
29864
30671
|
dir;
|
|
@@ -29869,12 +30676,12 @@ var GoalStore = class {
|
|
|
29869
30676
|
this.cwd = cwd;
|
|
29870
30677
|
this.scope = normalizeGoalStoreScope(options2.scope);
|
|
29871
30678
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
29872
|
-
this.activePath =
|
|
29873
|
-
this.archiveDir =
|
|
30679
|
+
this.activePath = path28.join(this.dir, "active.json");
|
|
30680
|
+
this.archiveDir = path28.join(this.dir, "archive");
|
|
29874
30681
|
}
|
|
29875
30682
|
async loadActive() {
|
|
29876
30683
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
29877
|
-
const text = await
|
|
30684
|
+
const text = await readFile14(this.activePath, "utf8");
|
|
29878
30685
|
try {
|
|
29879
30686
|
return JSON.parse(text);
|
|
29880
30687
|
} catch {
|
|
@@ -29883,36 +30690,36 @@ var GoalStore = class {
|
|
|
29883
30690
|
}
|
|
29884
30691
|
}
|
|
29885
30692
|
async saveActive(state) {
|
|
29886
|
-
await
|
|
29887
|
-
const tmp =
|
|
29888
|
-
await
|
|
30693
|
+
await mkdir11(this.dir, { recursive: true });
|
|
30694
|
+
const tmp = path28.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
30695
|
+
await writeFile11(tmp, `${JSON.stringify(state, null, 2)}
|
|
29889
30696
|
`, "utf8");
|
|
29890
|
-
await
|
|
30697
|
+
await rename5(tmp, this.activePath);
|
|
29891
30698
|
}
|
|
29892
30699
|
async archiveActive(status = "cleared") {
|
|
29893
30700
|
const state = await this.loadActive();
|
|
29894
30701
|
if (!state) return void 0;
|
|
29895
|
-
await
|
|
30702
|
+
await mkdir11(this.archiveDir, { recursive: true });
|
|
29896
30703
|
const archived = {
|
|
29897
30704
|
...state,
|
|
29898
30705
|
status,
|
|
29899
30706
|
updatedAt: Date.now()
|
|
29900
30707
|
};
|
|
29901
|
-
await
|
|
30708
|
+
await writeFile11(path28.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
29902
30709
|
`, "utf8");
|
|
29903
30710
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29904
30711
|
return archived;
|
|
29905
30712
|
}
|
|
29906
30713
|
async backupCorrupted(text) {
|
|
29907
|
-
await
|
|
29908
|
-
const backup =
|
|
29909
|
-
await
|
|
30714
|
+
await mkdir11(this.dir, { recursive: true });
|
|
30715
|
+
const backup = path28.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
30716
|
+
await writeFile11(backup, text, "utf8");
|
|
29910
30717
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29911
30718
|
}
|
|
29912
30719
|
};
|
|
29913
30720
|
function goalStoreDir(cwd, scope) {
|
|
29914
30721
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
29915
|
-
return safeScope ?
|
|
30722
|
+
return safeScope ? path28.join(cwd, ".demian", "goals", "sessions", safeScope) : path28.join(cwd, ".demian", "goals");
|
|
29916
30723
|
}
|
|
29917
30724
|
function normalizeGoalStoreScope(scope) {
|
|
29918
30725
|
const trimmed = scope?.trim();
|
|
@@ -29926,10 +30733,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
29926
30733
|
var GoalLock = class {
|
|
29927
30734
|
path;
|
|
29928
30735
|
constructor(cwd, scope) {
|
|
29929
|
-
this.path =
|
|
30736
|
+
this.path = path29.join(goalStoreDir(cwd, scope), "active.lock");
|
|
29930
30737
|
}
|
|
29931
30738
|
async acquire() {
|
|
29932
|
-
await
|
|
30739
|
+
await mkdir12(path29.dirname(this.path), { recursive: true });
|
|
29933
30740
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
29934
30741
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
29935
30742
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -30046,8 +30853,8 @@ async function summarizeGoalTitleWithProvider(options2) {
|
|
|
30046
30853
|
}
|
|
30047
30854
|
function normalizeGoalTitle(value, objective) {
|
|
30048
30855
|
const raw = typeof value === "string" ? value : "";
|
|
30049
|
-
const
|
|
30050
|
-
const cleaned = stripDecorations(
|
|
30856
|
+
const firstLine2 = raw.split(/\r?\n/).find((line) => line.trim()) ?? "";
|
|
30857
|
+
const cleaned = stripDecorations(firstLine2);
|
|
30051
30858
|
return truncateGoalTitle(cleaned || fallbackGoalTitle(objective));
|
|
30052
30859
|
}
|
|
30053
30860
|
function fallbackGoalTitle(objective) {
|
|
@@ -30517,8 +31324,8 @@ function sha256(value) {
|
|
|
30517
31324
|
|
|
30518
31325
|
// src/root-session.ts
|
|
30519
31326
|
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
30520
|
-
import
|
|
30521
|
-
import
|
|
31327
|
+
import os12 from "node:os";
|
|
31328
|
+
import path30 from "node:path";
|
|
30522
31329
|
|
|
30523
31330
|
// src/tools/cowork.ts
|
|
30524
31331
|
function createCoworkTool(options2) {
|
|
@@ -31341,8 +32148,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31341
32148
|
}
|
|
31342
32149
|
}
|
|
31343
32150
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
31344
|
-
const root = await mkdtemp(
|
|
31345
|
-
const cwd =
|
|
32151
|
+
const root = await mkdtemp(path30.join(os12.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
32152
|
+
const cwd = path30.join(root, "workspace");
|
|
31346
32153
|
await cp(this.#options.cwd, cwd, {
|
|
31347
32154
|
recursive: true,
|
|
31348
32155
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -31646,17 +32453,17 @@ function findDependencyCycle(group) {
|
|
|
31646
32453
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
31647
32454
|
const visiting = /* @__PURE__ */ new Set();
|
|
31648
32455
|
const visited = /* @__PURE__ */ new Set();
|
|
31649
|
-
const
|
|
32456
|
+
const path37 = [];
|
|
31650
32457
|
const visit = (id) => {
|
|
31651
|
-
if (visiting.has(id)) return [...
|
|
32458
|
+
if (visiting.has(id)) return [...path37.slice(path37.indexOf(id)), id];
|
|
31652
32459
|
if (visited.has(id)) return void 0;
|
|
31653
32460
|
visiting.add(id);
|
|
31654
|
-
|
|
32461
|
+
path37.push(id);
|
|
31655
32462
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
31656
32463
|
const cycle = visit(dep);
|
|
31657
32464
|
if (cycle) return cycle;
|
|
31658
32465
|
}
|
|
31659
|
-
|
|
32466
|
+
path37.pop();
|
|
31660
32467
|
visiting.delete(id);
|
|
31661
32468
|
visited.add(id);
|
|
31662
32469
|
return void 0;
|
|
@@ -31697,7 +32504,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
31697
32504
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
31698
32505
|
}
|
|
31699
32506
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
31700
|
-
const relative =
|
|
32507
|
+
const relative = path30.relative(root, source).split(path30.sep).join("/");
|
|
31701
32508
|
if (!relative) return true;
|
|
31702
32509
|
const parts = relative.split("/");
|
|
31703
32510
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -31909,13 +32716,13 @@ function finitePositiveInteger(value) {
|
|
|
31909
32716
|
}
|
|
31910
32717
|
|
|
31911
32718
|
// src/ui/preferences.ts
|
|
31912
|
-
import { mkdir as
|
|
32719
|
+
import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
31913
32720
|
import fs10 from "node:fs";
|
|
31914
|
-
import
|
|
32721
|
+
import path31 from "node:path";
|
|
31915
32722
|
var UiPreferenceStore = class {
|
|
31916
32723
|
filePath;
|
|
31917
32724
|
constructor(cwd, filePath) {
|
|
31918
|
-
this.filePath = filePath ?
|
|
32725
|
+
this.filePath = filePath ? path31.resolve(cwd, filePath) : path31.join(cwd, ".demian", "preferences.json");
|
|
31919
32726
|
}
|
|
31920
32727
|
async load() {
|
|
31921
32728
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -31933,13 +32740,13 @@ var UiPreferenceStore = class {
|
|
|
31933
32740
|
model: selection.model,
|
|
31934
32741
|
updatedAt: Date.now()
|
|
31935
32742
|
};
|
|
31936
|
-
await
|
|
31937
|
-
await
|
|
32743
|
+
await mkdir13(path31.dirname(this.filePath), { recursive: true });
|
|
32744
|
+
await writeFile12(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
31938
32745
|
`, { mode: 384 });
|
|
31939
32746
|
}
|
|
31940
32747
|
async #read() {
|
|
31941
32748
|
try {
|
|
31942
|
-
return JSON.parse(await
|
|
32749
|
+
return JSON.parse(await readFile15(this.filePath, "utf8"));
|
|
31943
32750
|
} catch {
|
|
31944
32751
|
return void 0;
|
|
31945
32752
|
}
|
|
@@ -31955,9 +32762,9 @@ function preferenceKey(selection) {
|
|
|
31955
32762
|
|
|
31956
32763
|
// src/models/catalog.ts
|
|
31957
32764
|
import crypto6 from "node:crypto";
|
|
31958
|
-
import { mkdir as
|
|
31959
|
-
import
|
|
31960
|
-
import
|
|
32765
|
+
import { mkdir as mkdir14, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
32766
|
+
import os13 from "node:os";
|
|
32767
|
+
import path32 from "node:path";
|
|
31961
32768
|
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
31962
32769
|
var PING_TIMEOUT_MS = 1500;
|
|
31963
32770
|
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
@@ -32269,12 +33076,12 @@ async function fetchJson2(url, options2) {
|
|
|
32269
33076
|
return text ? JSON.parse(text) : {};
|
|
32270
33077
|
}
|
|
32271
33078
|
function cachePath(cacheKey) {
|
|
32272
|
-
return
|
|
33079
|
+
return path32.join(os13.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
32273
33080
|
}
|
|
32274
33081
|
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
32275
33082
|
if (!allow) return void 0;
|
|
32276
33083
|
try {
|
|
32277
|
-
const raw = JSON.parse(await
|
|
33084
|
+
const raw = JSON.parse(await readFile16(cachePath(cacheKey), "utf8"));
|
|
32278
33085
|
if (!raw.result) return void 0;
|
|
32279
33086
|
if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
|
|
32280
33087
|
return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
|
|
@@ -32284,10 +33091,10 @@ async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
|
32284
33091
|
}
|
|
32285
33092
|
async function writeCatalogCache(cacheKey, result) {
|
|
32286
33093
|
const filePath = cachePath(cacheKey);
|
|
32287
|
-
await
|
|
33094
|
+
await mkdir14(path32.dirname(filePath), { recursive: true, mode: 448 });
|
|
32288
33095
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32289
|
-
await
|
|
32290
|
-
await
|
|
33096
|
+
await writeFile13(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
33097
|
+
await rename6(temp, filePath);
|
|
32291
33098
|
}
|
|
32292
33099
|
function safeCacheName(providerName) {
|
|
32293
33100
|
return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
|
|
@@ -32338,7 +33145,7 @@ async function codexClientVersion(override) {
|
|
|
32338
33145
|
}
|
|
32339
33146
|
async function readPackageVersion() {
|
|
32340
33147
|
try {
|
|
32341
|
-
const raw = JSON.parse(await
|
|
33148
|
+
const raw = JSON.parse(await readFile16(new URL("../package.json", import.meta.url), "utf8"));
|
|
32342
33149
|
return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
|
|
32343
33150
|
} catch {
|
|
32344
33151
|
return DEFAULT_CODEX_CLIENT_VERSION;
|
|
@@ -32402,6 +33209,9 @@ function providerOptions(config, catalogs = {}) {
|
|
|
32402
33209
|
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
32403
33210
|
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
32404
33211
|
available,
|
|
33212
|
+
baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
|
|
33213
|
+
apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
|
|
33214
|
+
auth: sanitizeProviderAuth(provider.auth),
|
|
32405
33215
|
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
32406
33216
|
};
|
|
32407
33217
|
}).sort((left, right) => {
|
|
@@ -32473,17 +33283,228 @@ function providerCatalogAvailable(catalog) {
|
|
|
32473
33283
|
function unavailableLabel(value) {
|
|
32474
33284
|
return `(n/a) ${value}`;
|
|
32475
33285
|
}
|
|
33286
|
+
function sanitizeProviderAuth(auth) {
|
|
33287
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
33288
|
+
const record = auth;
|
|
33289
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
33290
|
+
const header = typeof record.header === "string" ? record.header : void 0;
|
|
33291
|
+
return type || header ? { type, header } : void 0;
|
|
33292
|
+
}
|
|
32476
33293
|
function errorMessage3(error) {
|
|
32477
33294
|
return error instanceof Error ? error.message : String(error);
|
|
32478
33295
|
}
|
|
32479
33296
|
|
|
33297
|
+
// src/ui/conversations.ts
|
|
33298
|
+
import { mkdir as mkdir15, readFile as readFile17, readdir as readdir4, rm as rm4, writeFile as writeFile14 } from "node:fs/promises";
|
|
33299
|
+
import path33 from "node:path";
|
|
33300
|
+
var CONVERSATIONS_DIR = "conversations";
|
|
33301
|
+
var CONVERSATION_INDEX_FILE = "conversations.json";
|
|
33302
|
+
var CONVERSATION_FILE = "conversation.json";
|
|
33303
|
+
var MAX_STORED_CONVERSATIONS = 40;
|
|
33304
|
+
var MAX_MODEL_HISTORY = 80;
|
|
33305
|
+
function createConversationRecord(input2) {
|
|
33306
|
+
const timestamp = input2.now ?? Date.now();
|
|
33307
|
+
return {
|
|
33308
|
+
id: sanitizeConversationId(input2.id),
|
|
33309
|
+
title: input2.title?.trim() || "New session",
|
|
33310
|
+
createdAt: timestamp,
|
|
33311
|
+
updatedAt: timestamp,
|
|
33312
|
+
cwd: path33.resolve(input2.cwd),
|
|
33313
|
+
modelHistory: cleanModelHistory(input2.history ?? []),
|
|
33314
|
+
snapshot: input2.snapshot
|
|
33315
|
+
};
|
|
33316
|
+
}
|
|
33317
|
+
async function loadConversationIndex(storageDir = defaultDemianStorageDir()) {
|
|
33318
|
+
const index = await readJson(path33.join(storageDir, CONVERSATION_INDEX_FILE));
|
|
33319
|
+
const conversations = Array.isArray(index?.conversations) ? index.conversations.map((item) => {
|
|
33320
|
+
if (!item || typeof item !== "object") return void 0;
|
|
33321
|
+
const object2 = item;
|
|
33322
|
+
const id = typeof object2.id === "string" ? sanitizeConversationId(object2.id) : "";
|
|
33323
|
+
if (!id) return void 0;
|
|
33324
|
+
return {
|
|
33325
|
+
id,
|
|
33326
|
+
title: typeof object2.title === "string" && object2.title.trim() ? object2.title.trim() : "New session",
|
|
33327
|
+
updatedAt: numberOrNow(object2.updatedAt),
|
|
33328
|
+
cwd: typeof object2.cwd === "string" ? object2.cwd : void 0
|
|
33329
|
+
};
|
|
33330
|
+
}).filter((item) => Boolean(item)) : [];
|
|
33331
|
+
return {
|
|
33332
|
+
selectedSessionId: typeof index?.selectedSessionId === "string" ? sanitizeConversationId(index.selectedSessionId) : void 0,
|
|
33333
|
+
conversations
|
|
33334
|
+
};
|
|
33335
|
+
}
|
|
33336
|
+
async function loadConversationRecords(storageDir = defaultDemianStorageDir()) {
|
|
33337
|
+
const index = await loadConversationIndex(storageDir);
|
|
33338
|
+
const ids = new Set(index.conversations.map((item) => item.id));
|
|
33339
|
+
for (const id of await listConversationRecordIds(storageDir)) ids.add(id);
|
|
33340
|
+
const records = (await Promise.all(
|
|
33341
|
+
[...ids].map(async (id) => {
|
|
33342
|
+
const item = await readJson(conversationRecordPath(storageDir, id));
|
|
33343
|
+
return normalizeConversationRecord(item, id);
|
|
33344
|
+
})
|
|
33345
|
+
)).filter((item) => Boolean(item));
|
|
33346
|
+
records.sort((a, b2) => b2.updatedAt - a.updatedAt);
|
|
33347
|
+
return { selectedSessionId: index.selectedSessionId, records };
|
|
33348
|
+
}
|
|
33349
|
+
async function saveConversationRecords(storageDir, records, selectedSessionId) {
|
|
33350
|
+
const root = storageDir ?? defaultDemianStorageDir();
|
|
33351
|
+
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) }));
|
|
33352
|
+
await mkdir15(path33.join(root, CONVERSATIONS_DIR), { recursive: true });
|
|
33353
|
+
for (const record of kept) {
|
|
33354
|
+
await writeJson(conversationRecordPath(root, record.id), {
|
|
33355
|
+
version: 1,
|
|
33356
|
+
id: record.id,
|
|
33357
|
+
title: record.title,
|
|
33358
|
+
createdAt: record.createdAt,
|
|
33359
|
+
updatedAt: record.updatedAt,
|
|
33360
|
+
cwd: record.cwd,
|
|
33361
|
+
modelHistory: record.modelHistory,
|
|
33362
|
+
snapshot: record.snapshot
|
|
33363
|
+
});
|
|
33364
|
+
}
|
|
33365
|
+
await writeJson(path33.join(root, CONVERSATION_INDEX_FILE), {
|
|
33366
|
+
version: 1,
|
|
33367
|
+
selectedSessionId,
|
|
33368
|
+
conversations: kept.map((record) => ({
|
|
33369
|
+
id: record.id,
|
|
33370
|
+
title: record.title,
|
|
33371
|
+
updatedAt: record.updatedAt,
|
|
33372
|
+
cwd: record.cwd,
|
|
33373
|
+
path: conversationDir(root, record.id)
|
|
33374
|
+
}))
|
|
33375
|
+
});
|
|
33376
|
+
}
|
|
33377
|
+
async function deleteConversationRecord(storageDir, id) {
|
|
33378
|
+
const root = storageDir ?? defaultDemianStorageDir();
|
|
33379
|
+
await rm4(conversationDir(root, id), { recursive: true, force: true });
|
|
33380
|
+
}
|
|
33381
|
+
function findConversation(records, target) {
|
|
33382
|
+
const value = target?.trim();
|
|
33383
|
+
if (!value) return void 0;
|
|
33384
|
+
const index = Number(value);
|
|
33385
|
+
if (Number.isInteger(index) && index >= 1 && index <= records.length) return records[index - 1];
|
|
33386
|
+
const normalized = value.toLowerCase();
|
|
33387
|
+
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));
|
|
33388
|
+
}
|
|
33389
|
+
function sortConversationRecords(records, cwd) {
|
|
33390
|
+
const resolvedCwd = path33.resolve(cwd);
|
|
33391
|
+
return [...records].sort((a, b2) => {
|
|
33392
|
+
const aCurrent = isConversationForCwd(a, resolvedCwd) ? 0 : 1;
|
|
33393
|
+
const bCurrent = isConversationForCwd(b2, resolvedCwd) ? 0 : 1;
|
|
33394
|
+
if (aCurrent !== bCurrent) return aCurrent - bCurrent;
|
|
33395
|
+
const statusDelta = conversationStatusRank(conversationRuntimeStatus(a, resolvedCwd)) - conversationStatusRank(conversationRuntimeStatus(b2, resolvedCwd));
|
|
33396
|
+
if (statusDelta !== 0) return statusDelta;
|
|
33397
|
+
return b2.updatedAt - a.updatedAt;
|
|
33398
|
+
});
|
|
33399
|
+
}
|
|
33400
|
+
function conversationRuntimeStatus(record, cwd) {
|
|
33401
|
+
if (record.snapshot?.inputMode === "running") return "running";
|
|
33402
|
+
return isConversationForCwd(record, cwd) ? "ready" : "stored";
|
|
33403
|
+
}
|
|
33404
|
+
function isConversationForCwd(record, cwd) {
|
|
33405
|
+
return path33.resolve(record.cwd) === path33.resolve(cwd);
|
|
33406
|
+
}
|
|
33407
|
+
function conversationSummaryLine(record, index, activeId) {
|
|
33408
|
+
const active = record.id === activeId ? "*" : " ";
|
|
33409
|
+
const age = formatRelativeAge(Date.now() - record.updatedAt);
|
|
33410
|
+
return `${active} ${index + 1}. ${record.title} ${record.id} ${age} ${record.cwd}`;
|
|
33411
|
+
}
|
|
33412
|
+
function conversationStatusRank(status) {
|
|
33413
|
+
if (status === "running") return 0;
|
|
33414
|
+
if (status === "ready") return 1;
|
|
33415
|
+
return 2;
|
|
33416
|
+
}
|
|
33417
|
+
function titleFromHistory(history, fallback = "New session") {
|
|
33418
|
+
const firstUser = history.find((message) => message.role === "user" && typeof message.content === "string");
|
|
33419
|
+
return firstLine(firstUser?.content ?? fallback);
|
|
33420
|
+
}
|
|
33421
|
+
function cleanModelHistory(history) {
|
|
33422
|
+
return history.filter((message) => !isInternalModelHistoryMessage(message)).slice(-MAX_MODEL_HISTORY);
|
|
33423
|
+
}
|
|
33424
|
+
function conversationDir(storageDir, id) {
|
|
33425
|
+
return path33.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
|
|
33426
|
+
}
|
|
33427
|
+
function conversationRecordPath(storageDir, id) {
|
|
33428
|
+
return path33.join(conversationDir(storageDir, id), CONVERSATION_FILE);
|
|
33429
|
+
}
|
|
33430
|
+
async function listConversationRecordIds(storageDir) {
|
|
33431
|
+
try {
|
|
33432
|
+
const entries = await readdir4(path33.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
|
|
33433
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => sanitizeConversationId(entry.name)).filter(Boolean);
|
|
33434
|
+
} catch {
|
|
33435
|
+
return [];
|
|
33436
|
+
}
|
|
33437
|
+
}
|
|
33438
|
+
async function readJson(filePath) {
|
|
33439
|
+
try {
|
|
33440
|
+
return JSON.parse(await readFile17(filePath, "utf8"));
|
|
33441
|
+
} catch {
|
|
33442
|
+
return void 0;
|
|
33443
|
+
}
|
|
33444
|
+
}
|
|
33445
|
+
async function writeJson(filePath, value) {
|
|
33446
|
+
await mkdir15(path33.dirname(filePath), { recursive: true });
|
|
33447
|
+
await writeFile14(filePath, `${JSON.stringify(value, null, 2)}
|
|
33448
|
+
`, "utf8");
|
|
33449
|
+
}
|
|
33450
|
+
function normalizeConversationRecord(value, fallbackId) {
|
|
33451
|
+
if (!value || typeof value !== "object") return void 0;
|
|
33452
|
+
const object2 = value;
|
|
33453
|
+
const id = sanitizeConversationId(typeof object2.id === "string" ? object2.id : fallbackId);
|
|
33454
|
+
if (!id) return void 0;
|
|
33455
|
+
const history = Array.isArray(object2.modelHistory) ? object2.modelHistory.filter(isMessage) : [];
|
|
33456
|
+
const snapshot = object2.snapshot && typeof object2.snapshot === "object" && !Array.isArray(object2.snapshot) ? object2.snapshot : void 0;
|
|
33457
|
+
return {
|
|
33458
|
+
id,
|
|
33459
|
+
title: typeof object2.title === "string" && object2.title.trim() ? object2.title.trim() : titleFromHistory(history),
|
|
33460
|
+
createdAt: numberOrNow(object2.createdAt),
|
|
33461
|
+
updatedAt: numberOrNow(object2.updatedAt),
|
|
33462
|
+
cwd: typeof object2.cwd === "string" && object2.cwd.trim() ? object2.cwd : process.cwd(),
|
|
33463
|
+
modelHistory: cleanModelHistory(history),
|
|
33464
|
+
snapshot
|
|
33465
|
+
};
|
|
33466
|
+
}
|
|
33467
|
+
function isMessage(value) {
|
|
33468
|
+
if (!value || typeof value !== "object") return false;
|
|
33469
|
+
const message = value;
|
|
33470
|
+
if (message.role === "user") return typeof message.content === "string" || Array.isArray(message.content);
|
|
33471
|
+
if (message.role === "assistant") return message.content === void 0 || message.content === null || typeof message.content === "string" || Array.isArray(message.toolCalls);
|
|
33472
|
+
if (message.role === "tool") return typeof message.toolCallId === "string" && typeof message.name === "string" && typeof message.content === "string";
|
|
33473
|
+
if (message.role === "system") return typeof message.content === "string";
|
|
33474
|
+
return false;
|
|
33475
|
+
}
|
|
33476
|
+
function isInternalModelHistoryMessage(message) {
|
|
33477
|
+
return message.role === "user" && typeof message.content === "string" && /^You are running as sub agent:/i.test(message.content.trim());
|
|
33478
|
+
}
|
|
33479
|
+
function sanitizeConversationId(id) {
|
|
33480
|
+
return String(id || "conversation").replace(/[^a-zA-Z0-9._-]+/g, "_") || "conversation";
|
|
33481
|
+
}
|
|
33482
|
+
function numberOrNow(value) {
|
|
33483
|
+
return typeof value === "number" && Number.isFinite(value) ? value : Date.now();
|
|
33484
|
+
}
|
|
33485
|
+
function firstLine(value) {
|
|
33486
|
+
const line = value.replace(/\s+/g, " ").trim();
|
|
33487
|
+
if (!line) return "New session";
|
|
33488
|
+
return line.length > 60 ? `${line.slice(0, 57)}...` : line;
|
|
33489
|
+
}
|
|
33490
|
+
function formatRelativeAge(ms2) {
|
|
33491
|
+
const seconds = Math.max(0, Math.floor(ms2 / 1e3));
|
|
33492
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
33493
|
+
const minutes = Math.floor(seconds / 60);
|
|
33494
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
33495
|
+
const hours = Math.floor(minutes / 60);
|
|
33496
|
+
if (hours < 48) return `${hours}h ago`;
|
|
33497
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
33498
|
+
}
|
|
33499
|
+
|
|
32480
33500
|
// src/ui/tui/controller.ts
|
|
32481
33501
|
async function runTuiSession(flags, store2) {
|
|
32482
|
-
const cwd =
|
|
33502
|
+
const cwd = path34.resolve(flags.cwd ?? process.cwd());
|
|
32483
33503
|
const eventBus = new EventBus();
|
|
32484
33504
|
eventBus.subscribe((event) => store2.handleEvent(event));
|
|
32485
|
-
const
|
|
32486
|
-
|
|
33505
|
+
const configInit = await ensureTuiConfigExists(flags);
|
|
33506
|
+
let loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
33507
|
+
let config = flags.context ? mergeConfig(loadedConfig2, {
|
|
32487
33508
|
context: {
|
|
32488
33509
|
main: flags.context
|
|
32489
33510
|
}
|
|
@@ -32503,6 +33524,10 @@ async function runTuiSession(flags, store2) {
|
|
|
32503
33524
|
store2.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
32504
33525
|
agent: agentName,
|
|
32505
33526
|
cwd,
|
|
33527
|
+
configPath: configMutationPath(flags),
|
|
33528
|
+
configCreated: configInit.created,
|
|
33529
|
+
configDefaultProvider: config.defaultProvider,
|
|
33530
|
+
configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
|
|
32506
33531
|
agentOptions,
|
|
32507
33532
|
contextEfficiency: {
|
|
32508
33533
|
maxContextTokens: config.context.main.maxContextTokens,
|
|
@@ -32511,10 +33536,21 @@ async function runTuiSession(flags, store2) {
|
|
|
32511
33536
|
}
|
|
32512
33537
|
});
|
|
32513
33538
|
let nextPrompt = flags.prompt;
|
|
32514
|
-
let
|
|
33539
|
+
let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
|
|
33540
|
+
const conversationsEnabled = flags.conversationManagement === true;
|
|
33541
|
+
const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
|
|
33542
|
+
let currentConversation = conversationState?.current;
|
|
33543
|
+
const conversationRecords = conversationState?.records ?? /* @__PURE__ */ new Map();
|
|
33544
|
+
const shouldShowStartupSessionSelector = conversationsEnabled && !flags.prompt.trim() && !flags.sessionId;
|
|
33545
|
+
let history = flags.initialHistory ? [...flags.initialHistory] : currentConversation?.modelHistory ? [...currentConversation.modelHistory] : [];
|
|
32515
33546
|
let lastExternalSessionKey;
|
|
32516
|
-
|
|
32517
|
-
const
|
|
33547
|
+
let externalSessions = new ClaudeCodeSessionMap();
|
|
33548
|
+
const runtimeByConversation = /* @__PURE__ */ new Map();
|
|
33549
|
+
let rootSessionId = currentConversation?.id ?? flags.sessionId ?? createRootSessionId();
|
|
33550
|
+
if (currentConversation && !shouldShowStartupSessionSelector) {
|
|
33551
|
+
store2.restoreConversationSnapshot(currentConversation.snapshot, { sessionId: currentConversation.id, title: currentConversation.title, cwd: currentConversation.cwd });
|
|
33552
|
+
runtimeByConversation.set(currentConversation.id, { externalSessions, lastExternalSessionKey });
|
|
33553
|
+
}
|
|
32518
33554
|
const goalTitleGenerator = async (input2) => {
|
|
32519
33555
|
const selection = store2.currentSelection();
|
|
32520
33556
|
const runtime = resolveProviderRuntimeConfig(config, selection);
|
|
@@ -32532,19 +33568,48 @@ async function runTuiSession(flags, store2) {
|
|
|
32532
33568
|
if (resolved.kind === "external-agent") return input2.objective.trim().split(/\s+/).slice(0, 8).join(" ");
|
|
32533
33569
|
return summarizeGoalTitleWithProvider({ ...input2, provider: resolved.provider, model: resolved.model });
|
|
32534
33570
|
};
|
|
33571
|
+
if (shouldShowStartupSessionSelector) {
|
|
33572
|
+
const selected = await store2.requestSessionSelection(startupSessionOptions());
|
|
33573
|
+
if (selected.kind === "exit" || store2.exitRequested()) {
|
|
33574
|
+
await saveCurrentSelection();
|
|
33575
|
+
return 0;
|
|
33576
|
+
}
|
|
33577
|
+
await applyStartupSessionSelection(selected);
|
|
33578
|
+
}
|
|
32535
33579
|
for (; ; ) {
|
|
32536
|
-
const
|
|
33580
|
+
const promptPromise = store2.requestPrompt(nextPrompt);
|
|
33581
|
+
if (openConfigOnFirstPrompt) {
|
|
33582
|
+
openConfigOnFirstPrompt = false;
|
|
33583
|
+
store2.openConfigManager(`Created config at ${configInit.path}`);
|
|
33584
|
+
}
|
|
33585
|
+
const prompt = await promptPromise;
|
|
32537
33586
|
nextPrompt = void 0;
|
|
32538
33587
|
if (store2.exitRequested()) {
|
|
32539
33588
|
await saveCurrentSelection();
|
|
32540
33589
|
return 0;
|
|
32541
33590
|
}
|
|
32542
33591
|
if (!prompt) return 0;
|
|
33592
|
+
const configAction = parseTuiConfigAction(prompt);
|
|
33593
|
+
if (configAction) {
|
|
33594
|
+
await handleConfigAction(configAction);
|
|
33595
|
+
continue;
|
|
33596
|
+
}
|
|
33597
|
+
const sessionAction = parseTuiSessionAction(prompt);
|
|
33598
|
+
if (sessionAction) {
|
|
33599
|
+
await handleSessionSelectionShortcut();
|
|
33600
|
+
continue;
|
|
33601
|
+
}
|
|
33602
|
+
const sessionCommand = parseSessionCommand(prompt);
|
|
33603
|
+
if (sessionCommand) {
|
|
33604
|
+
await handleSessionCommand(sessionCommand);
|
|
33605
|
+
continue;
|
|
33606
|
+
}
|
|
32543
33607
|
if (isCompactCommand(prompt)) {
|
|
32544
33608
|
const result = compactInteractiveHistory(history, config.context.main);
|
|
32545
33609
|
history = result.messages;
|
|
32546
33610
|
store2.prepareForPrompt("waiting for next message");
|
|
32547
33611
|
store2.markHistoryCompacted(result);
|
|
33612
|
+
await persistCurrentConversation();
|
|
32548
33613
|
continue;
|
|
32549
33614
|
}
|
|
32550
33615
|
const mode = resolveAgentMode(config, flags.mode);
|
|
@@ -32696,6 +33761,7 @@ async function runTuiSession(flags, store2) {
|
|
|
32696
33761
|
history = interactiveHistoryFromRunMessages(result.messages);
|
|
32697
33762
|
lastExternalSessionKey = externalSessionKey ?? lastExternalSessionKey;
|
|
32698
33763
|
}
|
|
33764
|
+
await persistCurrentConversation();
|
|
32699
33765
|
} catch (error) {
|
|
32700
33766
|
store2.setStopTask(void 0);
|
|
32701
33767
|
if (activeAbort?.signal.aborted) {
|
|
@@ -32708,6 +33774,7 @@ async function runTuiSession(flags, store2) {
|
|
|
32708
33774
|
store2.setStopTask(void 0);
|
|
32709
33775
|
}
|
|
32710
33776
|
if (store2.exitRequested()) {
|
|
33777
|
+
await persistCurrentConversation();
|
|
32711
33778
|
await saveCurrentSelection();
|
|
32712
33779
|
return 0;
|
|
32713
33780
|
}
|
|
@@ -32716,6 +33783,184 @@ async function runTuiSession(flags, store2) {
|
|
|
32716
33783
|
async function saveCurrentSelection() {
|
|
32717
33784
|
await saveSelection(store2.currentSelection());
|
|
32718
33785
|
}
|
|
33786
|
+
async function handleSessionCommand(command) {
|
|
33787
|
+
if (!conversationsEnabled) {
|
|
33788
|
+
store2.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
|
|
33789
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33790
|
+
return;
|
|
33791
|
+
}
|
|
33792
|
+
if (!currentConversation) {
|
|
33793
|
+
currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
|
|
33794
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
33795
|
+
}
|
|
33796
|
+
if (command.action === "help") {
|
|
33797
|
+
store2.showSystemMessage("Sessions", sessionUsage());
|
|
33798
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33799
|
+
return;
|
|
33800
|
+
}
|
|
33801
|
+
if (command.action === "list") {
|
|
33802
|
+
store2.showSystemMessage("Sessions", sessionListMessage());
|
|
33803
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33804
|
+
return;
|
|
33805
|
+
}
|
|
33806
|
+
if (command.action === "current") {
|
|
33807
|
+
store2.showSystemMessage("Current Session", conversationSummaryLine(currentConversation, sortedConversations().findIndex((item) => item.id === currentConversation?.id), currentConversation.id));
|
|
33808
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33809
|
+
return;
|
|
33810
|
+
}
|
|
33811
|
+
if (command.action === "new") {
|
|
33812
|
+
await persistCurrentConversation();
|
|
33813
|
+
const next = createConversationRecord({ id: createRootSessionId(), cwd, title: command.title });
|
|
33814
|
+
conversationRecords.set(next.id, next);
|
|
33815
|
+
await switchConversation(next);
|
|
33816
|
+
store2.prepareForPrompt(`new session: ${next.title}`);
|
|
33817
|
+
await persistCurrentConversation();
|
|
33818
|
+
return;
|
|
33819
|
+
}
|
|
33820
|
+
if (command.action === "switch") {
|
|
33821
|
+
const target = findConversation(sortedConversations(), command.target);
|
|
33822
|
+
if (!target) {
|
|
33823
|
+
store2.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".
|
|
33824
|
+
|
|
33825
|
+
${sessionListMessage()}`);
|
|
33826
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33827
|
+
return;
|
|
33828
|
+
}
|
|
33829
|
+
await persistCurrentConversation();
|
|
33830
|
+
await switchConversation(target);
|
|
33831
|
+
store2.prepareForPrompt(`switched session: ${target.title}`);
|
|
33832
|
+
await persistCurrentConversation();
|
|
33833
|
+
return;
|
|
33834
|
+
}
|
|
33835
|
+
if (command.action === "delete") {
|
|
33836
|
+
const target = findConversation(sortedConversations(), command.target);
|
|
33837
|
+
if (!target) {
|
|
33838
|
+
store2.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
|
|
33839
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33840
|
+
return;
|
|
33841
|
+
}
|
|
33842
|
+
conversationRecords.delete(target.id);
|
|
33843
|
+
runtimeByConversation.delete(target.id);
|
|
33844
|
+
await deleteConversationRecord(flags.conversationStorageDir, target.id);
|
|
33845
|
+
if (currentConversation?.id === target.id) {
|
|
33846
|
+
const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33847
|
+
conversationRecords.set(next.id, next);
|
|
33848
|
+
await switchConversation(next);
|
|
33849
|
+
}
|
|
33850
|
+
store2.prepareForPrompt(`deleted session: ${target.title}`);
|
|
33851
|
+
await persistCurrentConversation();
|
|
33852
|
+
return;
|
|
33853
|
+
}
|
|
33854
|
+
if (command.action === "rename") {
|
|
33855
|
+
const title = command.title?.trim();
|
|
33856
|
+
if (!title) {
|
|
33857
|
+
store2.showSystemMessage("Sessions", "Usage: /session rename <title>");
|
|
33858
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33859
|
+
return;
|
|
33860
|
+
}
|
|
33861
|
+
currentConversation.title = title;
|
|
33862
|
+
currentConversation.updatedAt = Date.now();
|
|
33863
|
+
store2.prepareForPrompt(`renamed session: ${title}`);
|
|
33864
|
+
await persistCurrentConversation();
|
|
33865
|
+
return;
|
|
33866
|
+
}
|
|
33867
|
+
if (command.action === "clear") {
|
|
33868
|
+
history = [];
|
|
33869
|
+
currentConversation.modelHistory = [];
|
|
33870
|
+
currentConversation.snapshot = void 0;
|
|
33871
|
+
currentConversation.title = "New session";
|
|
33872
|
+
currentConversation.updatedAt = Date.now();
|
|
33873
|
+
store2.clearConversation(currentConversation.title);
|
|
33874
|
+
store2.prepareForPrompt("cleared current conversation");
|
|
33875
|
+
await persistCurrentConversation();
|
|
33876
|
+
}
|
|
33877
|
+
}
|
|
33878
|
+
async function handleSessionSelectionShortcut() {
|
|
33879
|
+
if (!conversationsEnabled) {
|
|
33880
|
+
store2.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
|
|
33881
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33882
|
+
return;
|
|
33883
|
+
}
|
|
33884
|
+
if (!currentConversation) {
|
|
33885
|
+
currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
|
|
33886
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
33887
|
+
}
|
|
33888
|
+
await persistCurrentConversation();
|
|
33889
|
+
const selection = await store2.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
|
|
33890
|
+
if (selection.kind === "cancel") {
|
|
33891
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33892
|
+
return;
|
|
33893
|
+
}
|
|
33894
|
+
if (selection.kind === "exit" || store2.exitRequested()) return;
|
|
33895
|
+
await applyStartupSessionSelection(selection);
|
|
33896
|
+
}
|
|
33897
|
+
function startupSessionOptions() {
|
|
33898
|
+
const records = sortedConversations();
|
|
33899
|
+
const sessionOptions = records.map((record) => ({
|
|
33900
|
+
kind: "session",
|
|
33901
|
+
id: record.id,
|
|
33902
|
+
title: record.title,
|
|
33903
|
+
cwd: record.cwd,
|
|
33904
|
+
status: conversationRuntimeStatus(record, cwd),
|
|
33905
|
+
updatedAt: record.updatedAt,
|
|
33906
|
+
currentWorkspace: path34.resolve(record.cwd) === cwd
|
|
33907
|
+
}));
|
|
33908
|
+
return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
|
|
33909
|
+
}
|
|
33910
|
+
async function applyStartupSessionSelection(selection) {
|
|
33911
|
+
if (selection.kind === "cancel") {
|
|
33912
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33913
|
+
return;
|
|
33914
|
+
}
|
|
33915
|
+
if (selection.kind === "new") {
|
|
33916
|
+
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33917
|
+
conversationRecords.set(next.id, next);
|
|
33918
|
+
await switchConversation(next);
|
|
33919
|
+
store2.prepareForPrompt("new session ready");
|
|
33920
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], next.id);
|
|
33921
|
+
return;
|
|
33922
|
+
}
|
|
33923
|
+
const target = conversationRecords.get(selection.id) ?? findConversation(sortedConversations(), selection.id);
|
|
33924
|
+
if (!target) {
|
|
33925
|
+
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33926
|
+
conversationRecords.set(next.id, next);
|
|
33927
|
+
await switchConversation(next);
|
|
33928
|
+
store2.prepareForPrompt("new session ready");
|
|
33929
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], next.id);
|
|
33930
|
+
return;
|
|
33931
|
+
}
|
|
33932
|
+
await switchConversation(target);
|
|
33933
|
+
store2.prepareForPrompt(`session ready: ${target.title}`);
|
|
33934
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
|
|
33935
|
+
}
|
|
33936
|
+
async function switchConversation(record) {
|
|
33937
|
+
currentConversation = record;
|
|
33938
|
+
rootSessionId = record.id;
|
|
33939
|
+
history = [...record.modelHistory];
|
|
33940
|
+
const runtime = runtimeByConversation.get(record.id) ?? { externalSessions: new ClaudeCodeSessionMap(), lastExternalSessionKey: void 0 };
|
|
33941
|
+
externalSessions = runtime.externalSessions;
|
|
33942
|
+
lastExternalSessionKey = runtime.lastExternalSessionKey;
|
|
33943
|
+
runtimeByConversation.set(record.id, runtime);
|
|
33944
|
+
store2.restoreConversationSnapshot(record.snapshot, { sessionId: record.id, title: record.title, cwd: record.cwd || cwd });
|
|
33945
|
+
}
|
|
33946
|
+
async function persistCurrentConversation() {
|
|
33947
|
+
if (!conversationsEnabled || !currentConversation) return;
|
|
33948
|
+
currentConversation.modelHistory = cleanModelHistory(history);
|
|
33949
|
+
if (currentConversation.title === "New session" && currentConversation.modelHistory.length > 0) currentConversation.title = titleFromHistory(currentConversation.modelHistory);
|
|
33950
|
+
currentConversation.snapshot = store2.conversationSnapshot();
|
|
33951
|
+
currentConversation.updatedAt = Date.now();
|
|
33952
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
33953
|
+
runtimeByConversation.set(currentConversation.id, { externalSessions, lastExternalSessionKey });
|
|
33954
|
+
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], currentConversation.id);
|
|
33955
|
+
}
|
|
33956
|
+
function sortedConversations() {
|
|
33957
|
+
return sortConversationRecords([...conversationRecords.values()], cwd);
|
|
33958
|
+
}
|
|
33959
|
+
function sessionListMessage() {
|
|
33960
|
+
const records = sortedConversations();
|
|
33961
|
+
if (!records.length) return "No saved sessions.";
|
|
33962
|
+
return ["Saved sessions:", ...records.map((record, index) => conversationSummaryLine(record, index, currentConversation?.id)), "", "Use /session switch <number|id|title> to open one."].join("\n");
|
|
33963
|
+
}
|
|
32719
33964
|
async function saveSelection(selection) {
|
|
32720
33965
|
if (!shouldPersistSelection(selection)) return;
|
|
32721
33966
|
const key = preferenceKey(selection);
|
|
@@ -32723,298 +33968,124 @@ async function runTuiSession(flags, store2) {
|
|
|
32723
33968
|
await preferenceStore.save(selection);
|
|
32724
33969
|
savedSelectionKey = key;
|
|
32725
33970
|
}
|
|
32726
|
-
|
|
32727
|
-
|
|
32728
|
-
|
|
32729
|
-
|
|
32730
|
-
|
|
32731
|
-
|
|
32732
|
-
|
|
32733
|
-
|
|
32734
|
-
|
|
32735
|
-
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32739
|
-
|
|
32740
|
-
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32744
|
-
|
|
32745
|
-
|
|
32746
|
-
|
|
32747
|
-
|
|
32748
|
-
|
|
32749
|
-
|
|
32750
|
-
|
|
32751
|
-
|
|
32752
|
-
|
|
32753
|
-
|
|
32754
|
-
|
|
32755
|
-
|
|
32756
|
-
|
|
32757
|
-
|
|
32758
|
-
|
|
32759
|
-
|
|
32760
|
-
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
32761
|
-
},
|
|
32762
|
-
gemini: {
|
|
32763
|
-
type: "openai-compatible",
|
|
32764
|
-
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
32765
|
-
apiKey: "",
|
|
32766
|
-
apiKeyEnv: "GEMINI_API_KEY",
|
|
32767
|
-
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
32768
|
-
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
32769
|
-
},
|
|
32770
|
-
groq: {
|
|
32771
|
-
type: "openai-compatible",
|
|
32772
|
-
baseURL: "https://api.groq.com/openai/v1",
|
|
32773
|
-
apiKey: "",
|
|
32774
|
-
apiKeyEnv: "GROQ_API_KEY",
|
|
32775
|
-
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
32776
|
-
},
|
|
32777
|
-
azure: {
|
|
32778
|
-
type: "openai-compatible",
|
|
32779
|
-
auth: { type: "api-key", header: "api-key" },
|
|
32780
|
-
modelProfiles: [
|
|
32781
|
-
{
|
|
32782
|
-
name: "azure-example",
|
|
32783
|
-
displayName: "Azure example",
|
|
32784
|
-
model: "azure-deployment-name",
|
|
32785
|
-
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
32786
|
-
apiKey: "",
|
|
32787
|
-
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
32788
|
-
}
|
|
32789
|
-
]
|
|
32790
|
-
},
|
|
32791
|
-
lmstudio: {
|
|
32792
|
-
type: "openai-compatible",
|
|
32793
|
-
baseURL: "http://localhost:1234/v1",
|
|
32794
|
-
apiKey: "lm-studio",
|
|
32795
|
-
modelProfiles: [],
|
|
32796
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
32797
|
-
},
|
|
32798
|
-
"ollama-local": {
|
|
32799
|
-
type: "openai-compatible",
|
|
32800
|
-
baseURL: "http://localhost:11434/v1",
|
|
32801
|
-
apiKey: "ollama",
|
|
32802
|
-
modelProfiles: [],
|
|
32803
|
-
quirks: { omitTemperature: true },
|
|
32804
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
32805
|
-
},
|
|
32806
|
-
"ollama-cloud": {
|
|
32807
|
-
type: "ollama",
|
|
32808
|
-
baseURL: "https://ollama.com/api",
|
|
32809
|
-
apiKey: "",
|
|
32810
|
-
apiKeyEnv: "OLLAMA_API_KEY",
|
|
32811
|
-
modelProfiles: [],
|
|
32812
|
-
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
32813
|
-
},
|
|
32814
|
-
llamacpp: {
|
|
32815
|
-
type: "openai-compatible",
|
|
32816
|
-
baseURL: "http://localhost:8080/v1",
|
|
32817
|
-
apiKey: "llama.cpp",
|
|
32818
|
-
modelProfiles: [],
|
|
32819
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
32820
|
-
},
|
|
32821
|
-
vllm: {
|
|
32822
|
-
type: "openai-compatible",
|
|
32823
|
-
baseURL: "http://localhost:8000/v1",
|
|
32824
|
-
apiKey: "vllm",
|
|
32825
|
-
modelProfiles: [],
|
|
32826
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
32827
|
-
},
|
|
32828
|
-
codex: {
|
|
32829
|
-
type: "codex",
|
|
32830
|
-
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
32831
|
-
authStore: "auto",
|
|
32832
|
-
allowApiKeyFallback: false,
|
|
32833
|
-
promptCacheKey: "root-session",
|
|
32834
|
-
catalog: { type: "codex-oauth-models" }
|
|
32835
|
-
},
|
|
32836
|
-
claudecode: {
|
|
32837
|
-
type: "claudecode",
|
|
32838
|
-
runtime: "agent-sdk",
|
|
32839
|
-
cliPath: "~/.local/bin/claude",
|
|
32840
|
-
cwdMode: "session",
|
|
32841
|
-
historyPolicy: "passthrough-resume",
|
|
32842
|
-
onInvalidResume: "fresh",
|
|
32843
|
-
attachmentFallback: "block",
|
|
32844
|
-
allowSubagents: false,
|
|
32845
|
-
sanitizeApiKeyEnv: true,
|
|
32846
|
-
authPreflight: true,
|
|
32847
|
-
useBareMode: false,
|
|
32848
|
-
usageLedgerScope: "process",
|
|
32849
|
-
sessionLock: true,
|
|
32850
|
-
abortPolicy: "record-only",
|
|
32851
|
-
catalog: { type: "claudecode-supported-models" }
|
|
33971
|
+
async function handleConfigAction(action) {
|
|
33972
|
+
const path37 = configMutationPath(flags);
|
|
33973
|
+
try {
|
|
33974
|
+
let message = "Config updated.";
|
|
33975
|
+
let nextSelection = {};
|
|
33976
|
+
if (action.type === "setDefaultProvider") {
|
|
33977
|
+
await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
|
|
33978
|
+
message = `Default provider saved: ${action.provider}`;
|
|
33979
|
+
nextSelection = { provider: action.provider };
|
|
33980
|
+
} else if (action.type === "setDefaultModel") {
|
|
33981
|
+
await setDefaultModelProfile({
|
|
33982
|
+
path: path37,
|
|
33983
|
+
provider: action.provider,
|
|
33984
|
+
profileName: action.profileName,
|
|
33985
|
+
name: action.name,
|
|
33986
|
+
displayName: action.displayName,
|
|
33987
|
+
model: action.model
|
|
33988
|
+
});
|
|
33989
|
+
message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
|
|
33990
|
+
nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
|
|
33991
|
+
} else if (action.type === "addProvider") {
|
|
33992
|
+
await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
|
|
33993
|
+
message = `Provider added: ${action.name}`;
|
|
33994
|
+
nextSelection = { provider: action.name };
|
|
33995
|
+
} else if (action.type === "updateProvider") {
|
|
33996
|
+
await updateProviderSettings({
|
|
33997
|
+
path: path37,
|
|
33998
|
+
provider: action.provider,
|
|
33999
|
+
...action.field === "baseURL" ? { baseURL: action.value } : {},
|
|
34000
|
+
...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
|
|
34001
|
+
...action.field === "authHeader" ? { authHeader: action.value } : {}
|
|
34002
|
+
});
|
|
34003
|
+
message = `Provider ${action.field} saved: ${action.provider}`;
|
|
34004
|
+
nextSelection = { provider: action.provider };
|
|
32852
34005
|
}
|
|
34006
|
+
await reloadConfigSettings(message, nextSelection);
|
|
34007
|
+
} catch (error) {
|
|
34008
|
+
store2.prepareForPrompt("config action failed");
|
|
34009
|
+
store2.showSystemMessage("Config", errorMessage(error));
|
|
34010
|
+
store2.openConfigManager(errorMessage(error));
|
|
32853
34011
|
}
|
|
32854
|
-
};
|
|
32855
|
-
}
|
|
32856
|
-
async function createUserConfig(options2 = {}) {
|
|
32857
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32858
|
-
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
32859
|
-
`;
|
|
32860
|
-
if (options2.print) return { path: filePath, created: false, content };
|
|
32861
|
-
const existed = await exists(filePath);
|
|
32862
|
-
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile16(filePath, "utf8"), existed: true };
|
|
32863
|
-
await writeJsonAtomic(filePath, content);
|
|
32864
|
-
return { path: filePath, created: true, content, existed };
|
|
32865
|
-
}
|
|
32866
|
-
async function addProvider(options2) {
|
|
32867
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32868
|
-
const config = await readConfigObject(filePath);
|
|
32869
|
-
const providers = objectValue(config.providers);
|
|
32870
|
-
const name = options2.name;
|
|
32871
|
-
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
32872
|
-
providers[name] = providerPreset(options2);
|
|
32873
|
-
config.providers = providers;
|
|
32874
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
32875
|
-
`;
|
|
32876
|
-
await writeJsonAtomic(filePath, content);
|
|
32877
|
-
return { path: filePath, created: true, content };
|
|
32878
|
-
}
|
|
32879
|
-
async function addModelProfile(options2) {
|
|
32880
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32881
|
-
const config = await readConfigObject(filePath);
|
|
32882
|
-
const providers = objectValue(config.providers);
|
|
32883
|
-
const provider = objectValue(providers[options2.provider]);
|
|
32884
|
-
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
32885
|
-
const displayName = options2.displayName ?? options2.name;
|
|
32886
|
-
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
32887
|
-
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
32888
|
-
if (existingByName >= 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
32889
|
-
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options2.force) {
|
|
32890
|
-
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options2.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
32891
|
-
}
|
|
32892
|
-
const next = {
|
|
32893
|
-
name: options2.name,
|
|
32894
|
-
displayName,
|
|
32895
|
-
model: options2.model,
|
|
32896
|
-
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
32897
|
-
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
32898
|
-
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
32899
|
-
};
|
|
32900
|
-
if (existingByName >= 0) profiles[existingByName] = next;
|
|
32901
|
-
else profiles.push(next);
|
|
32902
|
-
provider.modelProfiles = profiles;
|
|
32903
|
-
providers[options2.provider] = provider;
|
|
32904
|
-
config.providers = providers;
|
|
32905
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
32906
|
-
`;
|
|
32907
|
-
await writeJsonAtomic(filePath, content);
|
|
32908
|
-
return { path: filePath, created: true, content };
|
|
32909
|
-
}
|
|
32910
|
-
async function updateConfigDefaults(options2) {
|
|
32911
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32912
|
-
const config = await readConfigObject(filePath);
|
|
32913
|
-
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
32914
|
-
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
32915
|
-
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
32916
|
-
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
32917
|
-
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
32918
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
32919
|
-
`;
|
|
32920
|
-
await writeJsonAtomic(filePath, content);
|
|
32921
|
-
return { path: filePath, created: true, content };
|
|
32922
|
-
}
|
|
32923
|
-
async function readConfigObject(filePath) {
|
|
32924
|
-
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
32925
|
-
const raw = JSON.parse(await readFile16(filePath, "utf8"));
|
|
32926
|
-
raw.version ??= 2;
|
|
32927
|
-
raw.providers = objectValue(raw.providers);
|
|
32928
|
-
return raw;
|
|
32929
|
-
}
|
|
32930
|
-
function providerPreset(options2) {
|
|
32931
|
-
const preset = options2.preset ?? options2.name;
|
|
32932
|
-
const auth = apiKeyAuthFields(options2);
|
|
32933
|
-
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
32934
|
-
if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options2, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
32935
|
-
if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options2, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
32936
|
-
if (preset === "gemini") {
|
|
32937
|
-
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
32938
|
-
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options2, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
32939
34012
|
}
|
|
32940
|
-
|
|
32941
|
-
|
|
32942
|
-
|
|
32943
|
-
|
|
32944
|
-
|
|
32945
|
-
|
|
32946
|
-
|
|
32947
|
-
|
|
32948
|
-
|
|
32949
|
-
|
|
32950
|
-
|
|
32951
|
-
|
|
32952
|
-
|
|
32953
|
-
|
|
32954
|
-
|
|
32955
|
-
|
|
32956
|
-
|
|
32957
|
-
|
|
32958
|
-
|
|
32959
|
-
|
|
32960
|
-
|
|
32961
|
-
|
|
32962
|
-
|
|
32963
|
-
|
|
32964
|
-
|
|
32965
|
-
|
|
32966
|
-
const baseURL = options2.baseURL;
|
|
32967
|
-
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
32968
|
-
return {
|
|
32969
|
-
type: "openai-compatible",
|
|
32970
|
-
baseURL,
|
|
32971
|
-
...auth,
|
|
32972
|
-
...openAIAuth,
|
|
32973
|
-
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
32974
|
-
};
|
|
34013
|
+
async function reloadConfigSettings(message, selectionFlags = {}) {
|
|
34014
|
+
loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
34015
|
+
config = flags.context ? mergeConfig(loadedConfig2, {
|
|
34016
|
+
context: {
|
|
34017
|
+
main: flags.context
|
|
34018
|
+
}
|
|
34019
|
+
}) : loadedConfig2;
|
|
34020
|
+
const currentSelection = store2.currentSelection();
|
|
34021
|
+
const selection = createInteractiveModelSelection(
|
|
34022
|
+
config,
|
|
34023
|
+
selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
|
|
34024
|
+
);
|
|
34025
|
+
store2.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
34026
|
+
agent: store2.currentAgentName(),
|
|
34027
|
+
cwd,
|
|
34028
|
+
configPath: configMutationPath(flags),
|
|
34029
|
+
configDefaultProvider: config.defaultProvider,
|
|
34030
|
+
configMessage: message,
|
|
34031
|
+
agentOptions,
|
|
34032
|
+
contextEfficiency: {
|
|
34033
|
+
maxContextTokens: config.context.main.maxContextTokens,
|
|
34034
|
+
compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
|
|
34035
|
+
compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
|
|
34036
|
+
}
|
|
34037
|
+
});
|
|
34038
|
+
store2.openConfigManager(message);
|
|
32975
34039
|
}
|
|
32976
|
-
throw new Error(`Unknown provider preset: ${preset}`);
|
|
32977
34040
|
}
|
|
32978
|
-
function
|
|
32979
|
-
const
|
|
32980
|
-
|
|
32981
|
-
|
|
32982
|
-
|
|
32983
|
-
}
|
|
32984
|
-
}
|
|
32985
|
-
|
|
32986
|
-
await mkdir14(path33.dirname(filePath), { recursive: true, mode: 448 });
|
|
32987
|
-
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32988
|
-
await writeFile13(temp, content, { mode: 384 });
|
|
32989
|
-
await rename6(temp, filePath);
|
|
32990
|
-
await chmod3(filePath, 384).catch(() => void 0);
|
|
32991
|
-
}
|
|
32992
|
-
function detectDefaultProvider() {
|
|
32993
|
-
if (process.env.OPENAI_API_KEY) return "openai";
|
|
32994
|
-
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
32995
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
32996
|
-
if (process.env.GROQ_API_KEY) return "groq";
|
|
32997
|
-
return "openai";
|
|
34041
|
+
async function ensureTuiConfigExists(flags) {
|
|
34042
|
+
const target = configMutationPath(flags);
|
|
34043
|
+
if (!flags.configPath) {
|
|
34044
|
+
const jsondPath = target.replace(/\.json$/i, ".jsond");
|
|
34045
|
+
if (existsSync4(target) || existsSync4(jsondPath)) return { path: target, created: false };
|
|
34046
|
+
}
|
|
34047
|
+
const result = await createUserConfig({ path: target });
|
|
34048
|
+
return { path: result.path, created: result.created };
|
|
32998
34049
|
}
|
|
32999
|
-
function
|
|
33000
|
-
return
|
|
34050
|
+
function configMutationPath(flags) {
|
|
34051
|
+
return flags.configPath ? path34.resolve(flags.configPath) : defaultUserConfigPath();
|
|
33001
34052
|
}
|
|
33002
|
-
function
|
|
33003
|
-
return
|
|
34053
|
+
function isGoalStateClearingAction(action) {
|
|
34054
|
+
return action === "status" || action === "pause" || action === "resume";
|
|
33004
34055
|
}
|
|
33005
|
-
async function
|
|
33006
|
-
|
|
33007
|
-
|
|
33008
|
-
|
|
33009
|
-
|
|
33010
|
-
|
|
33011
|
-
|
|
34056
|
+
async function initializeConversationState(cwd, flags) {
|
|
34057
|
+
const loaded = await loadConversationRecords(flags.conversationStorageDir);
|
|
34058
|
+
const records = new Map(loaded.records.map((record) => [record.id, record]));
|
|
34059
|
+
const shouldResumeSelected = !flags.prompt.trim();
|
|
34060
|
+
const orderedRecords = sortConversationRecords([...records.values()], cwd);
|
|
34061
|
+
const preferred = (flags.sessionId ? findConversation(orderedRecords, flags.sessionId) : void 0) ?? (shouldResumeSelected && loaded.selectedSessionId ? findConversation(orderedRecords, loaded.selectedSessionId) : void 0) ?? (shouldResumeSelected ? orderedRecords[0] : void 0);
|
|
34062
|
+
const current = preferred ?? (flags.prompt.trim() || flags.sessionId || flags.initialHistory?.length ? createConversationRecord({
|
|
34063
|
+
id: flags.sessionId ?? createRootSessionId(),
|
|
34064
|
+
cwd,
|
|
34065
|
+
history: flags.initialHistory
|
|
34066
|
+
}) : void 0);
|
|
34067
|
+
if (current && flags.initialHistory?.length) current.modelHistory = cleanModelHistory(flags.initialHistory);
|
|
34068
|
+
if (current) records.set(current.id, current);
|
|
34069
|
+
return { records, current };
|
|
33012
34070
|
}
|
|
33013
|
-
function
|
|
33014
|
-
return
|
|
34071
|
+
function sessionUsage() {
|
|
34072
|
+
return [
|
|
34073
|
+
"Usage:",
|
|
34074
|
+
" /session list",
|
|
34075
|
+
" /session new [title]",
|
|
34076
|
+
" /session switch <number|id|title>",
|
|
34077
|
+
" /session rename <title>",
|
|
34078
|
+
" /session delete <number|id|title>",
|
|
34079
|
+
" /session clear",
|
|
34080
|
+
"",
|
|
34081
|
+
"Aliases: /sessions, /conversation, /conversations, /conv"
|
|
34082
|
+
].join("\n");
|
|
33015
34083
|
}
|
|
33016
34084
|
|
|
33017
34085
|
// src/config-watcher.ts
|
|
34086
|
+
import { EventEmitter } from "node:events";
|
|
34087
|
+
import fs11 from "node:fs";
|
|
34088
|
+
import path35 from "node:path";
|
|
33018
34089
|
var ConfigWatcher = class extends EventEmitter {
|
|
33019
34090
|
#filePath;
|
|
33020
34091
|
#debounceMs;
|
|
@@ -33036,9 +34107,9 @@ var ConfigWatcher = class extends EventEmitter {
|
|
|
33036
34107
|
this.#started = true;
|
|
33037
34108
|
this.#lastSeen = this.#snapshot();
|
|
33038
34109
|
try {
|
|
33039
|
-
fs11.mkdirSync(
|
|
33040
|
-
this.#watcher = fs11.watch(
|
|
33041
|
-
if (filename && filename !==
|
|
34110
|
+
fs11.mkdirSync(path35.dirname(this.#filePath), { recursive: true, mode: 448 });
|
|
34111
|
+
this.#watcher = fs11.watch(path35.dirname(this.#filePath), { persistent: false }, (_eventType, filename) => {
|
|
34112
|
+
if (filename && filename !== path35.basename(this.#filePath)) return;
|
|
33042
34113
|
this.#schedule();
|
|
33043
34114
|
});
|
|
33044
34115
|
this.#watcher.on("error", () => void 0);
|
|
@@ -33103,6 +34174,20 @@ if (process.argv.includes("--config-template")) {
|
|
|
33103
34174
|
`);
|
|
33104
34175
|
process.exit(0);
|
|
33105
34176
|
}
|
|
34177
|
+
if (process.argv.includes("--config-read")) {
|
|
34178
|
+
await readConfigMeta(jsonArg("--config-read")).then(
|
|
34179
|
+
(result) => {
|
|
34180
|
+
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}
|
|
34181
|
+
`);
|
|
34182
|
+
process.exit(0);
|
|
34183
|
+
},
|
|
34184
|
+
(error) => {
|
|
34185
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34186
|
+
`);
|
|
34187
|
+
process.exit(1);
|
|
34188
|
+
}
|
|
34189
|
+
);
|
|
34190
|
+
}
|
|
33106
34191
|
if (process.argv.includes("--config-init")) {
|
|
33107
34192
|
const force = process.argv.includes("--force");
|
|
33108
34193
|
await createUserConfig({ force }).then(
|
|
@@ -33160,6 +34245,34 @@ if (process.argv.includes("--config-set-defaults")) {
|
|
|
33160
34245
|
}
|
|
33161
34246
|
);
|
|
33162
34247
|
}
|
|
34248
|
+
if (process.argv.includes("--config-set-default-model")) {
|
|
34249
|
+
await setDefaultModelProfile(jsonArg("--config-set-default-model")).then(
|
|
34250
|
+
(result) => {
|
|
34251
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
34252
|
+
`);
|
|
34253
|
+
process.exit(0);
|
|
34254
|
+
},
|
|
34255
|
+
(error) => {
|
|
34256
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34257
|
+
`);
|
|
34258
|
+
process.exit(1);
|
|
34259
|
+
}
|
|
34260
|
+
);
|
|
34261
|
+
}
|
|
34262
|
+
if (process.argv.includes("--config-update-provider")) {
|
|
34263
|
+
await updateProviderSettings(jsonArg("--config-update-provider")).then(
|
|
34264
|
+
(result) => {
|
|
34265
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
34266
|
+
`);
|
|
34267
|
+
process.exit(0);
|
|
34268
|
+
},
|
|
34269
|
+
(error) => {
|
|
34270
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34271
|
+
`);
|
|
34272
|
+
process.exit(1);
|
|
34273
|
+
}
|
|
34274
|
+
);
|
|
34275
|
+
}
|
|
33163
34276
|
if (process.argv.includes("--provider-auth-check")) {
|
|
33164
34277
|
await checkProviderAuth(jsonArg("--provider-auth-check")).then(
|
|
33165
34278
|
(result) => {
|
|
@@ -33306,14 +34419,14 @@ async function undoDiffAction(actionId) {
|
|
|
33306
34419
|
if (!action) throw new Error("No diff action is available to undo.");
|
|
33307
34420
|
if (!canUndoDiffAction(diffState, action.id)) throw new Error("Only the latest change for a file can be undone.");
|
|
33308
34421
|
if (!hasBeforeSnapshot(action)) throw new Error("This diff was not recorded with enough content to undo safely.");
|
|
33309
|
-
const target =
|
|
33310
|
-
const relative =
|
|
33311
|
-
if (relative.startsWith("..") ||
|
|
34422
|
+
const target = path36.resolve(options.cwd, action.path);
|
|
34423
|
+
const relative = path36.relative(options.cwd, target);
|
|
34424
|
+
if (relative.startsWith("..") || path36.isAbsolute(relative)) throw new Error("Refusing to undo a file outside the workspace.");
|
|
33312
34425
|
if (!action.beforeExists) {
|
|
33313
|
-
await
|
|
34426
|
+
await rm5(target, { force: true });
|
|
33314
34427
|
} else {
|
|
33315
|
-
await
|
|
33316
|
-
await
|
|
34428
|
+
await mkdir16(path36.dirname(target), { recursive: true });
|
|
34429
|
+
await writeFile15(target, action.beforeContent ?? "", "utf8");
|
|
33317
34430
|
}
|
|
33318
34431
|
markDiffActionUndone(action.id);
|
|
33319
34432
|
send({ type: "diffUndoCompleted", actionId: action.id, path: action.path });
|
|
@@ -33734,30 +34847,43 @@ function setApiKey(providerName, apiKey, opts = {}) {
|
|
|
33734
34847
|
}
|
|
33735
34848
|
async function sendConfigMeta() {
|
|
33736
34849
|
if (!loadedConfig) return;
|
|
34850
|
+
send({ type: "configMeta", config: await buildConfigMeta(loadedConfig) });
|
|
34851
|
+
}
|
|
34852
|
+
async function readConfigMeta(options2) {
|
|
34853
|
+
const cwd = typeof options2.cwd === "string" && options2.cwd.trim() ? options2.cwd : process.cwd();
|
|
34854
|
+
const configPath = typeof options2.configPath === "string" && options2.configPath.trim() ? options2.configPath : defaultUserConfigPath();
|
|
34855
|
+
let created = false;
|
|
34856
|
+
if (options2.create && !existsSync5(configPath)) {
|
|
34857
|
+
const result = await createUserConfig({ path: configPath });
|
|
34858
|
+
created = result.created;
|
|
34859
|
+
}
|
|
34860
|
+
const config = await loadConfig({ cwd, configPath: options2.configPath });
|
|
34861
|
+
return { path: configPath, created, config: await buildConfigMeta(config) };
|
|
34862
|
+
}
|
|
34863
|
+
async function buildConfigMeta(config) {
|
|
33737
34864
|
const registry = new AgentRegistry();
|
|
33738
|
-
for (const agentDefinition of Object.values(
|
|
33739
|
-
const providerEntries = sortProviderNames(Object.keys(
|
|
34865
|
+
for (const agentDefinition of Object.values(config.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
|
|
34866
|
+
const providerEntries = sortProviderNames(Object.keys(config.providers)).map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
|
|
33740
34867
|
const providerOrder = new Map(providerEntries.map(([name], index) => [name, index]));
|
|
33741
34868
|
const providers = (await Promise.all(providerEntries.map(([name, provider]) => providerMeta(name, provider)))).sort((left, right) => {
|
|
33742
34869
|
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
33743
34870
|
return (providerOrder.get(left.name) ?? 0) - (providerOrder.get(right.name) ?? 0);
|
|
33744
34871
|
});
|
|
33745
|
-
|
|
33746
|
-
|
|
33747
|
-
|
|
33748
|
-
|
|
33749
|
-
|
|
33750
|
-
|
|
33751
|
-
|
|
33752
|
-
|
|
33753
|
-
|
|
33754
|
-
|
|
33755
|
-
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
33756
|
-
}
|
|
33757
|
-
});
|
|
34872
|
+
return {
|
|
34873
|
+
defaultProvider: config.defaultProvider,
|
|
34874
|
+
defaultAgent: config.defaultAgent,
|
|
34875
|
+
context: {
|
|
34876
|
+
main: config.context?.main
|
|
34877
|
+
},
|
|
34878
|
+
providerOrder: providers.map((provider) => provider.name),
|
|
34879
|
+
providers,
|
|
34880
|
+
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
34881
|
+
};
|
|
33758
34882
|
}
|
|
33759
34883
|
async function providerMeta(name, provider) {
|
|
33760
34884
|
const apiKeyEnv = typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0;
|
|
34885
|
+
const configuredProfiles = configuredModelProfiles(provider);
|
|
34886
|
+
const auth = sanitizeProviderAuth2(provider.auth);
|
|
33761
34887
|
const catalog = await listProviderModelCatalog(name, provider).catch((error) => ({ status: "unavailable", providerName: name, models: [], source: "fallback", message: errorMessage4(error) }));
|
|
33762
34888
|
const available = providerCatalogAvailable2(catalog);
|
|
33763
34889
|
const modelProfiles = catalog.models.length ? catalog.models.map((model) => ({
|
|
@@ -33790,7 +34916,16 @@ async function providerMeta(name, provider) {
|
|
|
33790
34916
|
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel2(defaultModel),
|
|
33791
34917
|
models,
|
|
33792
34918
|
modelProfiles: labeledProfiles,
|
|
34919
|
+
configuredModelProfiles: configuredProfiles,
|
|
34920
|
+
configured: {
|
|
34921
|
+
baseURL: provider.baseURL,
|
|
34922
|
+
apiKeyEnv,
|
|
34923
|
+
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
34924
|
+
auth,
|
|
34925
|
+
hasInlineApiKey: typeof provider.apiKey === "string" && provider.apiKey.length > 0
|
|
34926
|
+
},
|
|
33793
34927
|
baseURL: provider.baseURL,
|
|
34928
|
+
auth,
|
|
33794
34929
|
apiKeyEnv,
|
|
33795
34930
|
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
33796
34931
|
catalog: { status: catalog.status, source: catalog.source, message: catalog.message },
|
|
@@ -33799,6 +34934,26 @@ async function providerMeta(name, provider) {
|
|
|
33799
34934
|
requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic" || provider.type === "ollama"
|
|
33800
34935
|
};
|
|
33801
34936
|
}
|
|
34937
|
+
function configuredModelProfiles(provider) {
|
|
34938
|
+
if (!Array.isArray(provider.modelProfiles)) return [];
|
|
34939
|
+
return provider.modelProfiles.filter((profile) => profile && typeof profile === "object" && typeof profile.model === "string" && profile.model.trim()).map((profile, index) => {
|
|
34940
|
+
const name = typeof profile.name === "string" && profile.name.trim() ? profile.name.trim() : typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : profile.model.trim();
|
|
34941
|
+
const displayName = typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : name;
|
|
34942
|
+
return {
|
|
34943
|
+
name,
|
|
34944
|
+
displayName,
|
|
34945
|
+
model: profile.model.trim(),
|
|
34946
|
+
description: typeof profile.description === "string" ? profile.description : void 0,
|
|
34947
|
+
baseURL: typeof profile.baseURL === "string" ? profile.baseURL : void 0,
|
|
34948
|
+
apiKeyEnv: typeof profile.apiKeyEnv === "string" ? profile.apiKeyEnv : void 0,
|
|
34949
|
+
apiKeyEnvAliases: asStringArray(profile.apiKeyEnvAliases),
|
|
34950
|
+
auth: sanitizeProviderAuth2(profile.auth),
|
|
34951
|
+
hasInlineApiKey: typeof profile.apiKey === "string" && profile.apiKey.length > 0,
|
|
34952
|
+
source: "config",
|
|
34953
|
+
isDefault: index === 0
|
|
34954
|
+
};
|
|
34955
|
+
});
|
|
34956
|
+
}
|
|
33802
34957
|
function providerCatalogAvailable2(catalog) {
|
|
33803
34958
|
return catalog?.status === "ready" && Array.isArray(catalog.models) && catalog.models.length > 0;
|
|
33804
34959
|
}
|
|
@@ -33808,6 +34963,12 @@ function unavailableLabel2(value) {
|
|
|
33808
34963
|
function asStringArray(value) {
|
|
33809
34964
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
33810
34965
|
}
|
|
34966
|
+
function sanitizeProviderAuth2(auth) {
|
|
34967
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
34968
|
+
const type = typeof auth.type === "string" ? auth.type : void 0;
|
|
34969
|
+
const header = typeof auth.header === "string" ? auth.header : void 0;
|
|
34970
|
+
return type || header ? { type, header } : void 0;
|
|
34971
|
+
}
|
|
33811
34972
|
function contextOptions(value) {
|
|
33812
34973
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
33813
34974
|
const context = {};
|