@wrongstack/cli 0.89.1 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2119 -874
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool,
|
|
3
|
-
import * as path8 from 'path';
|
|
4
|
-
import { join } from 'path';
|
|
2
|
+
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError } from '@wrongstack/core';
|
|
5
3
|
import * as fsp4 from 'fs/promises';
|
|
6
4
|
import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
|
|
5
|
+
import * as path8 from 'path';
|
|
6
|
+
import { join } from 'path';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os2 from 'os';
|
|
9
9
|
import os2__default from 'os';
|
|
@@ -15,10 +15,10 @@ import { spawn } from 'child_process';
|
|
|
15
15
|
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
16
16
|
import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
|
|
17
17
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
18
|
-
import { builtinToolsPack, rememberTool, forgetTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
|
|
18
|
+
import { builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
20
|
import * as readline from 'readline';
|
|
21
|
-
import * as
|
|
21
|
+
import * as fs13 from 'fs';
|
|
22
22
|
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
23
23
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
24
24
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
@@ -28,9 +28,7 @@ import { ToolExecutor } from '@wrongstack/core/execution';
|
|
|
28
28
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
29
29
|
|
|
30
30
|
var __defProp = Object.defineProperty;
|
|
31
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
32
31
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
33
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
34
32
|
var __esm = (fn, res) => function __init() {
|
|
35
33
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
36
34
|
};
|
|
@@ -38,15 +36,102 @@ var __export = (target, all) => {
|
|
|
38
36
|
for (var name in all)
|
|
39
37
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
40
38
|
};
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
45
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
39
|
+
function normalizeKeys(cfg) {
|
|
40
|
+
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
41
|
+
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
46
42
|
}
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
|
|
43
|
+
if (typeof cfg.apiKey === "string" && cfg.apiKey.length > 0) {
|
|
44
|
+
return [{ label: "default", apiKey: cfg.apiKey, createdAt: "" }];
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
function writeKeysBack(cfg, keys) {
|
|
49
|
+
if (keys.length === 0) {
|
|
50
|
+
delete cfg.apiKeys;
|
|
51
|
+
delete cfg.apiKey;
|
|
52
|
+
delete cfg.activeKey;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
cfg.apiKeys = keys;
|
|
56
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
|
|
57
|
+
cfg.apiKey = active.apiKey;
|
|
58
|
+
if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
|
|
59
|
+
cfg.activeKey = active.label;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function activeLabel(cfg, keys) {
|
|
63
|
+
if (cfg.activeKey && keys.some((k) => k.label === cfg.activeKey)) return cfg.activeKey;
|
|
64
|
+
return keys[0]?.label;
|
|
65
|
+
}
|
|
66
|
+
function maskedKey(key) {
|
|
67
|
+
if (!key) return color.dim("\u2014");
|
|
68
|
+
if (key.length <= 8) return color.dim("\u2022".repeat(key.length));
|
|
69
|
+
const head = key.slice(0, 4);
|
|
70
|
+
const tail = key.slice(-4);
|
|
71
|
+
return `${color.dim(head + "\u2026")}${tail}`;
|
|
72
|
+
}
|
|
73
|
+
function nowIso() {
|
|
74
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
75
|
+
}
|
|
76
|
+
async function loadConfigProviders(configPath2, vault, opts) {
|
|
77
|
+
const warn = opts?.warn;
|
|
78
|
+
let raw;
|
|
79
|
+
try {
|
|
80
|
+
raw = await fsp4.readFile(configPath2, "utf8");
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err.code !== "ENOENT") {
|
|
83
|
+
warn?.(`Could not read ${configPath2}: ${err.message}. Treating as empty.`);
|
|
84
|
+
}
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(raw);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
warn?.(`Config at ${configPath2} is not valid JSON: ${err.message}`);
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
95
|
+
return decrypted.providers ?? {};
|
|
96
|
+
}
|
|
97
|
+
async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
98
|
+
let raw;
|
|
99
|
+
let fileExists = true;
|
|
100
|
+
try {
|
|
101
|
+
raw = await fsp4.readFile(configPath2, "utf8");
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (err.code !== "ENOENT") {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Refusing to mutate ${configPath2}: ${err.message}`,
|
|
106
|
+
{ cause: err }
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
fileExists = false;
|
|
110
|
+
raw = "{}";
|
|
111
|
+
}
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(raw);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (fileExists) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
|
|
119
|
+
{ cause: err }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
parsed = {};
|
|
123
|
+
}
|
|
124
|
+
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
125
|
+
const providers = decrypted.providers ?? {};
|
|
126
|
+
mutator(providers);
|
|
127
|
+
decrypted.providers = providers;
|
|
128
|
+
const encrypted = encryptConfigSecrets(decrypted, vault);
|
|
129
|
+
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
130
|
+
}
|
|
131
|
+
var init_provider_config_utils = __esm({
|
|
132
|
+
"src/provider-config-utils.ts"() {
|
|
133
|
+
}
|
|
134
|
+
});
|
|
50
135
|
function getSessionState(ctx) {
|
|
51
136
|
if (!ctx) return sddState;
|
|
52
137
|
let state = ctx.meta[SDD_META_KEY];
|
|
@@ -132,12 +217,6 @@ var init_state = __esm({
|
|
|
132
217
|
sddState = new SDDState();
|
|
133
218
|
}
|
|
134
219
|
});
|
|
135
|
-
function expectDefined3(value) {
|
|
136
|
-
if (value === null || value === void 0) {
|
|
137
|
-
throw new Error("Expected value to be defined");
|
|
138
|
-
}
|
|
139
|
-
return value;
|
|
140
|
-
}
|
|
141
220
|
function formatElapsed(ms) {
|
|
142
221
|
if (ms < 1e3) return `${ms}ms`;
|
|
143
222
|
const s = Math.floor(ms / 1e3);
|
|
@@ -201,7 +280,7 @@ function getCurrentTask() {
|
|
|
201
280
|
if (!tracker) return null;
|
|
202
281
|
const nodes = tracker.getAllNodes({ status: ["in_progress"] });
|
|
203
282
|
if (nodes.length === 0) return null;
|
|
204
|
-
const n =
|
|
283
|
+
const n = expectDefined(nodes[0]);
|
|
205
284
|
return { id: n.id, title: n.title, description: n.description, priority: n.priority, estimateHours: n.estimateHours ?? 0, tags: n.tags ?? [], startedAt: n.startedAt };
|
|
206
285
|
}
|
|
207
286
|
function advanceToNextTask() {
|
|
@@ -240,7 +319,7 @@ function renderTaskListWithProgress() {
|
|
|
240
319
|
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
241
320
|
});
|
|
242
321
|
for (let i = 0; i < sorted.length; i++) {
|
|
243
|
-
const n =
|
|
322
|
+
const n = expectDefined(sorted[i]);
|
|
244
323
|
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
245
324
|
const title = n.title.length > 50 ? n.title.slice(0, 49) + "\u2026" : n.title;
|
|
246
325
|
let elapsed = "";
|
|
@@ -254,7 +333,7 @@ function getCurrentExecutingContext() {
|
|
|
254
333
|
if (!tracker) return null;
|
|
255
334
|
const nodes = tracker.getAllNodes({ status: ["in_progress"] });
|
|
256
335
|
if (nodes.length === 0) return null;
|
|
257
|
-
const n =
|
|
336
|
+
const n = expectDefined(nodes[0]);
|
|
258
337
|
const elapsed = n.startedAt ? ` \xB7 elapsed: ${formatElapsed(Date.now() - n.startedAt)}` : "";
|
|
259
338
|
const progress = tracker.getProgress();
|
|
260
339
|
return [
|
|
@@ -562,12 +641,6 @@ __export(sdd_exports, {
|
|
|
562
641
|
trySaveSpecFromAIOutput: () => trySaveSpecFromAIOutput,
|
|
563
642
|
trySaveTasksFromAIOutput: () => trySaveTasksFromAIOutput
|
|
564
643
|
});
|
|
565
|
-
function expectDefined4(value) {
|
|
566
|
-
if (value === null || value === void 0) {
|
|
567
|
-
throw new Error("Expected value to be defined");
|
|
568
|
-
}
|
|
569
|
-
return value;
|
|
570
|
-
}
|
|
571
644
|
function getTaskTracker() {
|
|
572
645
|
return getTaskTrackerExport();
|
|
573
646
|
}
|
|
@@ -633,7 +706,7 @@ function buildSddCommand(opts) {
|
|
|
633
706
|
}));
|
|
634
707
|
sddState.setSessionStartTime(Date.now());
|
|
635
708
|
sddState.setPhaseStartTime(Date.now());
|
|
636
|
-
const builder =
|
|
709
|
+
const builder = expectDefined(sddState.getBuilder());
|
|
637
710
|
builder.startSession(title);
|
|
638
711
|
const aiPrompt = builder.getAIPrompt();
|
|
639
712
|
return {
|
|
@@ -875,7 +948,7 @@ Start executing the tasks one by one.`
|
|
|
875
948
|
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
876
949
|
});
|
|
877
950
|
for (let i = 0; i < sorted.length; i++) {
|
|
878
|
-
const n =
|
|
951
|
+
const n = expectDefined(sorted[i]);
|
|
879
952
|
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
880
953
|
const num = `${i + 1}`.padStart(3);
|
|
881
954
|
const prio = n.priority.slice(0, 4).padEnd(5);
|
|
@@ -883,7 +956,7 @@ Start executing the tasks one by one.`
|
|
|
883
956
|
const elapsed = n.status === "in_progress" && n.startedAt ? ` (${formatElapsed(Date.now() - n.startedAt)})` : "";
|
|
884
957
|
lines.push(` ${num} ${status} ${prio} ${title}${elapsed}`);
|
|
885
958
|
if (n.description && n.status !== "completed") {
|
|
886
|
-
const first =
|
|
959
|
+
const first = expectDefined(n.description.split("\n")[0]);
|
|
887
960
|
const truncated = first.length > 42 ? first.slice(0, 41) + "\u2026" : first;
|
|
888
961
|
lines.push(` \u21B3 ${truncated}`);
|
|
889
962
|
}
|
|
@@ -1050,7 +1123,7 @@ Start executing the tasks one by one.`
|
|
|
1050
1123
|
if (completed.length === 0) {
|
|
1051
1124
|
return { message: "No completed tasks to undo." };
|
|
1052
1125
|
}
|
|
1053
|
-
const last =
|
|
1126
|
+
const last = expectDefined(completed[completed.length - 1]);
|
|
1054
1127
|
undoTracker.updateNodeStatus(last.id, "pending");
|
|
1055
1128
|
const progress = undoTracker.getProgress();
|
|
1056
1129
|
return {
|
|
@@ -1100,7 +1173,7 @@ Start executing the tasks one by one.`
|
|
|
1100
1173
|
` \u{1F504} ${next.title}`
|
|
1101
1174
|
];
|
|
1102
1175
|
if (next.description) {
|
|
1103
|
-
const first =
|
|
1176
|
+
const first = expectDefined(next.description.split("\n")[0]);
|
|
1104
1177
|
lines.push(` \u21B3 ${first}`);
|
|
1105
1178
|
}
|
|
1106
1179
|
const taskElapsed = next.startedAt ? ` \u23F1 ${formatElapsed(Date.now() - next.startedAt)}` : "";
|
|
@@ -1212,7 +1285,7 @@ Start executing the tasks one by one.`
|
|
|
1212
1285
|
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
1213
1286
|
});
|
|
1214
1287
|
for (let i = 0; i < sorted2.length; i++) {
|
|
1215
|
-
const n =
|
|
1288
|
+
const n = expectDefined(sorted2[i]);
|
|
1216
1289
|
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
1217
1290
|
lines2.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
|
|
1218
1291
|
}
|
|
@@ -1237,7 +1310,7 @@ Start executing the tasks one by one.`
|
|
|
1237
1310
|
return (order[a.status] ?? 6) - (order[b.status] ?? 6);
|
|
1238
1311
|
});
|
|
1239
1312
|
for (let i = 0; i < sorted.length; i++) {
|
|
1240
|
-
const n =
|
|
1313
|
+
const n = expectDefined(sorted[i]);
|
|
1241
1314
|
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
|
|
1242
1315
|
lines.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
|
|
1243
1316
|
}
|
|
@@ -1285,7 +1358,7 @@ Start executing the tasks one by one.`
|
|
|
1285
1358
|
maxQuestions: 10,
|
|
1286
1359
|
sessionPath
|
|
1287
1360
|
}));
|
|
1288
|
-
const resumeBuilder =
|
|
1361
|
+
const resumeBuilder = expectDefined(sddState.getBuilder());
|
|
1289
1362
|
const loaded = await resumeBuilder.loadSession();
|
|
1290
1363
|
if (!loaded) {
|
|
1291
1364
|
sddState.setBuilder(null);
|
|
@@ -1519,108 +1592,6 @@ var init_sdd = __esm({
|
|
|
1519
1592
|
init_rendering();
|
|
1520
1593
|
}
|
|
1521
1594
|
});
|
|
1522
|
-
function expectDefined7(value) {
|
|
1523
|
-
if (value === null || value === void 0) {
|
|
1524
|
-
throw new Error("Expected value to be defined");
|
|
1525
|
-
}
|
|
1526
|
-
return value;
|
|
1527
|
-
}
|
|
1528
|
-
function normalizeKeys(cfg) {
|
|
1529
|
-
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
1530
|
-
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
1531
|
-
}
|
|
1532
|
-
if (typeof cfg.apiKey === "string" && cfg.apiKey.length > 0) {
|
|
1533
|
-
return [{ label: "default", apiKey: cfg.apiKey, createdAt: "" }];
|
|
1534
|
-
}
|
|
1535
|
-
return [];
|
|
1536
|
-
}
|
|
1537
|
-
function writeKeysBack(cfg, keys) {
|
|
1538
|
-
if (keys.length === 0) {
|
|
1539
|
-
delete cfg.apiKeys;
|
|
1540
|
-
delete cfg.apiKey;
|
|
1541
|
-
delete cfg.activeKey;
|
|
1542
|
-
return;
|
|
1543
|
-
}
|
|
1544
|
-
cfg.apiKeys = keys;
|
|
1545
|
-
const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined7(keys[0]);
|
|
1546
|
-
cfg.apiKey = active.apiKey;
|
|
1547
|
-
if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
|
|
1548
|
-
cfg.activeKey = active.label;
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
function activeLabel(cfg, keys) {
|
|
1552
|
-
if (cfg.activeKey && keys.some((k) => k.label === cfg.activeKey)) return cfg.activeKey;
|
|
1553
|
-
return keys[0]?.label;
|
|
1554
|
-
}
|
|
1555
|
-
function maskedKey(key) {
|
|
1556
|
-
if (!key) return color.dim("\u2014");
|
|
1557
|
-
if (key.length <= 8) return color.dim("\u2022".repeat(key.length));
|
|
1558
|
-
const head = key.slice(0, 4);
|
|
1559
|
-
const tail = key.slice(-4);
|
|
1560
|
-
return `${color.dim(head + "\u2026")}${tail}`;
|
|
1561
|
-
}
|
|
1562
|
-
function nowIso() {
|
|
1563
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1564
|
-
}
|
|
1565
|
-
async function loadConfigProviders(configPath2, vault, opts) {
|
|
1566
|
-
const warn = opts?.warn;
|
|
1567
|
-
let raw;
|
|
1568
|
-
try {
|
|
1569
|
-
raw = await fsp4.readFile(configPath2, "utf8");
|
|
1570
|
-
} catch (err) {
|
|
1571
|
-
if (err.code !== "ENOENT") {
|
|
1572
|
-
warn?.(`Could not read ${configPath2}: ${err.message}. Treating as empty.`);
|
|
1573
|
-
}
|
|
1574
|
-
return {};
|
|
1575
|
-
}
|
|
1576
|
-
let parsed;
|
|
1577
|
-
try {
|
|
1578
|
-
parsed = JSON.parse(raw);
|
|
1579
|
-
} catch (err) {
|
|
1580
|
-
warn?.(`Config at ${configPath2} is not valid JSON: ${err.message}`);
|
|
1581
|
-
return {};
|
|
1582
|
-
}
|
|
1583
|
-
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
1584
|
-
return decrypted.providers ?? {};
|
|
1585
|
-
}
|
|
1586
|
-
async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
1587
|
-
let raw;
|
|
1588
|
-
let fileExists = true;
|
|
1589
|
-
try {
|
|
1590
|
-
raw = await fsp4.readFile(configPath2, "utf8");
|
|
1591
|
-
} catch (err) {
|
|
1592
|
-
if (err.code !== "ENOENT") {
|
|
1593
|
-
throw new Error(
|
|
1594
|
-
`Refusing to mutate ${configPath2}: ${err.message}`,
|
|
1595
|
-
{ cause: err }
|
|
1596
|
-
);
|
|
1597
|
-
}
|
|
1598
|
-
fileExists = false;
|
|
1599
|
-
raw = "{}";
|
|
1600
|
-
}
|
|
1601
|
-
let parsed;
|
|
1602
|
-
try {
|
|
1603
|
-
parsed = JSON.parse(raw);
|
|
1604
|
-
} catch (err) {
|
|
1605
|
-
if (fileExists) {
|
|
1606
|
-
throw new Error(
|
|
1607
|
-
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
|
|
1608
|
-
{ cause: err }
|
|
1609
|
-
);
|
|
1610
|
-
}
|
|
1611
|
-
parsed = {};
|
|
1612
|
-
}
|
|
1613
|
-
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
1614
|
-
const providers = decrypted.providers ?? {};
|
|
1615
|
-
mutator(providers);
|
|
1616
|
-
decrypted.providers = providers;
|
|
1617
|
-
const encrypted = encryptConfigSecrets(decrypted, vault);
|
|
1618
|
-
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
1619
|
-
}
|
|
1620
|
-
var init_provider_config_utils = __esm({
|
|
1621
|
-
"src/provider-config-utils.ts"() {
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
1595
|
|
|
1625
1596
|
// src/update-check.ts
|
|
1626
1597
|
var update_check_exports = {};
|
|
@@ -2337,7 +2308,7 @@ async function runWebUI(opts) {
|
|
|
2337
2308
|
const keys = normalizeKeys(existing);
|
|
2338
2309
|
const existingIdx = keys.findIndex((k) => k.label === label);
|
|
2339
2310
|
if (existingIdx >= 0) {
|
|
2340
|
-
keys[existingIdx] = { ...
|
|
2311
|
+
keys[existingIdx] = { ...expectDefined(keys[existingIdx]), apiKey, createdAt: nowIso() };
|
|
2341
2312
|
} else {
|
|
2342
2313
|
keys.push({ label, apiKey, createdAt: nowIso() });
|
|
2343
2314
|
}
|
|
@@ -2471,14 +2442,6 @@ try {
|
|
|
2471
2442
|
if (corePkg.wrongstackApiVersion) API_VERSION = corePkg.wrongstackApiVersion;
|
|
2472
2443
|
} catch {
|
|
2473
2444
|
}
|
|
2474
|
-
|
|
2475
|
-
// src/slash-commands/commit-llm.ts
|
|
2476
|
-
function expectDefined(value) {
|
|
2477
|
-
if (value === null || value === void 0) {
|
|
2478
|
-
throw new Error("Expected value to be defined");
|
|
2479
|
-
}
|
|
2480
|
-
return value;
|
|
2481
|
-
}
|
|
2482
2445
|
async function generateCommitMessageWithLLM(diff, opts) {
|
|
2483
2446
|
const systemPrompt = "You are a helpful assistant that generates concise, conventional-commit-formatted git commit messages. Analyze the provided diff and output ONLY the commit message (no explanation, no quotes). Format: <type>(<scope>): <short description> \u2014 <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, ci, build, temp. If the diff contains multiple unrelated changes, pick the most important one. Keep the description under 72 characters. Example: feat(cli): add /commit LLM integration";
|
|
2484
2447
|
const userPrompt = `Here is the git diff:
|
|
@@ -2925,212 +2888,147 @@ function estimateTokens(messages) {
|
|
|
2925
2888
|
}
|
|
2926
2889
|
return total;
|
|
2927
2890
|
}
|
|
2928
|
-
|
|
2891
|
+
|
|
2892
|
+
// src/slash-commands/auth.ts
|
|
2893
|
+
init_provider_config_utils();
|
|
2894
|
+
function buildAuthCommand(opts) {
|
|
2895
|
+
const help = [
|
|
2896
|
+
"Usage:",
|
|
2897
|
+
" /auth Show saved providers and key status",
|
|
2898
|
+
" /auth status <provider> Show detail for one provider",
|
|
2899
|
+
" /auth open Show how to launch the interactive menu",
|
|
2900
|
+
"",
|
|
2901
|
+
"Run `wstack auth` for the full interactive key manager (add, edit, delete)."
|
|
2902
|
+
].join("\n");
|
|
2929
2903
|
return {
|
|
2930
|
-
name: "
|
|
2931
|
-
category: "
|
|
2932
|
-
description: "
|
|
2933
|
-
help
|
|
2934
|
-
"Usage:",
|
|
2935
|
-
" /autonomy Show current autonomy status",
|
|
2936
|
-
" /autonomy off Disabled \u2014 agent stops after each turn (default)",
|
|
2937
|
-
" /autonomy suggest Show next-step suggestions after each turn",
|
|
2938
|
-
" /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
|
|
2939
|
-
" /autonomy eternal Goal-driven loop \u2014 runs forever against /goal",
|
|
2940
|
-
" (prompts to confirm an existing goal; `--keep` to skip prompt)",
|
|
2941
|
-
" /autonomy parallel Parallel mode \u2014 4-8 agents per tick, fan-out parallelism",
|
|
2942
|
-
" (prompts to confirm an existing goal; `--keep` to skip prompt)",
|
|
2943
|
-
" /autonomy stop Stop eternal mode (no-op for other modes)",
|
|
2944
|
-
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 eternal \u2192 parallel \u2192 off",
|
|
2945
|
-
"",
|
|
2946
|
-
"Modes:",
|
|
2947
|
-
" off \u2014 Normal interactive mode. Agent stops and waits.",
|
|
2948
|
-
" suggest \u2014 After each turn, agent suggests next steps. You pick.",
|
|
2949
|
-
" auto \u2014 After each turn, agent picks the best next step and continues.",
|
|
2950
|
-
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
2951
|
-
" eternal \u2014 Goal-driven sense/decide/execute/reflect loop. Requires /goal.",
|
|
2952
|
-
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
2953
|
-
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
2954
|
-
" parallel \u2014 Fan-out 4\u20138 subagents per tick. Each tick decomposes the goal,",
|
|
2955
|
-
" spawns N agents, awaits results, aggregates. Requires /goal.",
|
|
2956
|
-
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
2957
|
-
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
2958
|
-
"",
|
|
2959
|
-
"Eternal stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
2960
|
-
"Stage shown in real-time. Use /goal pause to pause, /goal resume to continue.",
|
|
2961
|
-
"",
|
|
2962
|
-
"In auto/eternal/parallel modes the agent works autonomously. Press Esc to redirect,",
|
|
2963
|
-
"Ctrl+C to stop the active iteration. /autonomy stop ends the eternal loop."
|
|
2964
|
-
].join("\n"),
|
|
2904
|
+
name: "auth",
|
|
2905
|
+
category: "Config",
|
|
2906
|
+
description: "View API key status. Run wstack auth for the full interactive key manager.",
|
|
2907
|
+
help,
|
|
2965
2908
|
async run(args) {
|
|
2966
|
-
const parts = args.trim().
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
return { message:
|
|
2973
|
-
}
|
|
2974
|
-
if (
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2909
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
2910
|
+
const sub = (parts[0] ?? "").toLowerCase();
|
|
2911
|
+
if (sub === "help" || sub === "--help") {
|
|
2912
|
+
return { message: this.help ?? "" };
|
|
2913
|
+
}
|
|
2914
|
+
if (!opts.paths?.globalConfig) {
|
|
2915
|
+
return { message: `${color.red("Error")} auth not available \u2014 config path missing.` };
|
|
2916
|
+
}
|
|
2917
|
+
if (sub === "open") {
|
|
2918
|
+
return {
|
|
2919
|
+
message: [
|
|
2920
|
+
`${color.bold("API Key Manager")}`,
|
|
2921
|
+
"",
|
|
2922
|
+
` Run ${color.bold("wstack auth")} in a separate terminal to manage API keys interactively:`,
|
|
2923
|
+
"",
|
|
2924
|
+
` ${color.cyan("wstack auth")} Interactive menu`,
|
|
2925
|
+
` ${color.cyan("wstack auth <provider>")} Add a key for <provider>`,
|
|
2926
|
+
` ${color.cyan("wstack auth <p> --label <l>")} Add with custom label`,
|
|
2927
|
+
"",
|
|
2928
|
+
color.dim(" The interactive menu requires standard input (readline) which is not"),
|
|
2929
|
+
color.dim(" available inside the WrongStack session REPL.")
|
|
2930
|
+
].join("\n")
|
|
2982
2931
|
};
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
)
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
` Engine state: ${goal.engineState} \xB7 iterations: ${goal.iterations} \xB7 journal: ${goal.journal.length}`
|
|
2996
|
-
)
|
|
2997
|
-
);
|
|
2998
|
-
if (u.iterationsWithUsage > 0) {
|
|
2999
|
-
lines.push(
|
|
3000
|
-
color.dim(
|
|
3001
|
-
` Spent: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens`
|
|
3002
|
-
)
|
|
3003
|
-
);
|
|
3004
|
-
}
|
|
3005
|
-
const recent = goal.journal.slice(-10);
|
|
3006
|
-
const failed = recent.filter((e) => e.status === "failure").length;
|
|
3007
|
-
if (failed > 0) {
|
|
3008
|
-
lines.push(
|
|
3009
|
-
color.amber(` Recent failures: ${failed} of last ${recent.length} iterations`)
|
|
3010
|
-
);
|
|
3011
|
-
}
|
|
2932
|
+
}
|
|
2933
|
+
let providers;
|
|
2934
|
+
try {
|
|
2935
|
+
providers = await loadConfigProviders(
|
|
2936
|
+
opts.paths.globalConfig,
|
|
2937
|
+
// We don't have a full vault reference in slash commands;
|
|
2938
|
+
// use a simple passthrough vault since keys won't decrypt anyway
|
|
2939
|
+
// in this read-only view and the config may not have encrypted fields.
|
|
2940
|
+
{
|
|
2941
|
+
encrypt: (v) => v,
|
|
2942
|
+
decrypt: (v) => v,
|
|
2943
|
+
isEncrypted: () => false
|
|
3012
2944
|
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
opts.renderer.write(msg2);
|
|
3017
|
-
return { message: msg2 };
|
|
2945
|
+
);
|
|
2946
|
+
} catch {
|
|
2947
|
+
return { message: `${color.red("Error")} could not read config file.` };
|
|
3018
2948
|
}
|
|
3019
|
-
if (
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
return { message: msg3 };
|
|
2949
|
+
if (sub === "status") {
|
|
2950
|
+
const pid = parts[1];
|
|
2951
|
+
if (!pid) {
|
|
2952
|
+
return { message: `${color.amber("Usage:")} /auth status <provider>` };
|
|
3024
2953
|
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
opts.onAutonomy("off");
|
|
3029
|
-
let summaryLine = "";
|
|
3030
|
-
try {
|
|
3031
|
-
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3032
|
-
if (goal) {
|
|
3033
|
-
const u = summarizeUsage(goal);
|
|
3034
|
-
if (u.iterationsWithUsage > 0) {
|
|
3035
|
-
summaryLine = "\n" + color.dim(
|
|
3036
|
-
` Spent so far: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens \xB7 ${goal.iterations} total iterations.`
|
|
3037
|
-
);
|
|
3038
|
-
} else if (goal.iterations > 0) {
|
|
3039
|
-
summaryLine = "\n" + color.dim(` Total iterations: ${goal.iterations}.`);
|
|
3040
|
-
}
|
|
3041
|
-
}
|
|
3042
|
-
} catch {
|
|
2954
|
+
const cfg = providers[pid];
|
|
2955
|
+
if (!cfg) {
|
|
2956
|
+
return { message: `${color.yellow("Provider")} "${pid}" not found in saved config.` };
|
|
3043
2957
|
}
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
} else if (arg === "parallel" || arg === "eternal-parallel" || arg === "fanout") {
|
|
3058
|
-
newMode = "eternal-parallel";
|
|
3059
|
-
} else if (arg === "toggle" || arg === "cycle") {
|
|
3060
|
-
const current = opts.onAutonomy() ?? "off";
|
|
3061
|
-
const cycle = ["off", "suggest", "auto", "eternal", "eternal-parallel"];
|
|
3062
|
-
newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
|
|
3063
|
-
} else {
|
|
3064
|
-
const msg2 = `Unknown argument: ${arg}. Use /autonomy on, off, suggest, eternal, parallel, stop, or toggle.`;
|
|
3065
|
-
opts.renderer.writeWarning(msg2);
|
|
3066
|
-
return { message: msg2 };
|
|
3067
|
-
}
|
|
3068
|
-
if (newMode === "eternal" || newMode === "eternal-parallel") {
|
|
3069
|
-
const wantKeep = modifiers.includes("--keep") || modifiers.includes("keep");
|
|
3070
|
-
const wantNew = modifiers.includes("--new") || modifiers.includes("new");
|
|
3071
|
-
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3072
|
-
if (!goal) {
|
|
3073
|
-
const msg3 = `${color.red("Eternal/parallel mode requires a goal.")} Run \`/goal set <mission>\` first.`;
|
|
3074
|
-
opts.renderer.writeWarning(msg3);
|
|
3075
|
-
return { message: msg3 };
|
|
2958
|
+
const keys = cfg.apiKeys ?? [];
|
|
2959
|
+
const active = keys.find(
|
|
2960
|
+
(k) => cfg && cfg.activeKey === k.label
|
|
2961
|
+
) ?? keys[0];
|
|
2962
|
+
const lines2 = [
|
|
2963
|
+
`${color.bold(pid)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.amber("[no family]")}`,
|
|
2964
|
+
"",
|
|
2965
|
+
` type: ${color.cyan(cfg.type ?? pid)}`,
|
|
2966
|
+
` family: ${cfg.family ? color.cyan(cfg.family) : color.dim("unset")}`,
|
|
2967
|
+
` baseUrl: ${cfg.baseUrl ? color.cyan(cfg.baseUrl) : color.dim("unset")}`
|
|
2968
|
+
];
|
|
2969
|
+
if (cfg.models?.length) {
|
|
2970
|
+
lines2.push(` models: ${color.cyan(cfg.models.join(", "))}`);
|
|
3076
2971
|
}
|
|
3077
|
-
if (
|
|
3078
|
-
|
|
3079
|
-
opts.renderer.writeWarning(msg3);
|
|
3080
|
-
return { message: msg3 };
|
|
2972
|
+
if (cfg.envVars?.length) {
|
|
2973
|
+
lines2.push(` envVars: ${color.cyan(cfg.envVars.join(", "))}`);
|
|
3081
2974
|
}
|
|
3082
|
-
|
|
3083
|
-
if (
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3094
|
-
if (!answer) {
|
|
3095
|
-
const msg3 = `${color.amber("Skipped.")} To start a new mission: ${color.bold("/goal clear")} \u2192 ${color.bold("/goal set <mission>")} \u2192 ${color.bold(`/autonomy ${newMode}`)}. To force the existing one: ${color.bold(`/autonomy ${newMode} --keep`)}.`;
|
|
3096
|
-
opts.renderer.write(msg3);
|
|
3097
|
-
return { message: msg3 };
|
|
3098
|
-
}
|
|
3099
|
-
} else if (isStale) {
|
|
3100
|
-
const msg3 = `${color.amber("Stale goal detected.")} Previous mission has ${goal.iterations} iterations (engineState: ${goal.engineState}). Clear it first: ${color.bold("/goal clear")}, then set a new one: ${color.bold("/goal set <mission>")}.`;
|
|
3101
|
-
opts.renderer.writeWarning(msg3);
|
|
3102
|
-
return { message: msg3 };
|
|
2975
|
+
lines2.push("");
|
|
2976
|
+
if (keys.length === 0) {
|
|
2977
|
+
lines2.push(color.dim(" (no keys saved)"));
|
|
2978
|
+
} else {
|
|
2979
|
+
lines2.push(` ${color.dim("Keys:")}`);
|
|
2980
|
+
for (const k of keys) {
|
|
2981
|
+
const marker = k.label === active?.label ? color.green("\u25CF") : color.dim("\u25CB");
|
|
2982
|
+
const masked = k.label === active?.label ? color.dim("(active \u2014 masked)") : color.dim("(masked)");
|
|
2983
|
+
lines2.push(
|
|
2984
|
+
` ${marker} ${color.bold(k.label.padEnd(18))} ${masked} ${color.dim(k.createdAt)}`
|
|
2985
|
+
);
|
|
3103
2986
|
}
|
|
3104
2987
|
}
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
opts.renderer.writeWarning(msg3);
|
|
3108
|
-
return { message: msg3 };
|
|
3109
|
-
}
|
|
3110
|
-
if (opts.onYolo) opts.onYolo(true);
|
|
3111
|
-
opts.onAutonomy(newMode);
|
|
3112
|
-
opts.onEternalStart(newMode);
|
|
3113
|
-
const modeLabel = newMode === "eternal-parallel" ? `${color.magenta("PARALLEL")} mode` : `${color.red("ETERNAL")} mode`;
|
|
3114
|
-
const msg2 = `Autonomy mode: ${modeLabel} \u2014 engine launching against goal: ${color.bold(goal.goal)}
|
|
3115
|
-
${color.dim("Regular YOLO enabled; destructive-gated calls still use the permission flow. Use /autonomy stop to end. Journal at /goal journal.")}`;
|
|
3116
|
-
opts.renderer.write(msg2);
|
|
3117
|
-
return { message: msg2 };
|
|
2988
|
+
lines2.push("", color.dim(` Manage: wstack auth \u2192 pick ${pid}`));
|
|
2989
|
+
return { message: lines2.join("\n") };
|
|
3118
2990
|
}
|
|
3119
|
-
const
|
|
3120
|
-
if (
|
|
3121
|
-
|
|
2991
|
+
const ids = Object.keys(providers).sort();
|
|
2992
|
+
if (ids.length === 0) {
|
|
2993
|
+
return {
|
|
2994
|
+
message: [
|
|
2995
|
+
`${color.bold("API Keys")} ${color.dim("\u2014 No providers configured")}`,
|
|
2996
|
+
"",
|
|
2997
|
+
color.dim(" Run `wstack auth` to add a provider with an API key."),
|
|
2998
|
+
"",
|
|
2999
|
+
color.dim(" Quick start:"),
|
|
3000
|
+
` ${color.cyan("wstack auth")} Interactive menu`,
|
|
3001
|
+
` ${color.cyan("wstack auth anthropic")} Direct add`,
|
|
3002
|
+
"",
|
|
3003
|
+
color.dim(" Or /auth help for more commands.")
|
|
3004
|
+
].join("\n")
|
|
3005
|
+
};
|
|
3122
3006
|
}
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3007
|
+
const lines = [
|
|
3008
|
+
`${color.bold("API Keys")} ${color.dim(`\u2014 ${ids.length} provider${ids.length === 1 ? "" : "s"}`)}`,
|
|
3009
|
+
""
|
|
3010
|
+
];
|
|
3011
|
+
for (const id of ids) {
|
|
3012
|
+
const cfg = providers[id];
|
|
3013
|
+
if (!cfg) continue;
|
|
3014
|
+
const keys = cfg.apiKeys ?? [];
|
|
3015
|
+
const famTag = cfg.family ? color.dim(`[${cfg.family}]`) : "";
|
|
3016
|
+
const aliasTag = cfg.type && cfg.type !== id ? color.dim(`\u2192 ${cfg.type}`) : "";
|
|
3017
|
+
let status;
|
|
3018
|
+
if (keys.length === 0) {
|
|
3019
|
+
status = color.amber("no keys");
|
|
3020
|
+
} else if (keys.length === 1) {
|
|
3021
|
+
status = color.green(`1 key`);
|
|
3022
|
+
} else {
|
|
3023
|
+
status = color.green(`${keys.length} keys`);
|
|
3024
|
+
}
|
|
3025
|
+
lines.push(
|
|
3026
|
+
` ${color.bold(id.padEnd(22))} ${famTag} ${aliasTag} ${status}`
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
lines.push("");
|
|
3030
|
+
lines.push(color.dim(" /auth status <id> Detail /auth open Full menu"));
|
|
3031
|
+
return { message: lines.join("\n") };
|
|
3134
3032
|
}
|
|
3135
3033
|
};
|
|
3136
3034
|
}
|
|
@@ -3292,6 +3190,215 @@ function buildAutoPhaseCommand(opts) {
|
|
|
3292
3190
|
}
|
|
3293
3191
|
};
|
|
3294
3192
|
}
|
|
3193
|
+
function buildAutonomyCommand(opts) {
|
|
3194
|
+
return {
|
|
3195
|
+
name: "autonomy",
|
|
3196
|
+
category: "Agent",
|
|
3197
|
+
description: "Toggle or query autonomy mode (self-driving agent).",
|
|
3198
|
+
help: [
|
|
3199
|
+
"Usage:",
|
|
3200
|
+
" /autonomy Show current autonomy status",
|
|
3201
|
+
" /autonomy off Disabled \u2014 agent stops after each turn (default)",
|
|
3202
|
+
" /autonomy suggest Show next-step suggestions after each turn",
|
|
3203
|
+
" /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
|
|
3204
|
+
" /autonomy eternal Goal-driven loop \u2014 runs forever against /goal",
|
|
3205
|
+
" (prompts to confirm an existing goal; `--keep` to skip prompt)",
|
|
3206
|
+
" /autonomy parallel Parallel mode \u2014 4-8 agents per tick, fan-out parallelism",
|
|
3207
|
+
" (prompts to confirm an existing goal; `--keep` to skip prompt)",
|
|
3208
|
+
" /autonomy stop Stop eternal mode (no-op for other modes)",
|
|
3209
|
+
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 eternal \u2192 parallel \u2192 off",
|
|
3210
|
+
"",
|
|
3211
|
+
"Modes:",
|
|
3212
|
+
" off \u2014 Normal interactive mode. Agent stops and waits.",
|
|
3213
|
+
" suggest \u2014 After each turn, agent suggests next steps. You pick.",
|
|
3214
|
+
" auto \u2014 After each turn, agent picks the best next step and continues.",
|
|
3215
|
+
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
3216
|
+
" eternal \u2014 Goal-driven sense/decide/execute/reflect loop. Requires /goal.",
|
|
3217
|
+
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
3218
|
+
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
3219
|
+
" parallel \u2014 Fan-out 4\u20138 subagents per tick. Each tick decomposes the goal,",
|
|
3220
|
+
" spawns N agents, awaits results, aggregates. Requires /goal.",
|
|
3221
|
+
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
3222
|
+
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
3223
|
+
"",
|
|
3224
|
+
"Eternal stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
3225
|
+
"Stage shown in real-time. Use /goal pause to pause, /goal resume to continue.",
|
|
3226
|
+
"",
|
|
3227
|
+
"In auto/eternal/parallel modes the agent works autonomously. Press Esc to redirect,",
|
|
3228
|
+
"Ctrl+C to stop the active iteration. /autonomy stop ends the eternal loop."
|
|
3229
|
+
].join("\n"),
|
|
3230
|
+
async run(args) {
|
|
3231
|
+
const parts = args.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
3232
|
+
const arg = parts[0] ?? "";
|
|
3233
|
+
const modifiers = parts.slice(1);
|
|
3234
|
+
if (!opts.onAutonomy) {
|
|
3235
|
+
const msg2 = "Autonomy mode is not available in this session.";
|
|
3236
|
+
opts.renderer.writeWarning(msg2);
|
|
3237
|
+
return { message: msg2 };
|
|
3238
|
+
}
|
|
3239
|
+
if (!arg || arg === "status") {
|
|
3240
|
+
const current = opts.onAutonomy();
|
|
3241
|
+
const labels2 = {
|
|
3242
|
+
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
3243
|
+
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
3244
|
+
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
|
|
3245
|
+
eternal: `${color.red("ETERNAL")} ${color.dim("(goal-driven loop \u2014 YOLO, until /autonomy stop)")}`,
|
|
3246
|
+
"eternal-parallel": `${color.magenta("PARALLEL")} ${color.dim("(4-8 subagents per tick \u2014 fan-out, until /autonomy stop)")}`
|
|
3247
|
+
};
|
|
3248
|
+
const lines = [`Autonomy mode: ${labels2[current] ?? current}`];
|
|
3249
|
+
try {
|
|
3250
|
+
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3251
|
+
if (goal) {
|
|
3252
|
+
const u = summarizeUsage(goal);
|
|
3253
|
+
lines.push(
|
|
3254
|
+
color.dim(
|
|
3255
|
+
` Goal: ${goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal}`
|
|
3256
|
+
)
|
|
3257
|
+
);
|
|
3258
|
+
lines.push(
|
|
3259
|
+
color.dim(
|
|
3260
|
+
` Engine state: ${goal.engineState} \xB7 iterations: ${goal.iterations} \xB7 journal: ${goal.journal.length}`
|
|
3261
|
+
)
|
|
3262
|
+
);
|
|
3263
|
+
if (u.iterationsWithUsage > 0) {
|
|
3264
|
+
lines.push(
|
|
3265
|
+
color.dim(
|
|
3266
|
+
` Spent: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens`
|
|
3267
|
+
)
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
const recent = goal.journal.slice(-10);
|
|
3271
|
+
const failed = recent.filter((e) => e.status === "failure").length;
|
|
3272
|
+
if (failed > 0) {
|
|
3273
|
+
lines.push(
|
|
3274
|
+
color.amber(` Recent failures: ${failed} of last ${recent.length} iterations`)
|
|
3275
|
+
);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
} catch {
|
|
3279
|
+
}
|
|
3280
|
+
const msg2 = lines.join("\n");
|
|
3281
|
+
opts.renderer.write(msg2);
|
|
3282
|
+
return { message: msg2 };
|
|
3283
|
+
}
|
|
3284
|
+
if (arg === "stop" || arg === "halt" || arg === "kill") {
|
|
3285
|
+
if (!opts.onEternalStop) {
|
|
3286
|
+
const msg3 = "No eternal-mode controller wired in this session.";
|
|
3287
|
+
opts.renderer.writeWarning(msg3);
|
|
3288
|
+
return { message: msg3 };
|
|
3289
|
+
}
|
|
3290
|
+
opts.getEternalEngine?.()?.stop();
|
|
3291
|
+
opts.getParallelEngine?.()?.stop();
|
|
3292
|
+
opts.onEternalStop();
|
|
3293
|
+
opts.onAutonomy("off");
|
|
3294
|
+
let summaryLine = "";
|
|
3295
|
+
try {
|
|
3296
|
+
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3297
|
+
if (goal) {
|
|
3298
|
+
const u = summarizeUsage(goal);
|
|
3299
|
+
if (u.iterationsWithUsage > 0) {
|
|
3300
|
+
summaryLine = "\n" + color.dim(
|
|
3301
|
+
` Spent so far: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens \xB7 ${goal.iterations} total iterations.`
|
|
3302
|
+
);
|
|
3303
|
+
} else if (goal.iterations > 0) {
|
|
3304
|
+
summaryLine = "\n" + color.dim(` Total iterations: ${goal.iterations}.`);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
} catch {
|
|
3308
|
+
}
|
|
3309
|
+
const msg2 = `${color.amber("Eternal/parallel mode stop requested.")} In-flight eternal work is cancelled; parallel fan-out stops after the current tick cleans up.${summaryLine}`;
|
|
3310
|
+
opts.renderer.write(msg2);
|
|
3311
|
+
return { message: msg2 };
|
|
3312
|
+
}
|
|
3313
|
+
let newMode;
|
|
3314
|
+
if (arg === "on" || arg === "enable" || arg === "true" || arg === "auto") {
|
|
3315
|
+
newMode = "auto";
|
|
3316
|
+
} else if (arg === "off" || arg === "disable" || arg === "false") {
|
|
3317
|
+
newMode = "off";
|
|
3318
|
+
} else if (arg === "suggest" || arg === "suggestions") {
|
|
3319
|
+
newMode = "suggest";
|
|
3320
|
+
} else if (arg === "eternal" || arg === "forever" || arg === "infinite" || arg === "sittinsene") {
|
|
3321
|
+
newMode = "eternal";
|
|
3322
|
+
} else if (arg === "parallel" || arg === "eternal-parallel" || arg === "fanout") {
|
|
3323
|
+
newMode = "eternal-parallel";
|
|
3324
|
+
} else if (arg === "toggle" || arg === "cycle") {
|
|
3325
|
+
const current = opts.onAutonomy() ?? "off";
|
|
3326
|
+
const cycle = ["off", "suggest", "auto", "eternal", "eternal-parallel"];
|
|
3327
|
+
newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
|
|
3328
|
+
} else {
|
|
3329
|
+
const msg2 = `Unknown argument: ${arg}. Use /autonomy on, off, suggest, eternal, parallel, stop, or toggle.`;
|
|
3330
|
+
opts.renderer.writeWarning(msg2);
|
|
3331
|
+
return { message: msg2 };
|
|
3332
|
+
}
|
|
3333
|
+
if (newMode === "eternal" || newMode === "eternal-parallel") {
|
|
3334
|
+
const wantKeep = modifiers.includes("--keep") || modifiers.includes("keep");
|
|
3335
|
+
const wantNew = modifiers.includes("--new") || modifiers.includes("new");
|
|
3336
|
+
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3337
|
+
if (!goal) {
|
|
3338
|
+
const msg3 = `${color.red("Eternal/parallel mode requires a goal.")} Run \`/goal set <mission>\` first.`;
|
|
3339
|
+
opts.renderer.writeWarning(msg3);
|
|
3340
|
+
return { message: msg3 };
|
|
3341
|
+
}
|
|
3342
|
+
if (wantNew) {
|
|
3343
|
+
const msg3 = `${color.amber("New mission requested.")} Clear the current goal first: ${color.bold("/goal clear")}, then ${color.bold("/goal set <mission>")}, then re-run ${color.bold(`/autonomy ${newMode}`)}.`;
|
|
3344
|
+
opts.renderer.writeWarning(msg3);
|
|
3345
|
+
return { message: msg3 };
|
|
3346
|
+
}
|
|
3347
|
+
const isStale = goal.iterations > 0 || goal.engineState === "running";
|
|
3348
|
+
if (!wantKeep) {
|
|
3349
|
+
if (opts.confirm) {
|
|
3350
|
+
const goalPreview = goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal;
|
|
3351
|
+
const detail = isStale ? `${color.amber("Stale goal")} (${goal.iterations} iterations, engineState: ${goal.engineState}): "${goalPreview}". Continue with this mission?` : `Existing goal: "${goalPreview}". Use this mission?`;
|
|
3352
|
+
const defaultYes = !isStale;
|
|
3353
|
+
const answer = await opts.confirm(detail, defaultYes);
|
|
3354
|
+
if (answer === null) {
|
|
3355
|
+
const msg3 = `${color.dim("Cancelled.")} Autonomy mode unchanged.`;
|
|
3356
|
+
opts.renderer.write(msg3);
|
|
3357
|
+
return { message: msg3 };
|
|
3358
|
+
}
|
|
3359
|
+
if (!answer) {
|
|
3360
|
+
const msg3 = `${color.amber("Skipped.")} To start a new mission: ${color.bold("/goal clear")} \u2192 ${color.bold("/goal set <mission>")} \u2192 ${color.bold(`/autonomy ${newMode}`)}. To force the existing one: ${color.bold(`/autonomy ${newMode} --keep`)}.`;
|
|
3361
|
+
opts.renderer.write(msg3);
|
|
3362
|
+
return { message: msg3 };
|
|
3363
|
+
}
|
|
3364
|
+
} else if (isStale) {
|
|
3365
|
+
const msg3 = `${color.amber("Stale goal detected.")} Previous mission has ${goal.iterations} iterations (engineState: ${goal.engineState}). Clear it first: ${color.bold("/goal clear")}, then set a new one: ${color.bold("/goal set <mission>")}.`;
|
|
3366
|
+
opts.renderer.writeWarning(msg3);
|
|
3367
|
+
return { message: msg3 };
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
if (!opts.onEternalStart) {
|
|
3371
|
+
const msg3 = "Eternal mode controller is not wired in this session.";
|
|
3372
|
+
opts.renderer.writeWarning(msg3);
|
|
3373
|
+
return { message: msg3 };
|
|
3374
|
+
}
|
|
3375
|
+
if (opts.onYolo) opts.onYolo(true);
|
|
3376
|
+
opts.onAutonomy(newMode);
|
|
3377
|
+
opts.onEternalStart(newMode);
|
|
3378
|
+
const modeLabel = newMode === "eternal-parallel" ? `${color.magenta("PARALLEL")} mode` : `${color.red("ETERNAL")} mode`;
|
|
3379
|
+
const msg2 = `Autonomy mode: ${modeLabel} \u2014 engine launching against goal: ${color.bold(goal.goal)}
|
|
3380
|
+
${color.dim("Regular YOLO enabled; destructive-gated calls still use the permission flow. Use /autonomy stop to end. Journal at /goal journal.")}`;
|
|
3381
|
+
opts.renderer.write(msg2);
|
|
3382
|
+
return { message: msg2 };
|
|
3383
|
+
}
|
|
3384
|
+
const previous = opts.onAutonomy();
|
|
3385
|
+
if ((previous === "eternal" || previous === "eternal-parallel") && opts.onEternalStop) {
|
|
3386
|
+
opts.onEternalStop();
|
|
3387
|
+
}
|
|
3388
|
+
opts.onAutonomy(newMode);
|
|
3389
|
+
const labels = {
|
|
3390
|
+
off: `${color.green("OFF")} \u2014 agent stops after each turn`,
|
|
3391
|
+
suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
|
|
3392
|
+
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
|
|
3393
|
+
eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`,
|
|
3394
|
+
"eternal-parallel": `${color.magenta("PARALLEL")} \u2014 fan-out 4-8 subagents per tick`
|
|
3395
|
+
};
|
|
3396
|
+
const msg = `Autonomy mode: ${labels[newMode]}`;
|
|
3397
|
+
opts.renderer.write(msg);
|
|
3398
|
+
return { message: msg };
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3295
3402
|
function buildBtwCommand(opts) {
|
|
3296
3403
|
return {
|
|
3297
3404
|
name: "btw",
|
|
@@ -5168,6 +5275,112 @@ function buildFleetCommand(opts) {
|
|
|
5168
5275
|
}
|
|
5169
5276
|
};
|
|
5170
5277
|
}
|
|
5278
|
+
|
|
5279
|
+
// src/slash-commands/goal-refiner.ts
|
|
5280
|
+
async function refineGoal(rawGoal, provider, model) {
|
|
5281
|
+
const prompt = buildRefinementPrompt(rawGoal);
|
|
5282
|
+
try {
|
|
5283
|
+
const signal = AbortSignal.timeout(3e4);
|
|
5284
|
+
const response = await provider.complete({
|
|
5285
|
+
model,
|
|
5286
|
+
system: [{ type: "text", text: prompt }],
|
|
5287
|
+
messages: [{ role: "user", content: "Produce the refined goal." }],
|
|
5288
|
+
maxTokens: 1e3
|
|
5289
|
+
}, { signal });
|
|
5290
|
+
const text = extractText(response);
|
|
5291
|
+
if (!text) return null;
|
|
5292
|
+
return parseRefinement(text, rawGoal);
|
|
5293
|
+
} catch {
|
|
5294
|
+
return null;
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
function buildRefinementPrompt(rawGoal) {
|
|
5298
|
+
return [
|
|
5299
|
+
"You are a goal refinement assistant. Your job is to take a user's raw",
|
|
5300
|
+
"goal description and turn it into a clear, unambiguous, actionable mission",
|
|
5301
|
+
"with concrete, verifiable deliverables.",
|
|
5302
|
+
"",
|
|
5303
|
+
"Rules:",
|
|
5304
|
+
"- The refined goal must be self-contained \u2014 someone reading only the",
|
|
5305
|
+
" refined goal should understand exactly what to do without seeing the",
|
|
5306
|
+
" original.",
|
|
5307
|
+
"- Each deliverable must be a single, checkable item. Prefer concrete",
|
|
5308
|
+
' artifacts: "file X exists at path Y", "test Z passes", "function A',
|
|
5309
|
+
' is refactored into module B". Avoid vague items like "improve code".',
|
|
5310
|
+
"- Include acceptance criteria where helpful.",
|
|
5311
|
+
"- If the goal is already clear and concrete, refine it minimally \u2014 do",
|
|
5312
|
+
" not add fluff.",
|
|
5313
|
+
"",
|
|
5314
|
+
"Output format (exact \u2014 use these markers):",
|
|
5315
|
+
"",
|
|
5316
|
+
"REFINED_GOAL:",
|
|
5317
|
+
"<the refined goal text, 1-3 sentences>",
|
|
5318
|
+
"",
|
|
5319
|
+
"DELIVERABLES:",
|
|
5320
|
+
"- <deliverable 1>",
|
|
5321
|
+
"- <deliverable 2>",
|
|
5322
|
+
"- ...",
|
|
5323
|
+
"",
|
|
5324
|
+
"---",
|
|
5325
|
+
"",
|
|
5326
|
+
`RAW GOAL: ${rawGoal}`,
|
|
5327
|
+
"",
|
|
5328
|
+
"---",
|
|
5329
|
+
"",
|
|
5330
|
+
"Now produce the refined version:"
|
|
5331
|
+
].join("\n");
|
|
5332
|
+
}
|
|
5333
|
+
function extractText(result) {
|
|
5334
|
+
if (!result || typeof result !== "object") return null;
|
|
5335
|
+
const r = result;
|
|
5336
|
+
if (Array.isArray(r.content)) {
|
|
5337
|
+
const texts = r.content.filter((b) => b.type === "text").map((b) => b.text ?? "");
|
|
5338
|
+
return texts.join("") || null;
|
|
5339
|
+
}
|
|
5340
|
+
if (Array.isArray(r.choices)) {
|
|
5341
|
+
const choice = r.choices[0];
|
|
5342
|
+
return choice?.message?.content ?? null;
|
|
5343
|
+
}
|
|
5344
|
+
if (typeof r.text === "string") return r.text;
|
|
5345
|
+
return null;
|
|
5346
|
+
}
|
|
5347
|
+
function parseRefinement(text, fallbackGoal) {
|
|
5348
|
+
const refinedMatch = text.match(/REFINED_GOAL:\s*\n?([\s\S]*?)(?=\nDELIVERABLES:|$)/i);
|
|
5349
|
+
const refinedGoal = refinedMatch?.[1]?.trim() || fallbackGoal;
|
|
5350
|
+
const deliverablesMatch = text.match(/DELIVERABLES:\s*\n([\s\S]*?)$/i);
|
|
5351
|
+
const deliverablesRaw = deliverablesMatch?.[1] ?? "";
|
|
5352
|
+
const deliverables = deliverablesRaw.split("\n").map((line) => line.replace(/^[\s-]*[-*]\s*/, "").trim()).filter((line) => line.length > 0 && !line.startsWith("REFINED_GOAL"));
|
|
5353
|
+
return {
|
|
5354
|
+
refinedGoal,
|
|
5355
|
+
deliverables: deliverables.length > 0 ? deliverables : []
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
function refineGoalHeuristic(rawGoal) {
|
|
5359
|
+
const trimmed = rawGoal.trim();
|
|
5360
|
+
return {
|
|
5361
|
+
refinedGoal: trimmed,
|
|
5362
|
+
deliverables: extractHeuristicDeliverables(trimmed)
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
5365
|
+
function extractHeuristicDeliverables(goal) {
|
|
5366
|
+
const deliverables = [];
|
|
5367
|
+
const lines = goal.split(/[.;]\s*/);
|
|
5368
|
+
for (const line of lines) {
|
|
5369
|
+
const cleaned = line.trim();
|
|
5370
|
+
if (!cleaned) continue;
|
|
5371
|
+
if (/\b(add|build|create|fix|implement|refactor|write|remove|update|migrate|set up|configure|deploy|test|document)\b/i.test(
|
|
5372
|
+
cleaned
|
|
5373
|
+
)) {
|
|
5374
|
+
deliverables.push(cleaned);
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
if (deliverables.length === 0) {
|
|
5378
|
+
deliverables.push(goal.trim());
|
|
5379
|
+
}
|
|
5380
|
+
return deliverables;
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
// src/slash-commands/goal.ts
|
|
5171
5384
|
var KNOWN_VERBS = /* @__PURE__ */ new Set([
|
|
5172
5385
|
"",
|
|
5173
5386
|
"show",
|
|
@@ -5179,25 +5392,31 @@ var KNOWN_VERBS = /* @__PURE__ */ new Set([
|
|
|
5179
5392
|
"journal",
|
|
5180
5393
|
"log",
|
|
5181
5394
|
"pause",
|
|
5182
|
-
"resume"
|
|
5395
|
+
"resume",
|
|
5396
|
+
"refine"
|
|
5183
5397
|
]);
|
|
5184
5398
|
function buildGoalCommand(opts) {
|
|
5185
5399
|
return {
|
|
5186
5400
|
name: "goal",
|
|
5187
5401
|
category: "Agent",
|
|
5188
|
-
description: "Set, inspect, or clear the long-running autonomous mission
|
|
5402
|
+
description: "Set, inspect, or clear the long-running autonomous mission. Auto-refines goals for clarity.",
|
|
5189
5403
|
help: [
|
|
5190
5404
|
"Usage:",
|
|
5191
|
-
" /goal Show current goal + recent journal",
|
|
5192
|
-
" /goal set <text> Set a new goal (
|
|
5405
|
+
" /goal Show current goal + progress + recent journal",
|
|
5406
|
+
" /goal set <text> Set a new goal (auto-refined for clarity)",
|
|
5407
|
+
" /goal refine Re-refine the current goal",
|
|
5193
5408
|
" /goal clear Clear the goal (stops eternal mode if running)",
|
|
5194
|
-
" /goal pause Pause at end of current iteration
|
|
5195
|
-
" /goal resume Resume a paused goal
|
|
5196
|
-
" /goal status Same as /goal (alias)",
|
|
5409
|
+
" /goal pause Pause at end of current iteration",
|
|
5410
|
+
" /goal resume Resume a paused goal",
|
|
5197
5411
|
" /goal journal [N] Show last N journal entries (default 25)",
|
|
5198
5412
|
"",
|
|
5413
|
+
"When a goal is set, WrongStack auto-refines it using the LLM to:",
|
|
5414
|
+
" \u2022 Make it unambiguous and concrete",
|
|
5415
|
+
" \u2022 Extract verifiable deliverables with acceptance criteria",
|
|
5416
|
+
" \u2022 Estimate completion progress (shown as a progress bar)",
|
|
5417
|
+
"",
|
|
5199
5418
|
"Stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
5200
|
-
"
|
|
5419
|
+
"The engine updates progress after each iteration toward the deliverable list.",
|
|
5201
5420
|
"",
|
|
5202
5421
|
"Goals live in ~/.wrongstack/projects/<hash>/goal.json and persist across sessions.",
|
|
5203
5422
|
"A goal is the prerequisite for /autonomy eternal \u2014 the engine consults it on",
|
|
@@ -5233,24 +5452,93 @@ function buildGoalCommand(opts) {
|
|
|
5233
5452
|
opts.renderer.writeWarning(msg2);
|
|
5234
5453
|
return { message: msg2 };
|
|
5235
5454
|
}
|
|
5455
|
+
let refined = null;
|
|
5456
|
+
if (opts.llmProvider && opts.llmModel) {
|
|
5457
|
+
opts.renderer.write(color.dim("Refining goal with LLM\u2026"));
|
|
5458
|
+
refined = await refineGoal(setText, opts.llmProvider, opts.llmModel);
|
|
5459
|
+
}
|
|
5460
|
+
if (!refined) {
|
|
5461
|
+
refined = refineGoalHeuristic(setText);
|
|
5462
|
+
}
|
|
5236
5463
|
const existing = await loadGoal(goalPath);
|
|
5237
|
-
const
|
|
5464
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5465
|
+
const next = existing ? {
|
|
5466
|
+
...existing,
|
|
5467
|
+
goal: setText,
|
|
5468
|
+
refinedGoal: refined.refinedGoal,
|
|
5469
|
+
deliverables: refined.deliverables,
|
|
5470
|
+
setAt: now,
|
|
5471
|
+
lastActivityAt: now,
|
|
5472
|
+
progress: void 0,
|
|
5473
|
+
// reset progress
|
|
5474
|
+
progressNote: void 0
|
|
5475
|
+
} : {
|
|
5476
|
+
...emptyGoal(setText),
|
|
5477
|
+
refinedGoal: refined.refinedGoal,
|
|
5478
|
+
deliverables: refined.deliverables
|
|
5479
|
+
};
|
|
5238
5480
|
await saveGoal(goalPath, next);
|
|
5239
|
-
const
|
|
5240
|
-
|
|
5241
|
-
${color.
|
|
5481
|
+
const lines = [];
|
|
5482
|
+
lines.push(
|
|
5483
|
+
`\u{1F3AF} ${color.green("Goal locked:")} ${color.bold(refined.refinedGoal)}`
|
|
5484
|
+
);
|
|
5485
|
+
if (refined.refinedGoal !== setText) {
|
|
5486
|
+
lines.push(color.dim(` (original: "${setText.length > 60 ? setText.slice(0, 60) + "\u2026" : setText}")`));
|
|
5487
|
+
}
|
|
5488
|
+
if (refined.deliverables.length > 0) {
|
|
5489
|
+
lines.push("");
|
|
5490
|
+
lines.push(`${color.bold("Deliverables")} (${refined.deliverables.length}):`);
|
|
5491
|
+
for (const d of refined.deliverables) {
|
|
5492
|
+
lines.push(` ${color.dim("\u25CB")} ${d}`);
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
lines.push("");
|
|
5496
|
+
lines.push(
|
|
5497
|
+
color.dim(`Stored in ${goalPath} \u2014 progress tracked automatically.`)
|
|
5498
|
+
);
|
|
5499
|
+
const msg = lines.join("\n");
|
|
5500
|
+
opts.renderer.write(msg);
|
|
5501
|
+
return {
|
|
5502
|
+
message: msg,
|
|
5503
|
+
runText: buildGoalPreamble(refined.refinedGoal, refined.deliverables)
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
case "refine": {
|
|
5507
|
+
const current = await loadGoal(goalPath);
|
|
5508
|
+
if (!current) {
|
|
5509
|
+
const msg2 = "No goal set to refine. Use /goal set <text> first.";
|
|
5510
|
+
opts.renderer.writeWarning(msg2);
|
|
5511
|
+
return { message: msg2 };
|
|
5512
|
+
}
|
|
5513
|
+
let refined = null;
|
|
5514
|
+
if (opts.llmProvider && opts.llmModel) {
|
|
5515
|
+
opts.renderer.write(color.dim("Re-refining goal with LLM\u2026"));
|
|
5516
|
+
refined = await refineGoal(current.goal, opts.llmProvider, opts.llmModel);
|
|
5517
|
+
}
|
|
5518
|
+
if (!refined) {
|
|
5519
|
+
refined = refineGoalHeuristic(current.goal);
|
|
5520
|
+
}
|
|
5521
|
+
const updated = {
|
|
5522
|
+
...current,
|
|
5523
|
+
refinedGoal: refined.refinedGoal,
|
|
5524
|
+
deliverables: refined.deliverables
|
|
5525
|
+
};
|
|
5526
|
+
await saveGoal(goalPath, updated);
|
|
5527
|
+
const msg = `${color.green("\u2713")} Goal re-refined with ${refined.deliverables.length} deliverables.`;
|
|
5242
5528
|
opts.renderer.write(msg);
|
|
5243
|
-
return { message: msg
|
|
5529
|
+
return { message: `${msg}
|
|
5530
|
+
|
|
5531
|
+
${formatGoal(updated)}` };
|
|
5244
5532
|
}
|
|
5245
5533
|
case "clear":
|
|
5246
5534
|
case "reset": {
|
|
5247
|
-
const
|
|
5248
|
-
if (!
|
|
5535
|
+
const current = await loadGoal(goalPath);
|
|
5536
|
+
if (!current) {
|
|
5249
5537
|
const msg2 = "No goal to clear.";
|
|
5250
5538
|
opts.renderer.write(msg2);
|
|
5251
5539
|
return { message: msg2 };
|
|
5252
5540
|
}
|
|
5253
|
-
const abandoned = { ...
|
|
5541
|
+
const abandoned = { ...current, goalState: "abandoned" };
|
|
5254
5542
|
await saveGoal(goalPath, abandoned);
|
|
5255
5543
|
const { unlink: unlink4 } = await import('fs/promises');
|
|
5256
5544
|
try {
|
|
@@ -5325,7 +5613,7 @@ ${lines.join("\n")}`;
|
|
|
5325
5613
|
return { message: msg };
|
|
5326
5614
|
}
|
|
5327
5615
|
default: {
|
|
5328
|
-
const msg = `Unknown subcommand "${verb}". Try: show | set <text> | clear | journal [N]`;
|
|
5616
|
+
const msg = `Unknown subcommand "${verb}". Try: show | set <text> | refine | clear | journal [N]`;
|
|
5329
5617
|
opts.renderer.writeWarning(msg);
|
|
5330
5618
|
return { message: msg };
|
|
5331
5619
|
}
|
|
@@ -5424,17 +5712,11 @@ No project type auto-detected. Edit the file with project context and instructio
|
|
|
5424
5712
|
}
|
|
5425
5713
|
};
|
|
5426
5714
|
}
|
|
5427
|
-
function expectDefined2(value) {
|
|
5428
|
-
if (value === null || value === void 0) {
|
|
5429
|
-
throw new Error("Expected value to be defined");
|
|
5430
|
-
}
|
|
5431
|
-
return value;
|
|
5432
|
-
}
|
|
5433
5715
|
function parseMcpArgs(args) {
|
|
5434
5716
|
const trimmed = args.trim();
|
|
5435
5717
|
if (!trimmed || trimmed === "list") return { action: "list", name: "" };
|
|
5436
5718
|
const parts = trimmed.split(/\s+/);
|
|
5437
|
-
const action =
|
|
5719
|
+
const action = expectDefined(parts[0]);
|
|
5438
5720
|
const name = parts[1] ?? "";
|
|
5439
5721
|
const enable = parts.includes("--enable") || parts.includes("-e");
|
|
5440
5722
|
switch (action) {
|
|
@@ -5681,7 +5963,7 @@ function buildMemoryCommand(opts) {
|
|
|
5681
5963
|
return {
|
|
5682
5964
|
name: "memory",
|
|
5683
5965
|
category: "Inspect",
|
|
5684
|
-
description: "Inspect or edit persistent memory: /memory [show|remember <text>|forget <query>|clear]",
|
|
5966
|
+
description: "Inspect or edit persistent memory: /memory [show|remember <text>|forget <query>|clear|compact|stats]",
|
|
5685
5967
|
async run(args) {
|
|
5686
5968
|
const store = opts.memoryStore;
|
|
5687
5969
|
if (!store) return { message: "No memory store configured." };
|
|
@@ -5714,14 +5996,350 @@ function buildMemoryCommand(opts) {
|
|
|
5714
5996
|
await store.clear();
|
|
5715
5997
|
return { message: "Cleared all memory scopes." };
|
|
5716
5998
|
}
|
|
5999
|
+
case "compact": {
|
|
6000
|
+
return runCompact(opts);
|
|
6001
|
+
}
|
|
6002
|
+
case "stats": {
|
|
6003
|
+
return runStats(opts);
|
|
6004
|
+
}
|
|
5717
6005
|
default:
|
|
5718
6006
|
return {
|
|
5719
|
-
message: `Unknown subcommand "${verb}". Try: show | remember <text> | forget <query> | clear`
|
|
6007
|
+
message: `Unknown subcommand "${verb}". Try: show | remember <text> | forget <query> | clear | compact | stats`
|
|
5720
6008
|
};
|
|
5721
6009
|
}
|
|
5722
6010
|
}
|
|
5723
6011
|
};
|
|
5724
6012
|
}
|
|
6013
|
+
function buildCompactPrompt(entries) {
|
|
6014
|
+
const entriesBlock = entries.map(
|
|
6015
|
+
(e, i) => `${i + 1}. [${e.ts.slice(0, 10)}] ${e.id}
|
|
6016
|
+
${e.text}${e.tags ? `
|
|
6017
|
+
tags: ${e.tags.join(", ")}` : ""}${e.type ? `
|
|
6018
|
+
type: ${e.type}` : ""}${e.priority ? `
|
|
6019
|
+
priority: ${e.priority}` : ""}`
|
|
6020
|
+
).join("\n\n");
|
|
6021
|
+
return `You are a memory curator. Your task is to review, deduplicate, and improve a set of long-term memory entries.
|
|
6022
|
+
|
|
6023
|
+
These entries are injected into the context of an AI coding agent. Every token counts. The memory must be concise, accurate, and free of noise.
|
|
6024
|
+
|
|
6025
|
+
## Current Memory Entries
|
|
6026
|
+
|
|
6027
|
+
${entriesBlock}
|
|
6028
|
+
|
|
6029
|
+
## Your Task
|
|
6030
|
+
|
|
6031
|
+
Review each entry and return a JSON object with an "operations" array. Each operation targets one or more entries:
|
|
6032
|
+
|
|
6033
|
+
### Actions
|
|
6034
|
+
|
|
6035
|
+
- **"keep"** \u2014 The entry is valuable as-is. Include it in the operations so I know you reviewed it.
|
|
6036
|
+
- **"rewrite"** \u2014 The entry has value but needs better wording. Provide improved "newText". Target a single entry.
|
|
6037
|
+
- **"merge"** \u2014 Two or more entries say essentially the same thing. Combine them into one concise entry. The "targets" should list all entries being merged. Provide the combined "newText".
|
|
6038
|
+
- **"delete"** \u2014 The entry is obsolete, redundant, too vague, or not useful for future sessions. Target one or more entries.
|
|
6039
|
+
|
|
6040
|
+
### Rules
|
|
6041
|
+
|
|
6042
|
+
1. **Be ruthless about noise.** If an entry won't help a future AI agent do its job better, delete it.
|
|
6043
|
+
2. **Deduplicate aggressively.** Similar entries should be merged. Identical entries MUST be merged.
|
|
6044
|
+
3. **Keep entries concise.** Each entry should be one clear sentence. Remove filler words.
|
|
6045
|
+
4. **Preserve factual accuracy.** Don't change the meaning of entries unless they're wrong.
|
|
6046
|
+
5. **Handle every entry.** Every entry must appear in at least one operation (keep, rewrite, merge, or delete).
|
|
6047
|
+
6. **Prefer quality over quantity.** 10 excellent entries > 30 mediocre ones.
|
|
6048
|
+
7. **Tag entries appropriately.** If an entry mentions a technology or concept that could be tagged, suggest tags in the newText using #hashtag syntax.
|
|
6049
|
+
|
|
6050
|
+
### Response Format
|
|
6051
|
+
|
|
6052
|
+
Return ONLY valid JSON with this structure:
|
|
6053
|
+
|
|
6054
|
+
{
|
|
6055
|
+
"operations": [
|
|
6056
|
+
{ "action": "keep", "targets": ["mem_1234_abcd"], "reason": "Clear and useful" },
|
|
6057
|
+
{ "action": "rewrite", "targets": ["mem_5678_ef01"], "newText": "Project uses pnpm v9 with ESM-only modules #pnpm #esm", "reason": "Added version and ESM detail" },
|
|
6058
|
+
{ "action": "merge", "targets": ["mem_aaaa_1111", "mem_bbbb_2222"], "newText": "All packages use TypeScript strict mode with noUncheckedIndexedAccess #typescript", "reason": "Two entries about TS config, merged" },
|
|
6059
|
+
{ "action": "delete", "targets": ["mem_cccc_3333"], "reason": "Obsolete \u2014 was a temporary debug note" }
|
|
6060
|
+
],
|
|
6061
|
+
"summary": "Merged 2 TS entries, rewrote 1 for clarity, deleted 1 obsolete note. 12 entries \u2192 10 entries."
|
|
6062
|
+
}
|
|
6063
|
+
|
|
6064
|
+
Use the EXACT entry IDs from the list above for "targets". No markdown, no explanation outside the JSON.`;
|
|
6065
|
+
}
|
|
6066
|
+
async function runCompact(opts) {
|
|
6067
|
+
const store = opts.memoryStore;
|
|
6068
|
+
if (!store) return { message: "No memory store configured." };
|
|
6069
|
+
const entries = await store.list("project-memory");
|
|
6070
|
+
if (entries.length === 0) {
|
|
6071
|
+
return { message: "Memory is empty \u2014 nothing to compact." };
|
|
6072
|
+
}
|
|
6073
|
+
const raw = await store.read("project-memory");
|
|
6074
|
+
const compactEntries = parseCompactEntries(raw);
|
|
6075
|
+
if (compactEntries.length === 0) {
|
|
6076
|
+
return { message: "No parseable entries found." };
|
|
6077
|
+
}
|
|
6078
|
+
const provider = opts.llmProvider;
|
|
6079
|
+
if (!provider || !provider.complete) {
|
|
6080
|
+
return { message: "No LLM provider available. /memory compact requires an active session with a configured provider." };
|
|
6081
|
+
}
|
|
6082
|
+
const prompt = buildCompactPrompt(compactEntries);
|
|
6083
|
+
let responseText;
|
|
6084
|
+
try {
|
|
6085
|
+
const signal = AbortSignal.timeout(3e4);
|
|
6086
|
+
const response = await provider.complete(
|
|
6087
|
+
{
|
|
6088
|
+
model: opts.llmModel ?? "",
|
|
6089
|
+
system: [{ type: "text", text: prompt }],
|
|
6090
|
+
messages: [
|
|
6091
|
+
{
|
|
6092
|
+
role: "user",
|
|
6093
|
+
content: `Review the ${compactEntries.length} memory entries above and return operations as JSON.`
|
|
6094
|
+
}
|
|
6095
|
+
],
|
|
6096
|
+
maxTokens: 2e3,
|
|
6097
|
+
temperature: 0.1
|
|
6098
|
+
// low temperature for deterministic curation
|
|
6099
|
+
},
|
|
6100
|
+
{ signal }
|
|
6101
|
+
);
|
|
6102
|
+
responseText = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
6103
|
+
} catch (err) {
|
|
6104
|
+
return {
|
|
6105
|
+
message: `LLM call failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6106
|
+
};
|
|
6107
|
+
}
|
|
6108
|
+
if (!responseText) {
|
|
6109
|
+
return { message: "LLM returned empty response." };
|
|
6110
|
+
}
|
|
6111
|
+
let parsed;
|
|
6112
|
+
try {
|
|
6113
|
+
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
|
6114
|
+
if (!jsonMatch) {
|
|
6115
|
+
return { message: `LLM response is not valid JSON:
|
|
6116
|
+
${responseText.slice(0, 500)}` };
|
|
6117
|
+
}
|
|
6118
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
6119
|
+
} catch (err) {
|
|
6120
|
+
return {
|
|
6121
|
+
message: `Failed to parse LLM response: ${err instanceof Error ? err.message : String(err)}
|
|
6122
|
+
|
|
6123
|
+
Raw response:
|
|
6124
|
+
${responseText.slice(0, 500)}`
|
|
6125
|
+
};
|
|
6126
|
+
}
|
|
6127
|
+
if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) {
|
|
6128
|
+
return { message: "LLM returned no operations." };
|
|
6129
|
+
}
|
|
6130
|
+
let kept = 0;
|
|
6131
|
+
let rewritten = 0;
|
|
6132
|
+
let merged = 0;
|
|
6133
|
+
let deleted = 0;
|
|
6134
|
+
const errors = [];
|
|
6135
|
+
for (const op of parsed.operations) {
|
|
6136
|
+
try {
|
|
6137
|
+
switch (op.action) {
|
|
6138
|
+
case "keep": {
|
|
6139
|
+
kept += op.targets.length;
|
|
6140
|
+
break;
|
|
6141
|
+
}
|
|
6142
|
+
case "rewrite": {
|
|
6143
|
+
if (!op.newText) {
|
|
6144
|
+
errors.push(`rewrite missing newText for targets: ${op.targets.join(", ")}`);
|
|
6145
|
+
continue;
|
|
6146
|
+
}
|
|
6147
|
+
for (const target of op.targets) {
|
|
6148
|
+
await store.forget(target);
|
|
6149
|
+
}
|
|
6150
|
+
await store.remember(op.newText);
|
|
6151
|
+
rewritten++;
|
|
6152
|
+
break;
|
|
6153
|
+
}
|
|
6154
|
+
case "merge": {
|
|
6155
|
+
if (!op.newText) {
|
|
6156
|
+
errors.push(`merge missing newText for targets: ${op.targets.join(", ")}`);
|
|
6157
|
+
continue;
|
|
6158
|
+
}
|
|
6159
|
+
for (const target of op.targets) {
|
|
6160
|
+
await store.forget(target);
|
|
6161
|
+
}
|
|
6162
|
+
await store.remember(op.newText);
|
|
6163
|
+
merged++;
|
|
6164
|
+
break;
|
|
6165
|
+
}
|
|
6166
|
+
case "delete": {
|
|
6167
|
+
for (const target of op.targets) {
|
|
6168
|
+
await store.forget(target);
|
|
6169
|
+
}
|
|
6170
|
+
deleted += op.targets.length;
|
|
6171
|
+
break;
|
|
6172
|
+
}
|
|
6173
|
+
default: {
|
|
6174
|
+
errors.push(`unknown action "${op.action}"`);
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
} catch (err) {
|
|
6178
|
+
errors.push(
|
|
6179
|
+
`${op.action} failed for ${op.targets.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
const lines = ["## Memory Compact \u2014 Complete"];
|
|
6184
|
+
const stats = [];
|
|
6185
|
+
if (kept > 0) stats.push(`${kept} kept`);
|
|
6186
|
+
if (rewritten > 0) stats.push(`${rewritten} rewritten`);
|
|
6187
|
+
if (merged > 0) stats.push(`${merged} merged`);
|
|
6188
|
+
if (deleted > 0) stats.push(`${deleted} deleted`);
|
|
6189
|
+
lines.push(`**Result:** ${stats.join(", ")}`);
|
|
6190
|
+
lines.push(`**Before:** ${compactEntries.length} entries \u2192 **After:** ${kept + rewritten + merged} entries`);
|
|
6191
|
+
if (parsed.summary) {
|
|
6192
|
+
lines.push("");
|
|
6193
|
+
lines.push(parsed.summary);
|
|
6194
|
+
}
|
|
6195
|
+
lines.push("");
|
|
6196
|
+
lines.push("### Operations");
|
|
6197
|
+
for (const op of parsed.operations) {
|
|
6198
|
+
const icon = op.action === "keep" ? "\u2713" : op.action === "rewrite" ? "\u270F\uFE0F" : op.action === "merge" ? "\u{1F500}" : op.action === "delete" ? "\u2717" : "?";
|
|
6199
|
+
const detail = op.newText ? ` \u2192 "${op.newText}"` : "";
|
|
6200
|
+
lines.push(`- ${icon} **${op.action}** ${op.targets.join(", ")}${detail}`);
|
|
6201
|
+
if (op.reason) lines.push(` _${op.reason}_`);
|
|
6202
|
+
}
|
|
6203
|
+
if (errors.length > 0) {
|
|
6204
|
+
lines.push("");
|
|
6205
|
+
lines.push("### Errors");
|
|
6206
|
+
for (const err of errors) {
|
|
6207
|
+
lines.push(`- \u26A0\uFE0F ${err}`);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
return { message: lines.join("\n") };
|
|
6211
|
+
}
|
|
6212
|
+
function parseCompactEntries(raw) {
|
|
6213
|
+
const entries = [];
|
|
6214
|
+
for (const line of raw.split("\n")) {
|
|
6215
|
+
const trimmed = line.trim();
|
|
6216
|
+
if (!trimmed.startsWith("- [")) continue;
|
|
6217
|
+
const idMatch = trimmed.match(/mem_(\d+_\w+)/);
|
|
6218
|
+
if (!idMatch) continue;
|
|
6219
|
+
const id = idMatch[0] ?? "";
|
|
6220
|
+
const afterId = trimmed.slice((idMatch.index ?? 0) + id.length).trim();
|
|
6221
|
+
const tsMatch = trimmed.match(/^-\s*\[([^\]]+)\]/);
|
|
6222
|
+
const ts = tsMatch?.[1] ?? "";
|
|
6223
|
+
const tags = [];
|
|
6224
|
+
const tagRe = /#([\w-]+)/g;
|
|
6225
|
+
let tm;
|
|
6226
|
+
while ((tm = tagRe.exec(afterId)) !== null) {
|
|
6227
|
+
tags.push(tm[1] ?? "");
|
|
6228
|
+
}
|
|
6229
|
+
const text = afterId.replace(tagRe, "").replace(/\s{2,}/g, " ").trim();
|
|
6230
|
+
if (!text) continue;
|
|
6231
|
+
entries.push({ id, text, ts, tags: tags.length > 0 ? tags : void 0 });
|
|
6232
|
+
}
|
|
6233
|
+
return entries;
|
|
6234
|
+
}
|
|
6235
|
+
async function runStats(opts) {
|
|
6236
|
+
const store = opts.memoryStore;
|
|
6237
|
+
if (!store) return { message: "No memory store configured." };
|
|
6238
|
+
const entries = await store.list("project-memory");
|
|
6239
|
+
if (entries.length === 0) {
|
|
6240
|
+
return { message: "\u{1F4CA} Memory is empty. Start adding entries with `/memory remember <text>`." };
|
|
6241
|
+
}
|
|
6242
|
+
const now = Date.now();
|
|
6243
|
+
const lines = ["## \u{1F4CA} Memory Stats"];
|
|
6244
|
+
const raw = await store.read("project-memory");
|
|
6245
|
+
const byteSize = Buffer.byteLength(raw, "utf8");
|
|
6246
|
+
const kbSize = (byteSize / 1024).toFixed(1);
|
|
6247
|
+
const maxKb = (32e3 / 1024).toFixed(1);
|
|
6248
|
+
const pctFull = (byteSize / 32e3 * 100).toFixed(0);
|
|
6249
|
+
lines.push(`**Total:** ${entries.length} entries \xB7 ${kbSize} KB / ${maxKb} KB (${pctFull}%)`);
|
|
6250
|
+
const byType = /* @__PURE__ */ new Map();
|
|
6251
|
+
for (const e of entries) {
|
|
6252
|
+
const t = e.type ?? "untyped";
|
|
6253
|
+
byType.set(t, (byType.get(t) ?? 0) + 1);
|
|
6254
|
+
}
|
|
6255
|
+
if (byType.size > 0) {
|
|
6256
|
+
lines.push("");
|
|
6257
|
+
lines.push("### By Type");
|
|
6258
|
+
const typeOrder = ["convention", "decision", "fact", "preference", "reference", "anti_pattern", "untyped"];
|
|
6259
|
+
for (const t of typeOrder) {
|
|
6260
|
+
const count = byType.get(t);
|
|
6261
|
+
if (count) {
|
|
6262
|
+
const bar = "\u2588".repeat(Math.min(count, 20));
|
|
6263
|
+
lines.push(`- \`${t}\` ${bar} ${count}`);
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
const byPriority = /* @__PURE__ */ new Map();
|
|
6268
|
+
for (const e of entries) {
|
|
6269
|
+
const p = e.priority ?? "unset";
|
|
6270
|
+
byPriority.set(p, (byPriority.get(p) ?? 0) + 1);
|
|
6271
|
+
}
|
|
6272
|
+
if (byPriority.size > 0) {
|
|
6273
|
+
lines.push("");
|
|
6274
|
+
lines.push("### By Priority");
|
|
6275
|
+
const icon = { critical: "\u26A1", high: "\u25B2", medium: "\u25CF", low: "\u25CB", unset: "\xB7" };
|
|
6276
|
+
for (const [p, count] of [...byPriority.entries()].sort((a, b) => b[1] - a[1])) {
|
|
6277
|
+
lines.push(`- ${icon[p] ?? "\xB7"} \`${p}\`: ${count}`);
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
const ages = entries.map((e) => {
|
|
6281
|
+
const ageDays = (now - new Date(e.ts).getTime()) / (1e3 * 60 * 60 * 24);
|
|
6282
|
+
if (ageDays < 1) return "<1d";
|
|
6283
|
+
if (ageDays < 7) return "<7d";
|
|
6284
|
+
if (ageDays < 30) return "<30d";
|
|
6285
|
+
return ">30d";
|
|
6286
|
+
});
|
|
6287
|
+
const byAge = /* @__PURE__ */ new Map();
|
|
6288
|
+
for (const a of ages) byAge.set(a, (byAge.get(a) ?? 0) + 1);
|
|
6289
|
+
lines.push("");
|
|
6290
|
+
lines.push("### By Age");
|
|
6291
|
+
for (const age of ["<1d", "<7d", "<30d", ">30d"]) {
|
|
6292
|
+
const actual = byAge.get(age) ?? 0;
|
|
6293
|
+
if (actual > 0 || age === "<7d") {
|
|
6294
|
+
lines.push(`- ${age}: ${actual}`);
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
const tagCounts = /* @__PURE__ */ new Map();
|
|
6298
|
+
for (const e of entries) {
|
|
6299
|
+
for (const t of e.tags ?? []) {
|
|
6300
|
+
tagCounts.set(t, (tagCounts.get(t) ?? 0) + 1);
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
if (tagCounts.size > 0) {
|
|
6304
|
+
lines.push("");
|
|
6305
|
+
lines.push("### Top Tags");
|
|
6306
|
+
const sorted = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
6307
|
+
for (const [tag, count] of sorted) {
|
|
6308
|
+
lines.push(`- \`#${tag}\`: ${count}`);
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
lines.push("");
|
|
6312
|
+
lines.push("### Health");
|
|
6313
|
+
const untyped = byType.get("untyped") ?? 0;
|
|
6314
|
+
const unsetPriority = byPriority.get("unset") ?? 0;
|
|
6315
|
+
const old = byAge.get(">30d") ?? 0;
|
|
6316
|
+
if (untyped > entries.length * 0.5) {
|
|
6317
|
+
lines.push(`- \u26A0\uFE0F ${untyped}/${entries.length} entries have no type \u2014 run \`/memory compact\` to categorize`);
|
|
6318
|
+
} else if (untyped > 0) {
|
|
6319
|
+
lines.push(`- \u2139\uFE0F ${untyped} entries untyped \u2014 consider categorizing`);
|
|
6320
|
+
} else {
|
|
6321
|
+
lines.push("- \u2705 All entries have types");
|
|
6322
|
+
}
|
|
6323
|
+
if (unsetPriority > entries.length * 0.5) {
|
|
6324
|
+
lines.push(`- \u26A0\uFE0F ${unsetPriority}/${entries.length} entries have no priority`);
|
|
6325
|
+
} else if (unsetPriority > 0) {
|
|
6326
|
+
lines.push(`- \u2139\uFE0F ${unsetPriority} entries have no priority set`);
|
|
6327
|
+
} else {
|
|
6328
|
+
lines.push("- \u2705 All entries have priorities");
|
|
6329
|
+
}
|
|
6330
|
+
if (old > 5) {
|
|
6331
|
+
lines.push(`- \u26A0\uFE0F ${old} entries older than 30 days \u2014 run \`/memory compact\` to review`);
|
|
6332
|
+
}
|
|
6333
|
+
const pct2 = Number.parseInt(pctFull);
|
|
6334
|
+
if (pct2 > 80) {
|
|
6335
|
+
lines.push(`- \u26A0\uFE0F Storage ${pct2}% full \u2014 run \`/memory compact\` to free space`);
|
|
6336
|
+
} else {
|
|
6337
|
+
lines.push(`- \u2705 Storage ${pct2}% full \u2014 healthy`);
|
|
6338
|
+
}
|
|
6339
|
+
lines.push("");
|
|
6340
|
+
lines.push("**Commands:** `/memory show` \xB7 `/memory compact` \xB7 `/memory remember <text>`");
|
|
6341
|
+
return { message: lines.join("\n") };
|
|
6342
|
+
}
|
|
5725
6343
|
async function runModePicker(modeStore, reader) {
|
|
5726
6344
|
const modes = await modeStore.listModes();
|
|
5727
6345
|
const active = await modeStore.getActiveMode();
|
|
@@ -6397,12 +7015,6 @@ function summariseEvent(ev) {
|
|
|
6397
7015
|
return color.dim("\u2026");
|
|
6398
7016
|
}
|
|
6399
7017
|
}
|
|
6400
|
-
function expectDefined5(value) {
|
|
6401
|
-
if (value === null || value === void 0) {
|
|
6402
|
-
throw new Error("Expected value to be defined");
|
|
6403
|
-
}
|
|
6404
|
-
return value;
|
|
6405
|
-
}
|
|
6406
7018
|
var noOpVault3 = {
|
|
6407
7019
|
encrypt: (v) => v,
|
|
6408
7020
|
decrypt: (v) => v,
|
|
@@ -6464,12 +7076,14 @@ async function patchGlobalConfig2(globalConfigPath, mutate) {
|
|
|
6464
7076
|
function buildSetModelCommand(opts) {
|
|
6465
7077
|
const help = [
|
|
6466
7078
|
"Usage:",
|
|
6467
|
-
" /setmodel Show leader model +
|
|
7079
|
+
" /setmodel Show leader model + matrix + resolution summary",
|
|
6468
7080
|
" /setmodel list List keyed providers, their models, and valid keys",
|
|
6469
|
-
" /setmodel leader <provider> <model> Set the main (leader) model",
|
|
7081
|
+
" /setmodel leader <provider> <model> Set the main (leader / brain) model",
|
|
6470
7082
|
" /setmodel set <key> <provider>/<model> Pin a role/phase/* to a model",
|
|
6471
7083
|
" /setmodel set <key> <model> Pin to a model on the leader provider",
|
|
6472
7084
|
" /setmodel clear <key> Remove a matrix entry",
|
|
7085
|
+
" /setmodel resolve <role> Walk the resolution chain for one role",
|
|
7086
|
+
" /setmodel doctor Validate matrix entries (orphans, typos, missing keys)",
|
|
6473
7087
|
"",
|
|
6474
7088
|
"Keys: a catalog role (e.g. security-scanner), a phase (" + MATRIX_PHASE_KEYS.join(", ") + "),",
|
|
6475
7089
|
"or * for the fleet-wide default. Precedence at spawn: role \u2192 phase \u2192 * \u2192 leader.",
|
|
@@ -6483,24 +7097,57 @@ function buildSetModelCommand(opts) {
|
|
|
6483
7097
|
const lines = [
|
|
6484
7098
|
`${color.bold("WrongStack")} ${color.dim("\u2014 Models")}`,
|
|
6485
7099
|
"",
|
|
6486
|
-
` leader
|
|
6487
|
-
""
|
|
6488
|
-
` ${color.bold("task \u2192 model matrix")} ${color.dim("(role \u2192 phase \u2192 * \u2192 leader)")}`
|
|
7100
|
+
` ${color.bold("leader")} ${color.cyan(`${config.provider}/${config.model}`)} ${color.dim("/setmodel leader <provider> <model>")}`,
|
|
7101
|
+
""
|
|
6489
7102
|
];
|
|
6490
7103
|
if (keys.length === 0) {
|
|
6491
7104
|
lines.push(
|
|
6492
|
-
`
|
|
7105
|
+
` ${color.bold("matrix")} ${color.dim("(empty)")}`,
|
|
7106
|
+
` ${color.dim("pin a role: /setmodel set <role> <provider>/<model>")}`,
|
|
7107
|
+
` ${color.dim("set default: /setmodel set * <provider>/<model>")}`
|
|
6493
7108
|
);
|
|
6494
7109
|
} else {
|
|
7110
|
+
lines.push(` ${color.bold("matrix")} ${color.dim("(role \u2192 phase \u2192 * \u2192 leader)")}`);
|
|
6495
7111
|
for (const k of keys.sort()) {
|
|
6496
7112
|
const kind = matrixKeyKind(k);
|
|
6497
7113
|
const tag = kind === "unknown" ? color.red("?") : color.dim(kind);
|
|
6498
|
-
lines.push(` ${color.amber(k.padEnd(22))} \u2192 ${fmtEntry(
|
|
7114
|
+
lines.push(` ${color.amber(k.padEnd(22))} \u2192 ${fmtEntry(expectDefined(matrix[k]))} ${tag}`);
|
|
7115
|
+
}
|
|
7116
|
+
}
|
|
7117
|
+
const summaryRoles = getSummaryRoles();
|
|
7118
|
+
if (summaryRoles.length > 0) {
|
|
7119
|
+
lines.push("");
|
|
7120
|
+
lines.push(` ${color.bold("resolution")} ${color.dim("(selected roles)")}`);
|
|
7121
|
+
for (const role of summaryRoles) {
|
|
7122
|
+
const entry = resolveModelMatrix(matrix, role);
|
|
7123
|
+
const provider = entry?.provider ?? config.provider;
|
|
7124
|
+
const model = entry?.model ?? config.model;
|
|
7125
|
+
const source = resolutionSource(matrix, role);
|
|
7126
|
+
lines.push(` ${color.dim(role.padEnd(22))} \u2192 ${color.cyan(`${provider}/${model}`)} ${color.dim(source)}`);
|
|
6499
7127
|
}
|
|
6500
7128
|
}
|
|
6501
|
-
lines.push("", color.dim(" /setmodel list
|
|
7129
|
+
lines.push("", color.dim(" /setmodel list \xB7 resolve <role> \xB7 doctor \xB7 help"));
|
|
6502
7130
|
return lines.join("\n");
|
|
6503
7131
|
}
|
|
7132
|
+
function getSummaryRoles() {
|
|
7133
|
+
const picks = [];
|
|
7134
|
+
for (const phase of MATRIX_PHASE_KEYS) {
|
|
7135
|
+
const agents = AGENTS_BY_PHASE[phase];
|
|
7136
|
+
if (agents && agents.length > 0) {
|
|
7137
|
+
picks.push(agents[0].config.role);
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
picks.push("security-scanner", "bug-hunter");
|
|
7141
|
+
return [...new Set(picks)].sort();
|
|
7142
|
+
}
|
|
7143
|
+
function resolutionSource(matrix, role) {
|
|
7144
|
+
if (!matrix) return "leader";
|
|
7145
|
+
if (matrix[role]) return "role";
|
|
7146
|
+
const phase = phaseForRole(role);
|
|
7147
|
+
if (phase && matrix[phase]) return `phase (${phase})`;
|
|
7148
|
+
if (matrix["*"]) return "default (*)";
|
|
7149
|
+
return "leader";
|
|
7150
|
+
}
|
|
6504
7151
|
return {
|
|
6505
7152
|
name: "setmodel",
|
|
6506
7153
|
category: "Config",
|
|
@@ -6517,6 +7164,7 @@ function buildSetModelCommand(opts) {
|
|
|
6517
7164
|
const config = opts.configStore.get();
|
|
6518
7165
|
const keyed = keyedProviderIds(config);
|
|
6519
7166
|
const globalConfigPath = opts.paths.globalConfig;
|
|
7167
|
+
const matrix = config.modelMatrix ?? {};
|
|
6520
7168
|
if (sub === "list") {
|
|
6521
7169
|
const provLines = keyed.map((id) => {
|
|
6522
7170
|
const models = config.providers?.[id]?.models ?? [];
|
|
@@ -6537,6 +7185,119 @@ function buildSetModelCommand(opts) {
|
|
|
6537
7185
|
].join("\n")
|
|
6538
7186
|
};
|
|
6539
7187
|
}
|
|
7188
|
+
if (sub === "resolve") {
|
|
7189
|
+
const role = parts[1];
|
|
7190
|
+
if (!role) {
|
|
7191
|
+
return { message: `${color.amber("Usage:")} /setmodel resolve <role>` };
|
|
7192
|
+
}
|
|
7193
|
+
const kind = matrixKeyKind(role);
|
|
7194
|
+
if (kind === "unknown" && role !== "*") {
|
|
7195
|
+
return {
|
|
7196
|
+
message: `${color.red("Unknown role")}: "${role}". Use ${color.dim("/setmodel list")} to see valid roles.`
|
|
7197
|
+
};
|
|
7198
|
+
}
|
|
7199
|
+
const lines = [
|
|
7200
|
+
`${color.bold("Resolution chain")} for ${color.amber(role)}`,
|
|
7201
|
+
""
|
|
7202
|
+
];
|
|
7203
|
+
const phase = phaseForRole(role);
|
|
7204
|
+
const resolved = resolveModelMatrix(matrix, role);
|
|
7205
|
+
if (matrix[role]) {
|
|
7206
|
+
lines.push(` 1. matrix["${role}"] \u2192 ${fmtEntry(expectDefined(matrix[role]))} ${color.green("\u2713 exact role")}`);
|
|
7207
|
+
} else {
|
|
7208
|
+
lines.push(` 1. matrix["${role}"] \u2192 ${color.dim("not set")}`);
|
|
7209
|
+
}
|
|
7210
|
+
if (phase) {
|
|
7211
|
+
if (matrix[phase]) {
|
|
7212
|
+
lines.push(` 2. matrix["${phase}"] \u2192 ${fmtEntry(expectDefined(matrix[phase]))} ${matrix[role] ? color.dim("(skipped \u2014 role matched)") : color.green("\u2713 phase match")}`);
|
|
7213
|
+
} else {
|
|
7214
|
+
lines.push(` 2. matrix["${phase}"] \u2192 ${color.dim("not set")}`);
|
|
7215
|
+
}
|
|
7216
|
+
}
|
|
7217
|
+
if (matrix["*"]) {
|
|
7218
|
+
const skipped = matrix[role] || phase && matrix[phase];
|
|
7219
|
+
lines.push(` 3. matrix["*"] \u2192 ${fmtEntry(expectDefined(matrix["*"]))} ${skipped ? color.dim("(skipped)") : color.green("\u2713 default")}`);
|
|
7220
|
+
} else {
|
|
7221
|
+
lines.push(` 3. matrix["*"] \u2192 ${color.dim("not set")}`);
|
|
7222
|
+
}
|
|
7223
|
+
const leaderSkipped = matrix[role] || phase && matrix[phase] || matrix["*"];
|
|
7224
|
+
lines.push(` 4. ${color.dim("leader fallback")} \u2192 ${color.cyan(`${config.provider}/${config.model}`)} ${leaderSkipped ? color.dim("(skipped)") : color.green("\u2713 used")}`);
|
|
7225
|
+
lines.push("");
|
|
7226
|
+
if (resolved) {
|
|
7227
|
+
const rp = resolved.provider ?? config.provider;
|
|
7228
|
+
lines.push(`${color.green("\u2713 Resolved")}: ${color.cyan(`${rp}/${resolved.model}`)}`);
|
|
7229
|
+
} else {
|
|
7230
|
+
lines.push(`${color.green("\u2713 Resolved")}: ${color.cyan(`${config.provider}/${config.model}`)} ${color.dim("(leader)")}`);
|
|
7231
|
+
}
|
|
7232
|
+
return { message: lines.join("\n") };
|
|
7233
|
+
}
|
|
7234
|
+
if (sub === "doctor") {
|
|
7235
|
+
const issues = [];
|
|
7236
|
+
const warnings = [];
|
|
7237
|
+
for (const [key, entry] of Object.entries(matrix)) {
|
|
7238
|
+
const kind = matrixKeyKind(key);
|
|
7239
|
+
if (kind === "unknown") {
|
|
7240
|
+
issues.push(
|
|
7241
|
+
`${color.red("\u2717")} ${color.amber(key)}: not a valid role, phase, or * \u2014 ${color.dim("typo or stale entry?")}`
|
|
7242
|
+
);
|
|
7243
|
+
}
|
|
7244
|
+
if (entry.provider) {
|
|
7245
|
+
const provCfg2 = config.providers?.[entry.provider];
|
|
7246
|
+
if (!provCfg2) {
|
|
7247
|
+
issues.push(
|
|
7248
|
+
`${color.red("\u2717")} ${color.amber(key)}: provider "${entry.provider}" is not configured`
|
|
7249
|
+
);
|
|
7250
|
+
} else if (!providerHasKey(provCfg2)) {
|
|
7251
|
+
warnings.push(
|
|
7252
|
+
`${color.amber("\u26A0")} ${color.amber(key)}: provider "${entry.provider}" has no API key`
|
|
7253
|
+
);
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
const effectiveProvider = entry.provider ?? config.provider;
|
|
7257
|
+
const provCfg = config.providers?.[effectiveProvider];
|
|
7258
|
+
if (provCfg?.models && provCfg.models.length > 0) {
|
|
7259
|
+
if (!provCfg.models.includes(entry.model)) {
|
|
7260
|
+
warnings.push(
|
|
7261
|
+
`${color.amber("\u26A0")} ${color.amber(key)}: model "${entry.model}" not in ${effectiveProvider}'s model list (${provCfg.models.join(", ")})`
|
|
7262
|
+
);
|
|
7263
|
+
}
|
|
7264
|
+
}
|
|
7265
|
+
}
|
|
7266
|
+
if (Object.keys(matrix).length > 0 && !matrix["*"]) {
|
|
7267
|
+
const covered = /* @__PURE__ */ new Set();
|
|
7268
|
+
for (const [key] of Object.entries(matrix)) {
|
|
7269
|
+
covered.add(key);
|
|
7270
|
+
}
|
|
7271
|
+
const phasesCovered = new Set(Object.keys(matrix).filter((k) => matrixKeyKind(k) === "phase"));
|
|
7272
|
+
const unprotected = [];
|
|
7273
|
+
for (const role of Object.keys(AGENT_CATALOG)) {
|
|
7274
|
+
if (covered.has(role)) continue;
|
|
7275
|
+
const ph = phaseForRole(role);
|
|
7276
|
+
if (ph && phasesCovered.has(ph)) continue;
|
|
7277
|
+
unprotected.push(role);
|
|
7278
|
+
}
|
|
7279
|
+
if (unprotected.length > 0) {
|
|
7280
|
+
const sample = unprotected.slice(0, 10);
|
|
7281
|
+
const suffix = unprotected.length > 10 ? ` +${unprotected.length - 10} more` : "";
|
|
7282
|
+
warnings.push(
|
|
7283
|
+
`${color.amber("\u26A0")} ${unprotected.length} role(s) have no matrix coverage and no * default: ${sample.join(", ")}${suffix}`
|
|
7284
|
+
);
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
const header = [
|
|
7288
|
+
`${color.bold("Matrix Doctor")} ${color.dim("\u2014 " + Object.keys(matrix).length + " entries")}`,
|
|
7289
|
+
""
|
|
7290
|
+
];
|
|
7291
|
+
if (issues.length === 0 && warnings.length === 0) {
|
|
7292
|
+
header.push(`${color.green("\u2713")} All matrix entries are valid. No issues found.`);
|
|
7293
|
+
}
|
|
7294
|
+
const allLines = [
|
|
7295
|
+
...header,
|
|
7296
|
+
...issues.length ? ["", `${color.bold("Issues")}:`, ...issues] : [],
|
|
7297
|
+
...warnings.length ? ["", `${color.bold("Warnings")}:`, ...warnings] : []
|
|
7298
|
+
];
|
|
7299
|
+
return { message: allLines.join("\n") };
|
|
7300
|
+
}
|
|
6540
7301
|
try {
|
|
6541
7302
|
if (sub === "leader") {
|
|
6542
7303
|
const provider = parts[1];
|
|
@@ -6581,9 +7342,9 @@ function buildSetModelCommand(opts) {
|
|
|
6581
7342
|
};
|
|
6582
7343
|
}
|
|
6583
7344
|
const decrypted = await patchGlobalConfig2(globalConfigPath, (cfg) => {
|
|
6584
|
-
const
|
|
6585
|
-
|
|
6586
|
-
cfg.modelMatrix =
|
|
7345
|
+
const matrix2 = { ...cfg.modelMatrix ?? {} };
|
|
7346
|
+
matrix2[key] = parsed.provider ? { provider: parsed.provider, model: parsed.model } : { model: parsed.model };
|
|
7347
|
+
cfg.modelMatrix = matrix2;
|
|
6587
7348
|
});
|
|
6588
7349
|
opts.configStore.update({
|
|
6589
7350
|
modelMatrix: decrypted.modelMatrix
|
|
@@ -6598,9 +7359,9 @@ function buildSetModelCommand(opts) {
|
|
|
6598
7359
|
return { message: `${color.amber("No matrix entry")} for "${key}".` };
|
|
6599
7360
|
}
|
|
6600
7361
|
const decrypted = await patchGlobalConfig2(globalConfigPath, (cfg) => {
|
|
6601
|
-
const
|
|
6602
|
-
delete
|
|
6603
|
-
cfg.modelMatrix =
|
|
7362
|
+
const matrix2 = { ...cfg.modelMatrix ?? {} };
|
|
7363
|
+
delete matrix2[key];
|
|
7364
|
+
cfg.modelMatrix = matrix2;
|
|
6604
7365
|
});
|
|
6605
7366
|
opts.configStore.update({
|
|
6606
7367
|
modelMatrix: decrypted.modelMatrix
|
|
@@ -7169,6 +7930,198 @@ function buildTodosCommand(opts) {
|
|
|
7169
7930
|
}
|
|
7170
7931
|
};
|
|
7171
7932
|
}
|
|
7933
|
+
function findTask(tasks, query) {
|
|
7934
|
+
const asIndex = Number.parseInt(query, 10);
|
|
7935
|
+
if (!Number.isNaN(asIndex)) {
|
|
7936
|
+
const idx = asIndex - 1;
|
|
7937
|
+
const item = tasks[idx];
|
|
7938
|
+
if (item) return { idx, item };
|
|
7939
|
+
}
|
|
7940
|
+
const byId = tasks.findIndex((t) => t.id === query);
|
|
7941
|
+
if (byId >= 0) {
|
|
7942
|
+
const item = tasks[byId];
|
|
7943
|
+
if (item) return { idx: byId, item };
|
|
7944
|
+
}
|
|
7945
|
+
const q = query.toLowerCase();
|
|
7946
|
+
const byTitle = tasks.findIndex((t) => t.title.toLowerCase().includes(q));
|
|
7947
|
+
if (byTitle >= 0) {
|
|
7948
|
+
const item = tasks[byTitle];
|
|
7949
|
+
if (item) return { idx: byTitle, item };
|
|
7950
|
+
}
|
|
7951
|
+
return null;
|
|
7952
|
+
}
|
|
7953
|
+
function validateType(s) {
|
|
7954
|
+
const valid = ["feature", "bugfix", "refactor", "docs", "test", "chore"];
|
|
7955
|
+
return valid.includes(s) ? s : null;
|
|
7956
|
+
}
|
|
7957
|
+
function validatePriority(s) {
|
|
7958
|
+
const valid = ["critical", "high", "medium", "low"];
|
|
7959
|
+
return valid.includes(s) ? s : null;
|
|
7960
|
+
}
|
|
7961
|
+
function validateStatus(s) {
|
|
7962
|
+
const valid = ["pending", "in_progress", "blocked", "failed", "review", "completed"];
|
|
7963
|
+
return valid.includes(s) ? s : null;
|
|
7964
|
+
}
|
|
7965
|
+
function buildTasksCommand(_opts) {
|
|
7966
|
+
return {
|
|
7967
|
+
name: "tasks",
|
|
7968
|
+
category: "Inspect",
|
|
7969
|
+
description: "Manage structured tasks with dependencies, types, and priorities: /tasks [show|add <title>|start|done|fail|status <id> <status>|promote <id>|clear]",
|
|
7970
|
+
help: [
|
|
7971
|
+
"Usage:",
|
|
7972
|
+
" /tasks Show task progress + list",
|
|
7973
|
+
" /tasks show Same as no args",
|
|
7974
|
+
" /tasks add <title> [type] [prio] Add a task",
|
|
7975
|
+
" /tasks start <id|index> Mark task in-progress",
|
|
7976
|
+
" /tasks done <id|index> Mark task completed",
|
|
7977
|
+
" /tasks fail <id|index> Mark task failed",
|
|
7978
|
+
" /tasks status <id> <status> Set exact status (pending|in_progress|blocked|review|completed|failed)",
|
|
7979
|
+
" /tasks depends <id> <depId...> Set dependencies for a task",
|
|
7980
|
+
" /tasks assign <id> <agent> Assign task to an agent/subagent",
|
|
7981
|
+
" /tasks promote <id> Promote task to todo items",
|
|
7982
|
+
" /tasks clear Remove all tasks",
|
|
7983
|
+
"",
|
|
7984
|
+
"Types: feature, bugfix, refactor, docs, test, chore",
|
|
7985
|
+
"Priorities: critical, high, medium, low"
|
|
7986
|
+
].join("\n"),
|
|
7987
|
+
async run(args, ctx) {
|
|
7988
|
+
const taskPath = ctx.meta?.["task.path"];
|
|
7989
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
7990
|
+
return { message: "Task storage is not configured for this session." };
|
|
7991
|
+
}
|
|
7992
|
+
const sessionId = ctx.session?.id ?? "unknown";
|
|
7993
|
+
const file = await loadTasks(taskPath) ?? emptyTaskFile(sessionId);
|
|
7994
|
+
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
7995
|
+
const restJoined = rest.join(" ").trim();
|
|
7996
|
+
switch (verb) {
|
|
7997
|
+
case "":
|
|
7998
|
+
case "show":
|
|
7999
|
+
case "list":
|
|
8000
|
+
return { message: formatTaskList(file.tasks) };
|
|
8001
|
+
case "progress":
|
|
8002
|
+
case "statusline":
|
|
8003
|
+
return { message: formatTaskProgress(file.tasks) };
|
|
8004
|
+
case "add": {
|
|
8005
|
+
if (!restJoined) return { message: "Usage: /tasks add <title> [type] [priority]" };
|
|
8006
|
+
const parts = restJoined.split(/\s+/);
|
|
8007
|
+
const title = parts[0] ?? "";
|
|
8008
|
+
const type = validateType(parts[1] ?? "") ?? "feature";
|
|
8009
|
+
const priority = validatePriority(parts[2] ?? "") ?? "medium";
|
|
8010
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8011
|
+
const task = {
|
|
8012
|
+
id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8013
|
+
title,
|
|
8014
|
+
type,
|
|
8015
|
+
priority,
|
|
8016
|
+
status: "pending",
|
|
8017
|
+
createdAt: now,
|
|
8018
|
+
updatedAt: now
|
|
8019
|
+
};
|
|
8020
|
+
file.tasks.push(task);
|
|
8021
|
+
await saveTasks(taskPath, file);
|
|
8022
|
+
return { message: `Added: ${task.title}
|
|
8023
|
+
|
|
8024
|
+
${formatTaskProgress(file.tasks)}` };
|
|
8025
|
+
}
|
|
8026
|
+
case "start":
|
|
8027
|
+
case "done":
|
|
8028
|
+
case "fail": {
|
|
8029
|
+
if (!restJoined) return { message: `Usage: /tasks ${verb} <id|index>` };
|
|
8030
|
+
const found = findTask(file.tasks, restJoined);
|
|
8031
|
+
if (!found) return { message: `No task matched "${restJoined}".` };
|
|
8032
|
+
const statusMap = {
|
|
8033
|
+
start: "in_progress",
|
|
8034
|
+
done: "completed",
|
|
8035
|
+
fail: "failed"
|
|
8036
|
+
};
|
|
8037
|
+
found.item.status = statusMap[verb] ?? "pending";
|
|
8038
|
+
found.item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8039
|
+
await saveTasks(taskPath, file);
|
|
8040
|
+
return { message: `Marked ${verb}: ${found.item.title}
|
|
8041
|
+
|
|
8042
|
+
${formatTaskProgress(file.tasks)}` };
|
|
8043
|
+
}
|
|
8044
|
+
case "status": {
|
|
8045
|
+
if (rest.length < 2) return { message: "Usage: /tasks status <id> <pending|in_progress|blocked|review|completed|failed>" };
|
|
8046
|
+
const targetId = rest[0] ?? "";
|
|
8047
|
+
const newStatus = validateStatus(rest[1] ?? "");
|
|
8048
|
+
if (!newStatus) return { message: `Invalid status "${rest[1]}". Use: pending, in_progress, blocked, review, completed, failed.` };
|
|
8049
|
+
const found = findTask(file.tasks, targetId);
|
|
8050
|
+
if (!found) return { message: `No task matched "${targetId}".` };
|
|
8051
|
+
found.item.status = newStatus;
|
|
8052
|
+
found.item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8053
|
+
await saveTasks(taskPath, file);
|
|
8054
|
+
return { message: `Status \u2192 ${newStatus}: ${found.item.title}
|
|
8055
|
+
|
|
8056
|
+
${formatTaskProgress(file.tasks)}` };
|
|
8057
|
+
}
|
|
8058
|
+
case "depends":
|
|
8059
|
+
case "deps": {
|
|
8060
|
+
if (rest.length < 2) return { message: "Usage: /tasks depends <id> <depId1> [depId2 ...]" };
|
|
8061
|
+
const targetId = rest[0] ?? "";
|
|
8062
|
+
const depIds = rest.slice(1);
|
|
8063
|
+
const found = findTask(file.tasks, targetId);
|
|
8064
|
+
if (!found) return { message: `No task matched "${targetId}".` };
|
|
8065
|
+
found.item.dependsOn = depIds;
|
|
8066
|
+
found.item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8067
|
+
await saveTasks(taskPath, file);
|
|
8068
|
+
return { message: `Dependencies set for "${found.item.title}": ${depIds.join(", ")}` };
|
|
8069
|
+
}
|
|
8070
|
+
case "assign": {
|
|
8071
|
+
if (rest.length < 2) return { message: "Usage: /tasks assign <id> <agent>" };
|
|
8072
|
+
const targetId = rest[0] ?? "";
|
|
8073
|
+
const agent = rest.slice(1).join(" ");
|
|
8074
|
+
const found = findTask(file.tasks, targetId);
|
|
8075
|
+
if (!found) return { message: `No task matched "${targetId}".` };
|
|
8076
|
+
found.item.assignee = agent;
|
|
8077
|
+
found.item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8078
|
+
await saveTasks(taskPath, file);
|
|
8079
|
+
return { message: `Assigned to ${agent}: "${found.item.title}"` };
|
|
8080
|
+
}
|
|
8081
|
+
case "promote": {
|
|
8082
|
+
if (!restJoined) return { message: "Usage: /tasks promote <id|index>" };
|
|
8083
|
+
const found = findTask(file.tasks, restJoined);
|
|
8084
|
+
if (!found) return { message: `No task matched "${restJoined}".` };
|
|
8085
|
+
found.item.status = "in_progress";
|
|
8086
|
+
found.item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8087
|
+
const todos = [
|
|
8088
|
+
{
|
|
8089
|
+
id: `todo_${Date.now()}_task`,
|
|
8090
|
+
content: found.item.title,
|
|
8091
|
+
status: "in_progress",
|
|
8092
|
+
activeForm: found.item.title
|
|
8093
|
+
}
|
|
8094
|
+
];
|
|
8095
|
+
if (found.item.description) {
|
|
8096
|
+
todos.push({
|
|
8097
|
+
id: `todo_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8098
|
+
content: found.item.description.slice(0, 200),
|
|
8099
|
+
status: "pending"
|
|
8100
|
+
});
|
|
8101
|
+
}
|
|
8102
|
+
ctx.state.replaceTodos(todos);
|
|
8103
|
+
await saveTasks(taskPath, file);
|
|
8104
|
+
return {
|
|
8105
|
+
message: `Promoted to ${todos.length} todo(s): "${found.item.title}"
|
|
8106
|
+
|
|
8107
|
+
${formatTaskProgress(file.tasks)}`
|
|
8108
|
+
};
|
|
8109
|
+
}
|
|
8110
|
+
case "clear": {
|
|
8111
|
+
const n = file.tasks.length;
|
|
8112
|
+
if (n === 0) return { message: "Tasks were already empty." };
|
|
8113
|
+
file.tasks = [];
|
|
8114
|
+
await saveTasks(taskPath, file);
|
|
8115
|
+
return { message: `Cleared ${n} task${n === 1 ? "" : "s"}.` };
|
|
8116
|
+
}
|
|
8117
|
+
default:
|
|
8118
|
+
return {
|
|
8119
|
+
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id> | done <id> | fail <id> | status <id> <s> | depends <id> <deps> | assign <id> <agent> | promote <id> | clear`
|
|
8120
|
+
};
|
|
8121
|
+
}
|
|
8122
|
+
}
|
|
8123
|
+
};
|
|
8124
|
+
}
|
|
7172
8125
|
function buildToolsCommand(opts) {
|
|
7173
8126
|
return {
|
|
7174
8127
|
name: "tools",
|
|
@@ -7296,6 +8249,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
7296
8249
|
buildPluginCommand(opts),
|
|
7297
8250
|
buildPruneCommand(opts),
|
|
7298
8251
|
buildMcpSlashCommand(opts),
|
|
8252
|
+
buildAuthCommand(opts),
|
|
7299
8253
|
buildDiagCommand(opts),
|
|
7300
8254
|
buildStatsCommand(opts),
|
|
7301
8255
|
buildSpawnCommand(opts),
|
|
@@ -7305,6 +8259,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
7305
8259
|
buildEnhanceCommand(opts),
|
|
7306
8260
|
buildMemoryCommand(opts),
|
|
7307
8261
|
buildTodosCommand(opts),
|
|
8262
|
+
buildTasksCommand(),
|
|
7308
8263
|
buildSddCommand(opts),
|
|
7309
8264
|
buildSaveCommand(opts),
|
|
7310
8265
|
buildLoadCommand(opts),
|
|
@@ -7433,7 +8388,7 @@ async function runProjectCheck(opts) {
|
|
|
7433
8388
|
try {
|
|
7434
8389
|
const { spawn: spawn4 } = await import('child_process');
|
|
7435
8390
|
await new Promise((resolve5, reject) => {
|
|
7436
|
-
const child = spawn4("git", ["init"], { cwd });
|
|
8391
|
+
const child = spawn4("git", ["init"], { cwd, signal: AbortSignal.timeout(1e4) });
|
|
7437
8392
|
child.on("error", reject);
|
|
7438
8393
|
child.on("close", (code) => code === 0 ? resolve5() : reject(new Error(`git init failed with ${code}`)));
|
|
7439
8394
|
});
|
|
@@ -8139,20 +9094,14 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
8139
9094
|
}
|
|
8140
9095
|
|
|
8141
9096
|
// src/picker.ts
|
|
8142
|
-
function expectDefined6(value) {
|
|
8143
|
-
if (value === null || value === void 0) {
|
|
8144
|
-
throw new Error("Expected value to be defined");
|
|
8145
|
-
}
|
|
8146
|
-
return value;
|
|
8147
|
-
}
|
|
8148
9097
|
var theme = { primary: color.amber };
|
|
8149
9098
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
|
|
8150
9099
|
try {
|
|
8151
9100
|
const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
|
|
8152
|
-
const
|
|
9101
|
+
const fs27 = await import('fs/promises');
|
|
8153
9102
|
let existing = {};
|
|
8154
9103
|
try {
|
|
8155
|
-
const raw = await
|
|
9104
|
+
const raw = await fs27.readFile(configPath2, "utf8");
|
|
8156
9105
|
existing = JSON.parse(raw);
|
|
8157
9106
|
} catch {
|
|
8158
9107
|
}
|
|
@@ -8340,7 +9289,7 @@ async function pickModel(provider, registry, renderer, reader, defaultModel) {
|
|
|
8340
9289
|
while (offset < models.length) {
|
|
8341
9290
|
const page = models.slice(offset, offset + pageSize);
|
|
8342
9291
|
for (let i = 0; i < page.length; i++) {
|
|
8343
|
-
const m =
|
|
9292
|
+
const m = expectDefined(page[i]);
|
|
8344
9293
|
const num = offset + i + 1;
|
|
8345
9294
|
const ctx = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k`.padStart(6) : " ?";
|
|
8346
9295
|
const cost = m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
|
|
@@ -8488,12 +9437,12 @@ function pickGroupIndex(opts) {
|
|
|
8488
9437
|
try {
|
|
8489
9438
|
let current = 0;
|
|
8490
9439
|
try {
|
|
8491
|
-
const parsed = Number.parseInt(
|
|
9440
|
+
const parsed = Number.parseInt(fs13.readFileSync(opts.cursorFile, "utf8").trim(), 10);
|
|
8492
9441
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
8493
9442
|
} catch {
|
|
8494
9443
|
}
|
|
8495
|
-
|
|
8496
|
-
|
|
9444
|
+
fs13.mkdirSync(path8.dirname(opts.cursorFile), { recursive: true });
|
|
9445
|
+
fs13.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
8497
9446
|
return current;
|
|
8498
9447
|
} catch {
|
|
8499
9448
|
}
|
|
@@ -9001,333 +9950,201 @@ async function spawnACPAgent(args, deps) {
|
|
|
9001
9950
|
}
|
|
9002
9951
|
}
|
|
9003
9952
|
|
|
9004
|
-
// src/auth-menu.ts
|
|
9953
|
+
// src/auth-menu/add-provider.ts
|
|
9005
9954
|
init_provider_config_utils();
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
if (!choice || choice === "q" || choice === "quit" || choice === "exit") {
|
|
9014
|
-
deps.renderer.write(color.dim("Done.\n"));
|
|
9015
|
-
return 0;
|
|
9016
|
-
}
|
|
9017
|
-
if (choice === "a" || choice === "add") {
|
|
9018
|
-
await addForNewProvider(deps);
|
|
9019
|
-
continue;
|
|
9020
|
-
}
|
|
9021
|
-
if (choice === "c" || choice === "custom") {
|
|
9022
|
-
await addCustomProvider(deps);
|
|
9023
|
-
continue;
|
|
9024
|
-
}
|
|
9025
|
-
const idx = Number.parseInt(choice, 10);
|
|
9026
|
-
if (!Number.isNaN(idx) && idx >= 1 && idx <= ids.length) {
|
|
9027
|
-
const pid = expectDefined7(ids[idx - 1]);
|
|
9028
|
-
await manageProvider(pid, deps);
|
|
9029
|
-
continue;
|
|
9030
|
-
}
|
|
9031
|
-
const byId = ids.find((id) => id.toLowerCase() === choice);
|
|
9032
|
-
if (byId) {
|
|
9033
|
-
await manageProvider(byId, deps);
|
|
9034
|
-
continue;
|
|
9035
|
-
}
|
|
9036
|
-
deps.renderer.writeError(`Unknown selection: "${choice}"`);
|
|
9037
|
-
}
|
|
9955
|
+
|
|
9956
|
+
// src/auth-menu/helpers.ts
|
|
9957
|
+
init_provider_config_utils();
|
|
9958
|
+
async function loadProviders(deps) {
|
|
9959
|
+
return loadConfigProviders(deps.globalConfigPath, deps.vault, {
|
|
9960
|
+
warn: (msg) => deps.renderer.writeWarning(msg)
|
|
9961
|
+
});
|
|
9038
9962
|
}
|
|
9039
|
-
function renderTopMenu(renderer, providers) {
|
|
9040
|
-
renderer.write(`
|
|
9041
|
-
${color.bold("WrongStack")} ${color.dim("\u2014 API keys")}
|
|
9042
9963
|
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9964
|
+
// src/auth-menu/shared.ts
|
|
9965
|
+
init_provider_config_utils();
|
|
9966
|
+
function renderProviderLine(renderer, id, cfg, idx) {
|
|
9967
|
+
const keys = normalizeKeys(cfg);
|
|
9968
|
+
const active = activeLabel(cfg, keys);
|
|
9969
|
+
const firstKey = keys[0];
|
|
9970
|
+
let summary;
|
|
9971
|
+
if (keys.length === 0) {
|
|
9972
|
+
summary = color.dim("(no keys)");
|
|
9973
|
+
} else if (keys.length === 1) {
|
|
9974
|
+
summary = maskedKey(firstKey?.apiKey ?? "");
|
|
9047
9975
|
} else {
|
|
9048
|
-
|
|
9049
|
-
`);
|
|
9050
|
-
let idx = 1;
|
|
9051
|
-
for (const id of ids) {
|
|
9052
|
-
const cfg = providers[id];
|
|
9053
|
-
if (!cfg) continue;
|
|
9054
|
-
const keys = normalizeKeys(cfg);
|
|
9055
|
-
const active = activeLabel(cfg, keys);
|
|
9056
|
-
const firstKey = keys[0];
|
|
9057
|
-
const summary = keys.length === 0 ? color.dim("(no keys)") : keys.length === 1 ? maskedKey(firstKey?.apiKey ?? "") : `${color.dim(`${keys.length} keys`)} ${color.dim("active:")} ${color.bold(active ?? "?")} ${maskedKey(keys.find((k) => k.label === active)?.apiKey ?? firstKey?.apiKey ?? "")}`;
|
|
9058
|
-
const fam = cfg.family ? color.dim(`[${cfg.family}]`) : "";
|
|
9059
|
-
const aliasHint = cfg.type && cfg.type !== id ? color.dim(`\u2192 ${cfg.type}`) : "";
|
|
9060
|
-
renderer.write(
|
|
9061
|
-
` ${color.dim(`${idx}.`.padStart(4))} ${id.padEnd(22)} ${fam} ${aliasHint} ${summary}
|
|
9062
|
-
`
|
|
9063
|
-
);
|
|
9064
|
-
idx++;
|
|
9065
|
-
}
|
|
9066
|
-
}
|
|
9067
|
-
renderer.write(`
|
|
9068
|
-
${color.dim("Actions:")}
|
|
9069
|
-
`);
|
|
9070
|
-
renderer.write(` ${color.bold("a")} Add key for a new provider (from catalog)
|
|
9071
|
-
`);
|
|
9072
|
-
renderer.write(` ${color.bold("c")} Add custom provider (type + family + baseUrl)
|
|
9073
|
-
`);
|
|
9074
|
-
renderer.write(` ${color.bold("q")} Quit
|
|
9075
|
-
`);
|
|
9076
|
-
if (ids.length > 0) {
|
|
9077
|
-
renderer.write(color.dim(`
|
|
9078
|
-
Pick a number to manage that provider's keys.
|
|
9079
|
-
`));
|
|
9976
|
+
const activeKeyObj = active != null ? keys.find((k) => k.label === active) : void 0;
|
|
9977
|
+
summary = `${color.dim(`${keys.length} keys`)} ${color.dim("active:")} ${color.bold(active ?? "?")} ` + maskedKey(activeKeyObj?.apiKey ?? firstKey?.apiKey ?? "");
|
|
9080
9978
|
}
|
|
9979
|
+
const fam = cfg.family ? color.dim(`[${cfg.family}]`) : "";
|
|
9980
|
+
const aliasHint = cfg.type && cfg.type !== id ? color.dim(`\u2192 ${cfg.type}`) : "";
|
|
9981
|
+
renderer.write(
|
|
9982
|
+
` ${color.dim(`${idx}.`.padStart(4))} ${id.padEnd(22)} ${fam} ${aliasHint} ${summary}
|
|
9983
|
+
`
|
|
9984
|
+
);
|
|
9081
9985
|
}
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
deps.renderer.writeError(`Provider "${providerId}" no longer in config.`);
|
|
9088
|
-
return;
|
|
9089
|
-
}
|
|
9090
|
-
const keys = normalizeKeys(cfg);
|
|
9091
|
-
const active = activeLabel(cfg, keys);
|
|
9092
|
-
deps.renderer.write(
|
|
9093
|
-
`
|
|
9986
|
+
function renderProviderHeader(renderer, providerId, cfg) {
|
|
9987
|
+
const keys = normalizeKeys(cfg);
|
|
9988
|
+
const active = activeLabel(cfg, keys);
|
|
9989
|
+
renderer.write(
|
|
9990
|
+
`
|
|
9094
9991
|
${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.amber("[no family]")}
|
|
9095
9992
|
`
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
}
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9993
|
+
);
|
|
9994
|
+
const details = [
|
|
9995
|
+
color.dim(` type: ${cfg.type ?? providerId}`),
|
|
9996
|
+
color.dim(
|
|
9997
|
+
` family: ${cfg.family ?? "(unset \u2192 resolved from models.dev when type matches)"}`
|
|
9998
|
+
),
|
|
9999
|
+
color.dim(` baseUrl: ${cfg.baseUrl ?? "(unset \u2192 catalog default)"}`)
|
|
10000
|
+
];
|
|
10001
|
+
if (cfg.envVars && cfg.envVars.length > 0) {
|
|
10002
|
+
details.push(color.dim(` envVars: ${cfg.envVars.join(", ")}`));
|
|
10003
|
+
}
|
|
10004
|
+
if (cfg.models && cfg.models.length > 0) {
|
|
10005
|
+
details.push(color.dim(` models: ${cfg.models.join(", ")}`));
|
|
10006
|
+
}
|
|
10007
|
+
renderer.write(details.join("\n") + "\n");
|
|
10008
|
+
if (keys.length === 0) {
|
|
10009
|
+
renderer.write(color.dim(" (no keys saved)\n"));
|
|
10010
|
+
} else {
|
|
10011
|
+
for (let i = 0; i < keys.length; i++) {
|
|
10012
|
+
renderKeyLine(renderer, keys[i], i + 1, active);
|
|
9112
10013
|
}
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
deps.renderer.write(
|
|
9120
|
-
` ${color.dim(`${i + 1}.`.padStart(4))} ${marker} ${k.label.padEnd(20)} ${maskedKey(k.apiKey)} ${color.dim(k.createdAt)}
|
|
10014
|
+
}
|
|
10015
|
+
}
|
|
10016
|
+
function renderKeyLine(renderer, key, idx, active) {
|
|
10017
|
+
const marker = key.label === active ? color.green("\u25CF") : color.dim("\u25CB");
|
|
10018
|
+
renderer.write(
|
|
10019
|
+
` ${color.dim(`${idx}.`.padStart(4))} ${marker} ${key.label.padEnd(20)} ${maskedKey(key.apiKey)} ${color.dim(key.createdAt)}
|
|
9121
10020
|
`
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
10021
|
+
);
|
|
10022
|
+
}
|
|
10023
|
+
function renderActions(renderer, keysLength) {
|
|
10024
|
+
renderer.write(`
|
|
9126
10025
|
${color.dim("Actions:")}
|
|
9127
10026
|
`);
|
|
9128
|
-
|
|
10027
|
+
renderer.write(` ${color.bold("a")} Add another key
|
|
9129
10028
|
`);
|
|
9130
|
-
|
|
9131
|
-
|
|
10029
|
+
if (keysLength > 0) {
|
|
10030
|
+
renderer.write(` ${color.bold("u")} <n> Update key <n>
|
|
9132
10031
|
`);
|
|
9133
|
-
|
|
10032
|
+
renderer.write(` ${color.bold("d")} <n> Delete key <n>
|
|
9134
10033
|
`);
|
|
9135
|
-
|
|
10034
|
+
renderer.write(` ${color.bold("s")} <n> Set key <n> as active
|
|
9136
10035
|
`);
|
|
9137
|
-
|
|
9138
|
-
|
|
10036
|
+
}
|
|
10037
|
+
renderer.write(` ${color.bold("f")} Edit family
|
|
9139
10038
|
`);
|
|
9140
|
-
|
|
10039
|
+
renderer.write(` ${color.bold("B")} Edit baseUrl
|
|
9141
10040
|
`);
|
|
9142
|
-
|
|
10041
|
+
renderer.write(` ${color.bold("m")} Edit visible model list
|
|
9143
10042
|
`);
|
|
9144
|
-
|
|
10043
|
+
renderer.write(` ${color.bold("x")} Remove this provider entirely
|
|
9145
10044
|
`);
|
|
9146
|
-
|
|
10045
|
+
renderer.write(` ${color.bold("b")} Back
|
|
9147
10046
|
`);
|
|
9148
|
-
|
|
10047
|
+
renderer.write(` ${color.bold("q")} Quit
|
|
9149
10048
|
`);
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
if (confirm === "y" || confirm === "yes") {
|
|
9165
|
-
await mutateProviders(deps, (all) => {
|
|
9166
|
-
delete all[providerId];
|
|
9167
|
-
});
|
|
9168
|
-
deps.renderer.write(` ${color.green("\u2713")} Removed ${providerId}.
|
|
10049
|
+
}
|
|
10050
|
+
function renderTopMenu(renderer, providers) {
|
|
10051
|
+
renderer.write(
|
|
10052
|
+
`
|
|
10053
|
+
${color.bold("WrongStack")} ${color.dim("\u2014 API key manager")}
|
|
10054
|
+
|
|
10055
|
+
`
|
|
10056
|
+
);
|
|
10057
|
+
const ids = Object.keys(providers).sort();
|
|
10058
|
+
if (ids.length === 0) {
|
|
10059
|
+
renderer.write(color.dim(" No providers configured yet.\n"));
|
|
10060
|
+
renderer.write(color.dim(" Use (a) to add one from the models.dev catalog, or (c) for a custom provider.\n"));
|
|
10061
|
+
} else {
|
|
10062
|
+
renderer.write(` ${color.dim("Saved providers:")}
|
|
9169
10063
|
`);
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
10064
|
+
let idx = 1;
|
|
10065
|
+
for (const id of ids) {
|
|
10066
|
+
const cfg = providers[id];
|
|
10067
|
+
if (!cfg) continue;
|
|
10068
|
+
renderProviderLine(renderer, id, cfg, idx);
|
|
10069
|
+
idx++;
|
|
9173
10070
|
}
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
continue;
|
|
9178
|
-
}
|
|
9179
|
-
const target = expectDefined7(keys[arg - 1]);
|
|
9180
|
-
const newKey = await readKeyInput(deps, `Updated key for ${target.label}`);
|
|
9181
|
-
if (!newKey) continue;
|
|
9182
|
-
await mutateProviders(deps, (all) => {
|
|
9183
|
-
const p = all[providerId];
|
|
9184
|
-
if (!p) return;
|
|
9185
|
-
const list = normalizeKeys(p).map(
|
|
9186
|
-
(k) => k.label === target.label ? { ...k, apiKey: newKey, createdAt: nowIso() } : k
|
|
9187
|
-
);
|
|
9188
|
-
writeKeysBack(p, list);
|
|
9189
|
-
});
|
|
9190
|
-
deps.renderer.write(` ${color.green("\u2713")} Updated ${providerId}/${target.label}.
|
|
10071
|
+
}
|
|
10072
|
+
renderer.write(`
|
|
10073
|
+
${color.dim("Actions:")}
|
|
9191
10074
|
`);
|
|
9192
|
-
|
|
9193
|
-
}
|
|
9194
|
-
if (verb === "d" || verb === "delete" || verb === "rm") {
|
|
9195
|
-
if (!Number.isFinite(arg) || arg < 1 || arg > keys.length) {
|
|
9196
|
-
deps.renderer.writeError(`Usage: d <1-${keys.length}>`);
|
|
9197
|
-
continue;
|
|
9198
|
-
}
|
|
9199
|
-
const target = expectDefined7(keys[arg - 1]);
|
|
9200
|
-
const confirm = (await deps.reader.readLine(
|
|
9201
|
-
` ${color.amber("?")} Delete key "${target.label}" (${maskedKey(target.apiKey)})? ${color.dim("[y/N/q]")} `
|
|
9202
|
-
)).trim().toLowerCase();
|
|
9203
|
-
if (confirm === "q") continue;
|
|
9204
|
-
if (confirm !== "y" && confirm !== "yes") continue;
|
|
9205
|
-
await mutateProviders(deps, (all) => {
|
|
9206
|
-
const p = all[providerId];
|
|
9207
|
-
if (!p) return;
|
|
9208
|
-
const list = normalizeKeys(p).filter((k) => k.label !== target.label);
|
|
9209
|
-
writeKeysBack(p, list);
|
|
9210
|
-
if (p.activeKey === target.label) {
|
|
9211
|
-
p.activeKey = list[0]?.label;
|
|
9212
|
-
}
|
|
9213
|
-
});
|
|
9214
|
-
deps.renderer.write(` ${color.green("\u2713")} Deleted ${providerId}/${target.label}.
|
|
10075
|
+
renderer.write(` ${color.bold("a")} Add a provider (from catalog)
|
|
9215
10076
|
`);
|
|
9216
|
-
|
|
9217
|
-
}
|
|
9218
|
-
if (verb === "f" || verb === "family") {
|
|
9219
|
-
const current = cfg.family ?? "";
|
|
9220
|
-
const ans = (await deps.reader.readLine(
|
|
9221
|
-
` ${color.amber("?")} Family ${color.dim(`(anthropic | openai | openai-compatible | google, empty = unset, current: ${current || "unset"})`)}: `
|
|
9222
|
-
)).trim();
|
|
9223
|
-
if (ans !== "" && !["anthropic", "openai", "openai-compatible", "google"].includes(ans)) {
|
|
9224
|
-
deps.renderer.writeError(`Invalid family: "${ans}"`);
|
|
9225
|
-
continue;
|
|
9226
|
-
}
|
|
9227
|
-
await mutateProviders(deps, (all) => {
|
|
9228
|
-
const p = all[providerId];
|
|
9229
|
-
if (!p) return;
|
|
9230
|
-
if (ans === "") delete p.family;
|
|
9231
|
-
else p.family = ans;
|
|
9232
|
-
});
|
|
9233
|
-
deps.renderer.write(` ${color.green("\u2713")} family \u2192 ${ans || "(unset)"}
|
|
10077
|
+
renderer.write(` ${color.bold("c")} Add a custom provider
|
|
9234
10078
|
`);
|
|
9235
|
-
|
|
9236
|
-
}
|
|
9237
|
-
if (verb === "B" || verb === "baseurl" || verb === "base-url") {
|
|
9238
|
-
const current = cfg.baseUrl ?? "";
|
|
9239
|
-
const ans = (await deps.reader.readLine(
|
|
9240
|
-
` ${color.amber("?")} Base URL ${color.dim(`(empty = unset, current: ${current || "unset"})`)}: `
|
|
9241
|
-
)).trim();
|
|
9242
|
-
await mutateProviders(deps, (all) => {
|
|
9243
|
-
const p = all[providerId];
|
|
9244
|
-
if (!p) return;
|
|
9245
|
-
if (ans === "") delete p.baseUrl;
|
|
9246
|
-
else p.baseUrl = ans;
|
|
9247
|
-
});
|
|
9248
|
-
deps.renderer.write(` ${color.green("\u2713")} baseUrl \u2192 ${ans || "(unset)"}
|
|
10079
|
+
if (ids.length > 0) {
|
|
10080
|
+
renderer.write(` ${color.dim("1-")}${color.dim(String(ids.length))} ${color.bold("Manage a provider")}
|
|
9249
10081
|
`);
|
|
9250
|
-
continue;
|
|
9251
|
-
}
|
|
9252
|
-
if (verb === "m" || verb === "models") {
|
|
9253
|
-
const current = (cfg.models ?? []).join(", ");
|
|
9254
|
-
const ans = (await deps.reader.readLine(
|
|
9255
|
-
` ${color.amber("?")} Model ids ${color.dim(`(comma-separated, empty = catalog default, current: ${current || "none"})`)}: `
|
|
9256
|
-
)).trim();
|
|
9257
|
-
const list = ans ? ans.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
9258
|
-
await mutateProviders(deps, (all) => {
|
|
9259
|
-
const p = all[providerId];
|
|
9260
|
-
if (!p) return;
|
|
9261
|
-
if (list.length === 0) delete p.models;
|
|
9262
|
-
else p.models = list;
|
|
9263
|
-
});
|
|
9264
|
-
deps.renderer.write(
|
|
9265
|
-
` ${color.green("\u2713")} models \u2192 ${list.length === 0 ? "(catalog default)" : list.join(", ")}
|
|
9266
|
-
`
|
|
9267
|
-
);
|
|
9268
|
-
continue;
|
|
9269
|
-
}
|
|
9270
|
-
if (verb === "s" || verb === "set" || verb === "active") {
|
|
9271
|
-
if (!Number.isFinite(arg) || arg < 1 || arg > keys.length) {
|
|
9272
|
-
deps.renderer.writeError(`Usage: s <1-${keys.length}>`);
|
|
9273
|
-
continue;
|
|
9274
|
-
}
|
|
9275
|
-
const target = expectDefined7(keys[arg - 1]);
|
|
9276
|
-
await mutateProviders(deps, (all) => {
|
|
9277
|
-
const p = all[providerId];
|
|
9278
|
-
if (!p) return;
|
|
9279
|
-
const list = normalizeKeys(p);
|
|
9280
|
-
writeKeysBack(p, list);
|
|
9281
|
-
p.activeKey = target.label;
|
|
9282
|
-
});
|
|
9283
|
-
deps.renderer.write(
|
|
9284
|
-
` ${color.green("\u2713")} Active key for ${providerId} \u2192 ${color.bold(target.label)}.
|
|
9285
|
-
`
|
|
9286
|
-
);
|
|
9287
|
-
continue;
|
|
9288
|
-
}
|
|
9289
|
-
deps.renderer.writeError(`Unknown action: "${raw}"`);
|
|
9290
10082
|
}
|
|
10083
|
+
renderer.write(` ${color.bold("q")} Quit
|
|
10084
|
+
`);
|
|
10085
|
+
}
|
|
10086
|
+
async function readKeyInput(deps, intent) {
|
|
10087
|
+
const key = (await deps.reader.readSecret(
|
|
10088
|
+
` ${color.amber("?")} ${intent} ${color.dim("(hidden, paste OK)")}: `
|
|
10089
|
+
)).trim();
|
|
10090
|
+
if (!key) {
|
|
10091
|
+
deps.renderer.writeError("No key entered.");
|
|
10092
|
+
return void 0;
|
|
10093
|
+
}
|
|
10094
|
+
return key;
|
|
10095
|
+
}
|
|
10096
|
+
async function confirm(deps, question) {
|
|
10097
|
+
const answer = (await deps.reader.readLine(
|
|
10098
|
+
` ${color.amber("?")} ${question} ${color.dim("[y/N/q]")} `
|
|
10099
|
+
)).trim().toLowerCase();
|
|
10100
|
+
if (answer === "q" || answer === "quit") return null;
|
|
10101
|
+
return answer === "y" || answer === "yes";
|
|
10102
|
+
}
|
|
10103
|
+
function suggestLabel(usedLabels) {
|
|
10104
|
+
let candidate = "default";
|
|
10105
|
+
if (!usedLabels.has(candidate)) return candidate;
|
|
10106
|
+
let n = 2;
|
|
10107
|
+
while (usedLabels.has(`key${n}`)) n++;
|
|
10108
|
+
return `key${n}`;
|
|
10109
|
+
}
|
|
10110
|
+
function validateFamily(raw) {
|
|
10111
|
+
const valid = ["anthropic", "openai", "openai-compatible", "google"];
|
|
10112
|
+
return valid.includes(raw) ? raw : null;
|
|
9291
10113
|
}
|
|
9292
|
-
|
|
10114
|
+
|
|
10115
|
+
// src/auth-menu/add-provider.ts
|
|
10116
|
+
async function addFromCatalog(deps) {
|
|
9293
10117
|
let catalog = [];
|
|
9294
10118
|
try {
|
|
9295
|
-
catalog = (await deps.modelsRegistry.listProviders()).filter(
|
|
10119
|
+
catalog = (await deps.modelsRegistry.listProviders()).filter(
|
|
10120
|
+
(p) => p.family !== "unsupported"
|
|
10121
|
+
);
|
|
9296
10122
|
} catch {
|
|
9297
|
-
deps.renderer.writeWarning(
|
|
10123
|
+
deps.renderer.writeWarning(
|
|
10124
|
+
"Catalog unavailable \u2014 falling back to manual entry.\n"
|
|
10125
|
+
);
|
|
9298
10126
|
}
|
|
9299
10127
|
if (catalog.length === 0) {
|
|
9300
|
-
|
|
9301
|
-
if (!pid || pid === "q") return;
|
|
9302
|
-
const fam = (await deps.reader.readLine(
|
|
9303
|
-
` ${color.amber("?")} Family (anthropic/openai/openai-compatible/google): `
|
|
9304
|
-
)).trim();
|
|
9305
|
-
const baseUrl2 = (await deps.reader.readLine(` ${color.amber("?")} Base URL ${color.dim("(optional)")}: `)).trim();
|
|
9306
|
-
await addKeyForProvider(pid, deps, {
|
|
9307
|
-
type: pid,
|
|
9308
|
-
family: fam || void 0,
|
|
9309
|
-
...baseUrl2 ? { baseUrl: baseUrl2 } : {}
|
|
9310
|
-
});
|
|
9311
|
-
return;
|
|
10128
|
+
return addManualEntry(deps);
|
|
9312
10129
|
}
|
|
9313
10130
|
const saved = new Set(Object.keys(await loadProviders(deps)));
|
|
9314
10131
|
deps.renderer.write(
|
|
9315
10132
|
color.dim(
|
|
9316
|
-
` Catalog
|
|
10133
|
+
` Catalog: ${catalog.length} providers. Filter to narrow, "s" for unsaved-only, or enter to show all.
|
|
9317
10134
|
`
|
|
9318
10135
|
)
|
|
9319
10136
|
);
|
|
9320
10137
|
const filterRaw = (await deps.reader.readLine(
|
|
9321
|
-
` ${color.amber("?")} Filter ${color.dim('(substring
|
|
10138
|
+
` ${color.amber("?")} Filter ${color.dim('(substring / "s" / q to quit)')}: `
|
|
9322
10139
|
)).trim();
|
|
9323
|
-
if (filterRaw === "q") return;
|
|
10140
|
+
if (filterRaw === "q") return false;
|
|
9324
10141
|
const filterLc = filterRaw.toLowerCase();
|
|
9325
10142
|
const showUnsavedOnly = filterLc === "s" || filterLc === "unsaved";
|
|
9326
|
-
|
|
10143
|
+
function matches(p) {
|
|
9327
10144
|
if (showUnsavedOnly) return !saved.has(p.id);
|
|
9328
10145
|
if (!filterLc) return true;
|
|
9329
10146
|
return p.id.toLowerCase().includes(filterLc) || p.name.toLowerCase().includes(filterLc);
|
|
9330
|
-
}
|
|
10147
|
+
}
|
|
9331
10148
|
const byFamily = /* @__PURE__ */ new Map();
|
|
9332
10149
|
let filteredCount = 0;
|
|
9333
10150
|
for (const p of catalog) {
|
|
@@ -9339,18 +10156,25 @@ async function addForNewProvider(deps) {
|
|
|
9339
10156
|
}
|
|
9340
10157
|
if (filteredCount === 0) {
|
|
9341
10158
|
deps.renderer.writeError(
|
|
9342
|
-
`No providers match "${filterRaw}". Try a shorter substring or check \`wstack providers
|
|
10159
|
+
`No providers match "${filterRaw}". Try a shorter substring or check \`wstack providers\`.`
|
|
9343
10160
|
);
|
|
9344
|
-
return;
|
|
10161
|
+
return false;
|
|
9345
10162
|
}
|
|
9346
10163
|
if (filterRaw && !showUnsavedOnly) {
|
|
9347
10164
|
deps.renderer.write(
|
|
9348
|
-
color.dim(
|
|
9349
|
-
`
|
|
10165
|
+
color.dim(
|
|
10166
|
+
` ${filteredCount} match${filteredCount === 1 ? "" : "es"} for "${filterRaw}".
|
|
10167
|
+
`
|
|
10168
|
+
)
|
|
9350
10169
|
);
|
|
9351
10170
|
}
|
|
9352
10171
|
const ordered = [];
|
|
9353
|
-
const familyOrder = [
|
|
10172
|
+
const familyOrder = [
|
|
10173
|
+
"anthropic",
|
|
10174
|
+
"openai",
|
|
10175
|
+
"google",
|
|
10176
|
+
"openai-compatible"
|
|
10177
|
+
];
|
|
9354
10178
|
let idx = 1;
|
|
9355
10179
|
deps.renderer.write("\n");
|
|
9356
10180
|
for (const fam of familyOrder) {
|
|
@@ -9376,7 +10200,7 @@ async function addForNewProvider(deps) {
|
|
|
9376
10200
|
`
|
|
9377
10201
|
${color.amber("?")} Pick (1-${ordered.length}) or type provider id ${color.dim("[q to quit]")}: `
|
|
9378
10202
|
)).trim();
|
|
9379
|
-
if (!answer || answer === "q") return;
|
|
10203
|
+
if (!answer || answer === "q") return false;
|
|
9380
10204
|
let chosen;
|
|
9381
10205
|
const num = Number.parseInt(answer, 10);
|
|
9382
10206
|
if (!Number.isNaN(num) && num >= 1 && num <= ordered.length) {
|
|
@@ -9386,29 +10210,35 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id ${color.dim("
|
|
|
9386
10210
|
}
|
|
9387
10211
|
if (!chosen) {
|
|
9388
10212
|
deps.renderer.writeError(`No such provider: "${answer}"`);
|
|
9389
|
-
return;
|
|
10213
|
+
return false;
|
|
9390
10214
|
}
|
|
10215
|
+
return addKeyForCatalogProvider(deps, chosen);
|
|
10216
|
+
}
|
|
10217
|
+
async function addKeyForCatalogProvider(deps, chosen) {
|
|
9391
10218
|
deps.renderer.write(
|
|
9392
10219
|
color.dim(`
|
|
9393
|
-
Defaults from models.dev \u2014 press Enter to keep, or type
|
|
10220
|
+
Defaults from models.dev \u2014 press Enter to keep, or type overrides.
|
|
9394
10221
|
`)
|
|
9395
10222
|
);
|
|
9396
|
-
const famRaw = (await deps.reader.readLine(
|
|
9397
|
-
|
|
10223
|
+
const famRaw = (await deps.reader.readLine(
|
|
10224
|
+
` ${color.amber("?")} Family ${color.dim(`[${chosen.family}]`)} ${color.dim("(q to quit)")}: `
|
|
10225
|
+
)).trim();
|
|
10226
|
+
if (famRaw === "q") return false;
|
|
9398
10227
|
let family = chosen.family;
|
|
9399
10228
|
if (famRaw) {
|
|
9400
|
-
|
|
10229
|
+
const validated = validateFamily(famRaw);
|
|
10230
|
+
if (!validated) {
|
|
9401
10231
|
deps.renderer.writeError(
|
|
9402
|
-
`Invalid family: "${famRaw}" (must be anthropic
|
|
10232
|
+
`Invalid family: "${famRaw}" (must be: anthropic, openai, openai-compatible, google).`
|
|
9403
10233
|
);
|
|
9404
|
-
return;
|
|
10234
|
+
return false;
|
|
9405
10235
|
}
|
|
9406
|
-
family =
|
|
10236
|
+
family = validated;
|
|
9407
10237
|
}
|
|
9408
10238
|
const baseRaw = (await deps.reader.readLine(
|
|
9409
10239
|
` ${color.amber("?")} Base URL ${color.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color.dim("(q to quit)")}: `
|
|
9410
10240
|
)).trim();
|
|
9411
|
-
if (baseRaw === "q") return;
|
|
10241
|
+
if (baseRaw === "q") return false;
|
|
9412
10242
|
const baseUrl = baseRaw || chosen.apiBase;
|
|
9413
10243
|
const providersNow = await loadProviders(deps);
|
|
9414
10244
|
let suggestedAlias = chosen.id;
|
|
@@ -9422,7 +10252,7 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id ${color.dim("
|
|
|
9422
10252
|
suggestedAlias = candidate;
|
|
9423
10253
|
}
|
|
9424
10254
|
const aliasRaw = (await deps.reader.readLine(
|
|
9425
|
-
` ${color.amber("?")} Save
|
|
10255
|
+
` ${color.amber("?")} Save as alias ${color.dim(`[${suggestedAlias}]`)} ${color.dim("(used with --provider <alias>)")}: `
|
|
9426
10256
|
)).trim();
|
|
9427
10257
|
const alias = aliasRaw || suggestedAlias;
|
|
9428
10258
|
const existing = providersNow[alias];
|
|
@@ -9436,10 +10266,10 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id ${color.dim("
|
|
|
9436
10266
|
New: family=${family}, baseUrl=${baseUrl ?? "(unset)"}
|
|
9437
10267
|
Pick a different alias to keep them separate.`
|
|
9438
10268
|
);
|
|
9439
|
-
return;
|
|
10269
|
+
return false;
|
|
9440
10270
|
}
|
|
9441
10271
|
}
|
|
9442
|
-
|
|
10272
|
+
return addKeyForProvider(alias, deps, {
|
|
9443
10273
|
type: chosen.id,
|
|
9444
10274
|
family,
|
|
9445
10275
|
baseUrl,
|
|
@@ -9449,39 +10279,41 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id ${color.dim("
|
|
|
9449
10279
|
async function addCustomProvider(deps) {
|
|
9450
10280
|
deps.renderer.write(
|
|
9451
10281
|
`
|
|
9452
|
-
${color.bold("Custom provider")} ${color.dim("\u2014 for local models or proxies not in the
|
|
10282
|
+
${color.bold("Custom provider")} ${color.dim("\u2014 for local models or proxies not in the catalog.")}
|
|
9453
10283
|
`
|
|
9454
10284
|
);
|
|
9455
10285
|
const type = (await deps.reader.readLine(
|
|
9456
10286
|
` ${color.amber("?")} Provider id ${color.dim('(e.g. "local-llama", "my-proxy", q to quit)')}: `
|
|
9457
10287
|
)).trim();
|
|
9458
|
-
if (!type || type === "q") return;
|
|
10288
|
+
if (!type || type === "q") return false;
|
|
9459
10289
|
const existing = (await loadProviders(deps))[type];
|
|
9460
10290
|
if (existing) {
|
|
9461
|
-
deps.renderer.writeWarning(
|
|
9462
|
-
|
|
10291
|
+
deps.renderer.writeWarning(
|
|
10292
|
+
`"${type}" already exists. Pick it from the main menu to edit.`
|
|
10293
|
+
);
|
|
10294
|
+
return false;
|
|
9463
10295
|
}
|
|
9464
10296
|
const familyRaw = (await deps.reader.readLine(
|
|
9465
10297
|
` ${color.amber("?")} Wire family ${color.dim("(anthropic | openai | openai-compatible | google)")} ${color.dim("(q to quit)")}: `
|
|
9466
10298
|
)).trim();
|
|
9467
|
-
if (familyRaw === "q") return;
|
|
9468
|
-
|
|
10299
|
+
if (familyRaw === "q") return false;
|
|
10300
|
+
const family = validateFamily(familyRaw);
|
|
10301
|
+
if (!family) {
|
|
9469
10302
|
deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
|
|
9470
|
-
return;
|
|
10303
|
+
return false;
|
|
9471
10304
|
}
|
|
9472
|
-
const family = familyRaw;
|
|
9473
10305
|
const baseUrl = (await deps.reader.readLine(
|
|
9474
|
-
` ${color.amber("?")} Base URL ${color.dim("(e.g. http://localhost:11434/v1,
|
|
10306
|
+
` ${color.amber("?")} Base URL ${color.dim("(e.g. http://localhost:11434/v1, optional)")}: `
|
|
9475
10307
|
)).trim();
|
|
9476
10308
|
const modelsRaw = (await deps.reader.readLine(
|
|
9477
10309
|
` ${color.amber("?")} Model ids ${color.dim("(comma-separated, optional)")}: `
|
|
9478
10310
|
)).trim();
|
|
9479
10311
|
const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
9480
10312
|
const envVarsRaw = (await deps.reader.readLine(
|
|
9481
|
-
` ${color.amber("?")} Env var names ${color.dim("(comma-separated, optional
|
|
10313
|
+
` ${color.amber("?")} Env var names ${color.dim("(comma-separated, optional)")}: `
|
|
9482
10314
|
)).trim();
|
|
9483
10315
|
const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
9484
|
-
|
|
10316
|
+
return addKeyForProvider(type, deps, {
|
|
9485
10317
|
type,
|
|
9486
10318
|
family,
|
|
9487
10319
|
...baseUrl ? { baseUrl } : {},
|
|
@@ -9489,49 +10321,295 @@ ${color.bold("Custom provider")} ${color.dim("\u2014 for local models or proxies
|
|
|
9489
10321
|
...envVars ? { envVars } : {}
|
|
9490
10322
|
});
|
|
9491
10323
|
}
|
|
10324
|
+
async function addManualEntry(deps) {
|
|
10325
|
+
const pid = (await deps.reader.readLine(
|
|
10326
|
+
` ${color.amber("?")} Provider id ${color.dim("[q to quit]")}: `
|
|
10327
|
+
)).trim();
|
|
10328
|
+
if (!pid || pid === "q") return false;
|
|
10329
|
+
const famRaw = (await deps.reader.readLine(
|
|
10330
|
+
` ${color.amber("?")} Family ${color.dim("(anthropic/openai/openai-compatible/google)")}: `
|
|
10331
|
+
)).trim();
|
|
10332
|
+
const family = validateFamily(famRaw);
|
|
10333
|
+
if (!family) {
|
|
10334
|
+
deps.renderer.writeError(`Invalid family: "${famRaw}"`);
|
|
10335
|
+
return false;
|
|
10336
|
+
}
|
|
10337
|
+
const baseUrl = (await deps.reader.readLine(
|
|
10338
|
+
` ${color.amber("?")} Base URL ${color.dim("(optional)")}: `
|
|
10339
|
+
)).trim();
|
|
10340
|
+
return addKeyForProvider(pid, deps, {
|
|
10341
|
+
type: pid,
|
|
10342
|
+
family,
|
|
10343
|
+
...baseUrl ? { baseUrl } : {}
|
|
10344
|
+
});
|
|
10345
|
+
}
|
|
9492
10346
|
async function addKeyForProvider(providerId, deps, template) {
|
|
9493
10347
|
const providers = await loadProviders(deps);
|
|
9494
10348
|
const existing = providers[providerId];
|
|
9495
10349
|
const existingKeys = existing ? normalizeKeys(existing) : [];
|
|
9496
10350
|
const usedLabels = new Set(existingKeys.map((k) => k.label));
|
|
9497
|
-
|
|
9498
|
-
if (
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
10351
|
+
const label = await promptForLabel(deps, usedLabels);
|
|
10352
|
+
if (!label) return false;
|
|
10353
|
+
const apiKey = await readKeyInput(deps, `API key for ${providerId}/${label}`);
|
|
10354
|
+
if (!apiKey) return false;
|
|
10355
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10356
|
+
const existingProv = all[providerId] ?? {
|
|
10357
|
+
type: providerId,
|
|
10358
|
+
...template
|
|
10359
|
+
};
|
|
10360
|
+
if (!existingProv.type) existingProv.type = providerId;
|
|
10361
|
+
if (!existingProv.family && template.family) {
|
|
10362
|
+
existingProv.family = template.family;
|
|
10363
|
+
}
|
|
10364
|
+
if (!existingProv.baseUrl && template.baseUrl) {
|
|
10365
|
+
existingProv.baseUrl = template.baseUrl;
|
|
10366
|
+
}
|
|
10367
|
+
if (!existingProv.envVars && template.envVars) {
|
|
10368
|
+
existingProv.envVars = template.envVars;
|
|
10369
|
+
}
|
|
10370
|
+
const list = normalizeKeys(existingProv);
|
|
10371
|
+
list.push({ label, apiKey, createdAt: nowIso() });
|
|
10372
|
+
writeKeysBack(existingProv, list);
|
|
10373
|
+
if (!existingProv.activeKey) existingProv.activeKey = label;
|
|
10374
|
+
all[providerId] = existingProv;
|
|
10375
|
+
});
|
|
10376
|
+
deps.renderer.write(
|
|
10377
|
+
` ${color.green("\u2713")} Saved ${color.bold(providerId)}/${color.bold(label)}.
|
|
10378
|
+
`
|
|
10379
|
+
);
|
|
10380
|
+
deps.renderer.write(
|
|
10381
|
+
color.dim(
|
|
10382
|
+
` Launch: wstack --provider ${providerId} "<task>"
|
|
10383
|
+
`
|
|
10384
|
+
)
|
|
10385
|
+
);
|
|
10386
|
+
return true;
|
|
10387
|
+
}
|
|
10388
|
+
async function promptForLabel(deps, usedLabels) {
|
|
10389
|
+
const defaultLabel = suggestLabel(usedLabels);
|
|
9503
10390
|
const labelRaw = (await deps.reader.readLine(
|
|
9504
10391
|
` ${color.amber("?")} Label for this key ${color.dim(`[${defaultLabel}]`)}: `
|
|
9505
10392
|
)).trim();
|
|
9506
10393
|
const label = labelRaw || defaultLabel;
|
|
9507
10394
|
if (usedLabels.has(label)) {
|
|
9508
10395
|
deps.renderer.writeError(
|
|
9509
|
-
`Label "${label}" already used
|
|
10396
|
+
`Label "${label}" is already used. Use update (u) instead.`
|
|
10397
|
+
);
|
|
10398
|
+
return null;
|
|
10399
|
+
}
|
|
10400
|
+
return label;
|
|
10401
|
+
}
|
|
10402
|
+
|
|
10403
|
+
// src/auth-menu/provider-menu.ts
|
|
10404
|
+
init_provider_config_utils();
|
|
10405
|
+
async function manageProvider(providerId, deps) {
|
|
10406
|
+
for (; ; ) {
|
|
10407
|
+
const providers = await loadProviders(deps);
|
|
10408
|
+
const cfg = providers[providerId];
|
|
10409
|
+
if (!cfg) {
|
|
10410
|
+
deps.renderer.writeError(`Provider "${providerId}" no longer in config.`);
|
|
10411
|
+
return;
|
|
10412
|
+
}
|
|
10413
|
+
const keys = normalizeKeys(cfg);
|
|
10414
|
+
renderProviderHeader(deps.renderer, providerId, cfg);
|
|
10415
|
+
renderActions(deps.renderer, keys.length);
|
|
10416
|
+
const raw = (await deps.reader.readLine(
|
|
10417
|
+
`
|
|
10418
|
+
${color.amber("?")} ${providerId} > `
|
|
10419
|
+
)).trim();
|
|
10420
|
+
if (!raw || raw === "b" || raw === "back" || raw === "q" || raw === "quit") {
|
|
10421
|
+
return;
|
|
10422
|
+
}
|
|
10423
|
+
const [verb, argRaw] = raw.split(/\s+/, 2);
|
|
10424
|
+
const arg = argRaw ? Number.parseInt(argRaw, 10) : Number.NaN;
|
|
10425
|
+
const handled = await dispatchAction(verb, arg, providerId, keys, cfg, deps);
|
|
10426
|
+
if (handled === "exit") return;
|
|
10427
|
+
if (handled === "continue") continue;
|
|
10428
|
+
}
|
|
10429
|
+
}
|
|
10430
|
+
async function dispatchAction(verb, arg, providerId, keys, cfg, deps) {
|
|
10431
|
+
if (verb === "a" || verb === "add") {
|
|
10432
|
+
await addKeyForProvider(providerId, deps, cfg);
|
|
10433
|
+
return "continue";
|
|
10434
|
+
}
|
|
10435
|
+
if (verb === "x" || verb === "remove") {
|
|
10436
|
+
const answer = await confirm(deps, `Remove provider "${providerId}" and ${keys.length} key(s)?`);
|
|
10437
|
+
if (answer === null) return "continue";
|
|
10438
|
+
if (answer) {
|
|
10439
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10440
|
+
delete all[providerId];
|
|
10441
|
+
});
|
|
10442
|
+
deps.renderer.write(` ${color.green("\u2713")} Removed ${providerId}.
|
|
10443
|
+
`);
|
|
10444
|
+
return "exit";
|
|
10445
|
+
}
|
|
10446
|
+
return "continue";
|
|
10447
|
+
}
|
|
10448
|
+
if (verb === "u" || verb === "update") {
|
|
10449
|
+
if (!validKeyIndex(arg, keys.length, deps, "u")) return "continue";
|
|
10450
|
+
const target = expectDefined(keys[arg - 1]);
|
|
10451
|
+
const newKey = await readKeyInput(deps, `New key for ${target.label}`);
|
|
10452
|
+
if (!newKey) return "continue";
|
|
10453
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10454
|
+
const p = all[providerId];
|
|
10455
|
+
if (!p) return;
|
|
10456
|
+
const list = normalizeKeys(p).map(
|
|
10457
|
+
(k) => k.label === target.label ? { ...k, apiKey: newKey, createdAt: nowIso() } : k
|
|
10458
|
+
);
|
|
10459
|
+
writeKeysBack(p, list);
|
|
10460
|
+
});
|
|
10461
|
+
deps.renderer.write(` ${color.green("\u2713")} Updated ${providerId}/${target.label}.
|
|
10462
|
+
`);
|
|
10463
|
+
return "continue";
|
|
10464
|
+
}
|
|
10465
|
+
if (verb === "d" || verb === "delete" || verb === "rm") {
|
|
10466
|
+
if (!validKeyIndex(arg, keys.length, deps, "d")) return "continue";
|
|
10467
|
+
const target = expectDefined(keys[arg - 1]);
|
|
10468
|
+
const answer = await confirm(
|
|
10469
|
+
deps,
|
|
10470
|
+
`Delete key "${target.label}" (${maskedKey(target.apiKey)})?`
|
|
9510
10471
|
);
|
|
9511
|
-
return;
|
|
10472
|
+
if (answer === null) return "continue";
|
|
10473
|
+
if (!answer) return "continue";
|
|
10474
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10475
|
+
const p = all[providerId];
|
|
10476
|
+
if (!p) return;
|
|
10477
|
+
const list = normalizeKeys(p).filter((k) => k.label !== target.label);
|
|
10478
|
+
writeKeysBack(p, list);
|
|
10479
|
+
if (p.activeKey === target.label) {
|
|
10480
|
+
p.activeKey = list[0]?.label;
|
|
10481
|
+
}
|
|
10482
|
+
});
|
|
10483
|
+
deps.renderer.write(` ${color.green("\u2713")} Deleted ${providerId}/${target.label}.
|
|
10484
|
+
`);
|
|
10485
|
+
return "continue";
|
|
10486
|
+
}
|
|
10487
|
+
if (verb === "s" || verb === "set" || verb === "active") {
|
|
10488
|
+
if (!validKeyIndex(arg, keys.length, deps, "s")) return "continue";
|
|
10489
|
+
const target = expectDefined(keys[arg - 1]);
|
|
10490
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10491
|
+
const p = all[providerId];
|
|
10492
|
+
if (!p) return;
|
|
10493
|
+
const list = normalizeKeys(p);
|
|
10494
|
+
writeKeysBack(p, list);
|
|
10495
|
+
p.activeKey = target.label;
|
|
10496
|
+
});
|
|
10497
|
+
deps.renderer.write(
|
|
10498
|
+
` ${color.green("\u2713")} Active key \u2192 ${color.bold(target.label)}.
|
|
10499
|
+
`
|
|
10500
|
+
);
|
|
10501
|
+
return "continue";
|
|
9512
10502
|
}
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
10503
|
+
if (verb === "f" || verb === "family") {
|
|
10504
|
+
const current = cfg.family ?? "";
|
|
10505
|
+
const ans = (await deps.reader.readLine(
|
|
10506
|
+
` ${color.amber("?")} Family ${color.dim(`(anthropic | openai | openai-compatible | google, empty = unset, current: ${current || "unset"})`)}: `
|
|
10507
|
+
)).trim();
|
|
10508
|
+
if (ans !== "") {
|
|
10509
|
+
const validated = validateFamily(ans);
|
|
10510
|
+
if (!validated) {
|
|
10511
|
+
deps.renderer.writeError(`Invalid family: "${ans}". Must be one of: anthropic, openai, openai-compatible, google.`);
|
|
10512
|
+
return "continue";
|
|
10513
|
+
}
|
|
10514
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10515
|
+
const p = all[providerId];
|
|
10516
|
+
if (!p) return;
|
|
10517
|
+
p.family = validated;
|
|
10518
|
+
});
|
|
10519
|
+
deps.renderer.write(` ${color.green("\u2713")} family \u2192 ${validated}
|
|
10520
|
+
`);
|
|
10521
|
+
} else {
|
|
10522
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10523
|
+
const p = all[providerId];
|
|
10524
|
+
if (!p) return;
|
|
10525
|
+
delete p.family;
|
|
10526
|
+
});
|
|
10527
|
+
deps.renderer.write(` ${color.green("\u2713")} family \u2192 (unset)
|
|
10528
|
+
`);
|
|
10529
|
+
}
|
|
10530
|
+
return "continue";
|
|
9517
10531
|
}
|
|
9518
|
-
|
|
9519
|
-
const
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
})
|
|
9530
|
-
|
|
9531
|
-
|
|
10532
|
+
if (verb === "B" || verb === "baseurl" || verb === "base-url") {
|
|
10533
|
+
const current = cfg.baseUrl ?? "";
|
|
10534
|
+
const ans = (await deps.reader.readLine(
|
|
10535
|
+
` ${color.amber("?")} Base URL ${color.dim(`(empty = unset, current: ${current || "unset"})`)}: `
|
|
10536
|
+
)).trim();
|
|
10537
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10538
|
+
const p = all[providerId];
|
|
10539
|
+
if (!p) return;
|
|
10540
|
+
if (ans === "") delete p.baseUrl;
|
|
10541
|
+
else p.baseUrl = ans;
|
|
10542
|
+
});
|
|
10543
|
+
deps.renderer.write(` ${color.green("\u2713")} baseUrl \u2192 ${ans || "(unset)"}
|
|
10544
|
+
`);
|
|
10545
|
+
return "continue";
|
|
10546
|
+
}
|
|
10547
|
+
if (verb === "m" || verb === "models") {
|
|
10548
|
+
const current = (cfg.models ?? []).join(", ");
|
|
10549
|
+
const ans = (await deps.reader.readLine(
|
|
10550
|
+
` ${color.amber("?")} Model ids ${color.dim(`(comma-separated, empty = catalog default, current: ${current || "none"})`)}: `
|
|
10551
|
+
)).trim();
|
|
10552
|
+
const list = ans ? ans.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
10553
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10554
|
+
const p = all[providerId];
|
|
10555
|
+
if (!p) return;
|
|
10556
|
+
if (list.length === 0) delete p.models;
|
|
10557
|
+
else p.models = list;
|
|
10558
|
+
});
|
|
10559
|
+
deps.renderer.write(
|
|
10560
|
+
` ${color.green("\u2713")} models \u2192 ${list.length === 0 ? "(catalog default)" : list.join(", ")}
|
|
9532
10561
|
`
|
|
9533
|
-
|
|
10562
|
+
);
|
|
10563
|
+
return "continue";
|
|
10564
|
+
}
|
|
10565
|
+
deps.renderer.writeError(`Unknown action: "${verb}". Type b for back or q to quit.`);
|
|
10566
|
+
return "unknown";
|
|
10567
|
+
}
|
|
10568
|
+
function validKeyIndex(arg, max, deps, verb) {
|
|
10569
|
+
if (!Number.isFinite(arg) || arg < 1 || arg > max) {
|
|
10570
|
+
deps.renderer.writeError(`Usage: ${verb} <1-${max}>`);
|
|
10571
|
+
return false;
|
|
10572
|
+
}
|
|
10573
|
+
return true;
|
|
10574
|
+
}
|
|
10575
|
+
|
|
10576
|
+
// src/auth-menu/top-menu.ts
|
|
10577
|
+
async function runTopMenu(deps) {
|
|
10578
|
+
for (; ; ) {
|
|
10579
|
+
const providers = await loadProviders(deps);
|
|
10580
|
+
renderTopMenu(deps.renderer, providers);
|
|
10581
|
+
const ids = Object.keys(providers).sort();
|
|
10582
|
+
const choice = (await deps.reader.readLine(`
|
|
10583
|
+
${color.amber("?")} Pick: `)).trim().toLowerCase();
|
|
10584
|
+
if (!choice || choice === "q" || choice === "quit" || choice === "exit") {
|
|
10585
|
+
deps.renderer.write(color.dim("Done.\n"));
|
|
10586
|
+
return 0;
|
|
10587
|
+
}
|
|
10588
|
+
if (choice === "a" || choice === "add") {
|
|
10589
|
+
await addFromCatalog(deps);
|
|
10590
|
+
continue;
|
|
10591
|
+
}
|
|
10592
|
+
if (choice === "c" || choice === "custom") {
|
|
10593
|
+
await addCustomProvider(deps);
|
|
10594
|
+
continue;
|
|
10595
|
+
}
|
|
10596
|
+
const idx = Number.parseInt(choice, 10);
|
|
10597
|
+
if (!Number.isNaN(idx) && idx >= 1 && idx <= ids.length) {
|
|
10598
|
+
const pid = ids[idx - 1];
|
|
10599
|
+
await manageProvider(pid, deps);
|
|
10600
|
+
continue;
|
|
10601
|
+
}
|
|
10602
|
+
const byId = ids.find((id) => id.toLowerCase() === choice);
|
|
10603
|
+
if (byId) {
|
|
10604
|
+
await manageProvider(byId, deps);
|
|
10605
|
+
continue;
|
|
10606
|
+
}
|
|
10607
|
+
deps.renderer.writeError(`Unknown selection: "${choice}"`);
|
|
10608
|
+
}
|
|
9534
10609
|
}
|
|
10610
|
+
|
|
10611
|
+
// src/auth-menu/direct.ts
|
|
10612
|
+
init_provider_config_utils();
|
|
9535
10613
|
async function runAuthDirect(deps, opts) {
|
|
9536
10614
|
const { providerId } = opts;
|
|
9537
10615
|
const providers = await loadProviders(deps);
|
|
@@ -9559,7 +10637,9 @@ async function runAuthDirect(deps, opts) {
|
|
|
9559
10637
|
opts.baseUrl ??= knownBase;
|
|
9560
10638
|
opts.envVars ??= knownEnv;
|
|
9561
10639
|
}
|
|
9562
|
-
const usedLabels = new Set(
|
|
10640
|
+
const usedLabels = new Set(
|
|
10641
|
+
existing ? normalizeKeys(existing).map((k) => k.label) : []
|
|
10642
|
+
);
|
|
9563
10643
|
let label = opts.label ?? "default";
|
|
9564
10644
|
if (usedLabels.has(label)) {
|
|
9565
10645
|
let n = 2;
|
|
@@ -9569,7 +10649,7 @@ async function runAuthDirect(deps, opts) {
|
|
|
9569
10649
|
}
|
|
9570
10650
|
const apiKey = await readKeyInput(deps, `API key for ${providerId}/${label}`);
|
|
9571
10651
|
if (!apiKey) return 1;
|
|
9572
|
-
await
|
|
10652
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
9573
10653
|
const p = all[providerId] ?? { type: providerId };
|
|
9574
10654
|
if (!p.type) p.type = providerId;
|
|
9575
10655
|
if (!p.family && opts.family) p.family = opts.family;
|
|
@@ -9585,32 +10665,9 @@ async function runAuthDirect(deps, opts) {
|
|
|
9585
10665
|
deps.renderer.writeInfo(`Use: wstack --provider ${providerId} "<task>"`);
|
|
9586
10666
|
return 0;
|
|
9587
10667
|
}
|
|
9588
|
-
async function readKeyInput(deps, intent) {
|
|
9589
|
-
const key = (await deps.reader.readSecret(
|
|
9590
|
-
` ${color.amber("?")} ${intent} ${color.dim("(hidden, paste OK)")}: `
|
|
9591
|
-
)).trim();
|
|
9592
|
-
if (!key) {
|
|
9593
|
-
deps.renderer.writeError("No key entered.");
|
|
9594
|
-
return void 0;
|
|
9595
|
-
}
|
|
9596
|
-
return key;
|
|
9597
|
-
}
|
|
9598
|
-
function loadProviders(deps) {
|
|
9599
|
-
return loadConfigProviders(deps.globalConfigPath, deps.vault, {
|
|
9600
|
-
warn: (msg) => deps.renderer.writeWarning(msg)
|
|
9601
|
-
});
|
|
9602
|
-
}
|
|
9603
|
-
function mutateProviders(deps, mutator) {
|
|
9604
|
-
return mutateConfigProviders(deps.globalConfigPath, deps.vault, mutator);
|
|
9605
|
-
}
|
|
9606
10668
|
|
|
9607
10669
|
// src/subcommands/handlers/auth.ts
|
|
9608
|
-
|
|
9609
|
-
if (value === null || value === void 0) {
|
|
9610
|
-
throw new Error("Expected value to be defined");
|
|
9611
|
-
}
|
|
9612
|
-
return value;
|
|
9613
|
-
}
|
|
10670
|
+
init_provider_config_utils();
|
|
9614
10671
|
var authCmd = async (args, deps) => {
|
|
9615
10672
|
const flags = parseAuthFlags(args);
|
|
9616
10673
|
const menuDeps = {
|
|
@@ -9620,15 +10677,175 @@ var authCmd = async (args, deps) => {
|
|
|
9620
10677
|
vault: deps.vault,
|
|
9621
10678
|
globalConfigPath: deps.paths.globalConfig
|
|
9622
10679
|
};
|
|
9623
|
-
if (flags.positional.length === 0)
|
|
10680
|
+
if (flags.positional.length === 0) {
|
|
10681
|
+
return runTopMenu(menuDeps);
|
|
10682
|
+
}
|
|
10683
|
+
const first = flags.positional[0];
|
|
10684
|
+
if (first === "list" || first === "ls") {
|
|
10685
|
+
return runAuthList(menuDeps);
|
|
10686
|
+
}
|
|
10687
|
+
if (first === "status") {
|
|
10688
|
+
const pid = flags.positional[1];
|
|
10689
|
+
if (!pid) {
|
|
10690
|
+
deps.renderer.writeError("Usage: wstack auth status <provider>");
|
|
10691
|
+
return 1;
|
|
10692
|
+
}
|
|
10693
|
+
return runAuthStatus(menuDeps, pid);
|
|
10694
|
+
}
|
|
10695
|
+
if (first === "remove" || first === "rm") {
|
|
10696
|
+
const pid = flags.positional[1];
|
|
10697
|
+
if (!pid) {
|
|
10698
|
+
deps.renderer.writeError("Usage: wstack auth remove <provider> [--force]");
|
|
10699
|
+
return 1;
|
|
10700
|
+
}
|
|
10701
|
+
return runAuthRemove(menuDeps, pid);
|
|
10702
|
+
}
|
|
9624
10703
|
return runAuthDirect(menuDeps, {
|
|
9625
|
-
providerId:
|
|
10704
|
+
providerId: first,
|
|
9626
10705
|
label: flags.label,
|
|
9627
10706
|
family: flags.family,
|
|
9628
10707
|
baseUrl: flags.baseUrl,
|
|
9629
10708
|
envVars: flags.envVars
|
|
9630
10709
|
});
|
|
9631
10710
|
};
|
|
10711
|
+
async function runAuthList(deps) {
|
|
10712
|
+
let providers;
|
|
10713
|
+
try {
|
|
10714
|
+
providers = await loadConfigProviders(deps.globalConfigPath, deps.vault);
|
|
10715
|
+
} catch (err) {
|
|
10716
|
+
deps.renderer.writeError(`Could not read config: ${err.message}`);
|
|
10717
|
+
return 1;
|
|
10718
|
+
}
|
|
10719
|
+
const ids = Object.keys(providers).sort();
|
|
10720
|
+
if (ids.length === 0) {
|
|
10721
|
+
deps.renderer.write(
|
|
10722
|
+
`${color.dim("No providers configured.")}
|
|
10723
|
+
${color.dim("Run")} ${color.bold("wstack auth")} ${color.dim("to add one.")}
|
|
10724
|
+
`
|
|
10725
|
+
);
|
|
10726
|
+
return 0;
|
|
10727
|
+
}
|
|
10728
|
+
deps.renderer.write(`
|
|
10729
|
+
${color.bold("Saved providers")} ${color.dim(`(${ids.length})`)}
|
|
10730
|
+
|
|
10731
|
+
`);
|
|
10732
|
+
for (const id of ids) {
|
|
10733
|
+
const cfg = providers[id];
|
|
10734
|
+
if (!cfg) continue;
|
|
10735
|
+
const keys = normalizeKeys(cfg);
|
|
10736
|
+
const active = cfg.activeKey ?? keys[0]?.label;
|
|
10737
|
+
const famTag = cfg.family ? `${cfg.family}` : color.amber("no-family");
|
|
10738
|
+
const aliasHint = cfg.type && cfg.type !== id ? color.dim(` (\u2192 ${cfg.type})`) : "";
|
|
10739
|
+
const modelHint = cfg.models && cfg.models.length > 0 ? color.dim(` [${cfg.models.length} models]`) : "";
|
|
10740
|
+
deps.renderer.write(` ${color.bold(id)}${aliasHint}
|
|
10741
|
+
`);
|
|
10742
|
+
deps.renderer.write(
|
|
10743
|
+
` family: ${famTag} baseUrl: ${cfg.baseUrl ?? color.dim("unset")}${modelHint}
|
|
10744
|
+
`
|
|
10745
|
+
);
|
|
10746
|
+
if (keys.length === 0) {
|
|
10747
|
+
deps.renderer.write(` ${color.amber("no keys")}
|
|
10748
|
+
`);
|
|
10749
|
+
} else {
|
|
10750
|
+
deps.renderer.write(` ${color.dim(`${keys.length} key${keys.length === 1 ? "" : "s"}:`)}
|
|
10751
|
+
`);
|
|
10752
|
+
for (const k of keys) {
|
|
10753
|
+
const marker = k.label === active ? color.green("\u25CF") : color.dim("\u25CB");
|
|
10754
|
+
deps.renderer.write(
|
|
10755
|
+
` ${marker} ${k.label.padEnd(18)} ${maskedKey(k.apiKey)} ${color.dim(k.createdAt)}
|
|
10756
|
+
`
|
|
10757
|
+
);
|
|
10758
|
+
}
|
|
10759
|
+
}
|
|
10760
|
+
deps.renderer.write("\n");
|
|
10761
|
+
}
|
|
10762
|
+
deps.renderer.write(
|
|
10763
|
+
color.dim(`Manage: wstack auth Add key: wstack auth <provider>
|
|
10764
|
+
`)
|
|
10765
|
+
);
|
|
10766
|
+
return 0;
|
|
10767
|
+
}
|
|
10768
|
+
async function runAuthStatus(deps, providerId) {
|
|
10769
|
+
let providers;
|
|
10770
|
+
try {
|
|
10771
|
+
providers = await loadConfigProviders(deps.globalConfigPath, deps.vault);
|
|
10772
|
+
} catch (err) {
|
|
10773
|
+
deps.renderer.writeError(`Could not read config: ${err.message}`);
|
|
10774
|
+
return 1;
|
|
10775
|
+
}
|
|
10776
|
+
const cfg = providers[providerId];
|
|
10777
|
+
if (!cfg) {
|
|
10778
|
+
deps.renderer.writeError(`Provider "${providerId}" not found in config.`);
|
|
10779
|
+
deps.renderer.write(
|
|
10780
|
+
color.dim(`Run ${color.bold("wstack auth list")} to see saved providers.
|
|
10781
|
+
`)
|
|
10782
|
+
);
|
|
10783
|
+
return 1;
|
|
10784
|
+
}
|
|
10785
|
+
const keys = normalizeKeys(cfg);
|
|
10786
|
+
const active = cfg.activeKey ?? keys[0]?.label;
|
|
10787
|
+
const lines = [
|
|
10788
|
+
`
|
|
10789
|
+
${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.amber("[no family]")}`,
|
|
10790
|
+
"",
|
|
10791
|
+
` type: ${color.cyan(cfg.type ?? providerId)}`,
|
|
10792
|
+
` family: ${cfg.family ? color.cyan(cfg.family) : color.dim("unset")}`,
|
|
10793
|
+
` baseUrl: ${cfg.baseUrl ? color.cyan(cfg.baseUrl) : color.dim("unset")}`
|
|
10794
|
+
];
|
|
10795
|
+
if (cfg.models?.length) {
|
|
10796
|
+
lines.push(` models: ${color.cyan(cfg.models.join(", "))}`);
|
|
10797
|
+
}
|
|
10798
|
+
if (cfg.envVars?.length) {
|
|
10799
|
+
lines.push(` envVars: ${color.cyan(cfg.envVars.join(", "))}`);
|
|
10800
|
+
}
|
|
10801
|
+
lines.push("");
|
|
10802
|
+
if (keys.length === 0) {
|
|
10803
|
+
lines.push(color.amber(" (no keys saved)"));
|
|
10804
|
+
} else {
|
|
10805
|
+
lines.push(` ${color.dim("Keys:")}`);
|
|
10806
|
+
for (const k of keys) {
|
|
10807
|
+
const marker = k.label === active ? color.green("\u25CF") : color.dim("\u25CB");
|
|
10808
|
+
lines.push(
|
|
10809
|
+
` ${marker} ${color.bold(k.label.padEnd(18))} ${maskedKey(k.apiKey)} ${color.dim(k.createdAt)}`
|
|
10810
|
+
);
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
lines.push("");
|
|
10814
|
+
lines.push(color.dim(`Manage: wstack auth \u2192 pick ${providerId}`));
|
|
10815
|
+
deps.renderer.write(lines.join("\n") + "\n");
|
|
10816
|
+
return 0;
|
|
10817
|
+
}
|
|
10818
|
+
async function runAuthRemove(deps, providerId) {
|
|
10819
|
+
const providers = await loadConfigProviders(deps.globalConfigPath, deps.vault);
|
|
10820
|
+
if (!providers[providerId]) {
|
|
10821
|
+
deps.renderer.writeError(`Provider "${providerId}" not found.`);
|
|
10822
|
+
return 1;
|
|
10823
|
+
}
|
|
10824
|
+
deps.renderer.write(
|
|
10825
|
+
`${color.amber("!")} This will remove "${providerId}" and all its saved keys.
|
|
10826
|
+
`
|
|
10827
|
+
);
|
|
10828
|
+
const answer = (await deps.reader.readLine(
|
|
10829
|
+
` ${color.amber("?")} Confirm removal? ${color.dim("[y/N]")} `
|
|
10830
|
+
)).trim().toLowerCase();
|
|
10831
|
+
if (answer !== "y" && answer !== "yes") {
|
|
10832
|
+
deps.renderer.write(color.dim("Cancelled.\n"));
|
|
10833
|
+
return 0;
|
|
10834
|
+
}
|
|
10835
|
+
try {
|
|
10836
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
10837
|
+
delete all[providerId];
|
|
10838
|
+
});
|
|
10839
|
+
deps.renderer.write(
|
|
10840
|
+
` ${color.green("\u2713")} Removed ${color.bold(providerId)}.
|
|
10841
|
+
`
|
|
10842
|
+
);
|
|
10843
|
+
return 0;
|
|
10844
|
+
} catch (err) {
|
|
10845
|
+
deps.renderer.writeError(`Failed to remove: ${err.message}`);
|
|
10846
|
+
return 1;
|
|
10847
|
+
}
|
|
10848
|
+
}
|
|
9632
10849
|
|
|
9633
10850
|
// src/subcommands/handlers/update.ts
|
|
9634
10851
|
init_update_check();
|
|
@@ -9658,7 +10875,8 @@ var updateCmd = async (args, deps) => {
|
|
|
9658
10875
|
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
9659
10876
|
const child = spawn(npmCommand, ["install", "-g", "wrongstack@latest"], {
|
|
9660
10877
|
cwd,
|
|
9661
|
-
stdio: "pipe"
|
|
10878
|
+
stdio: "pipe",
|
|
10879
|
+
signal: AbortSignal.timeout(12e4)
|
|
9662
10880
|
});
|
|
9663
10881
|
let _stderr = "";
|
|
9664
10882
|
child.stderr?.on("data", (d) => {
|
|
@@ -9845,12 +11063,6 @@ var doctorCmd = async (_args, deps) => {
|
|
|
9845
11063
|
deps.renderer.write(color.green("All checks passed.\n"));
|
|
9846
11064
|
return 0;
|
|
9847
11065
|
};
|
|
9848
|
-
function expectDefined9(value) {
|
|
9849
|
-
if (value === null || value === void 0) {
|
|
9850
|
-
throw new Error("Expected value to be defined");
|
|
9851
|
-
}
|
|
9852
|
-
return value;
|
|
9853
|
-
}
|
|
9854
11066
|
var exportCmd = async (args, deps) => {
|
|
9855
11067
|
if (!deps.sessionStore) {
|
|
9856
11068
|
deps.renderer.writeError("No session store configured.");
|
|
@@ -9862,7 +11074,7 @@ var exportCmd = async (args, deps) => {
|
|
|
9862
11074
|
let includeDiagnostics = true;
|
|
9863
11075
|
let sessionId;
|
|
9864
11076
|
for (let i = 0; i < args.length; i++) {
|
|
9865
|
-
const a =
|
|
11077
|
+
const a = expectDefined(args[i]);
|
|
9866
11078
|
if (a === "--format" || a === "-f") {
|
|
9867
11079
|
const v = args[++i];
|
|
9868
11080
|
if (v !== "markdown" && v !== "json" && v !== "text") {
|
|
@@ -10124,12 +11336,6 @@ async function serveMcpStdio(deps) {
|
|
|
10124
11336
|
}
|
|
10125
11337
|
|
|
10126
11338
|
// src/subcommands/handlers/mcp.ts
|
|
10127
|
-
function expectDefined10(value) {
|
|
10128
|
-
if (value === null || value === void 0) {
|
|
10129
|
-
throw new Error("Expected value to be defined");
|
|
10130
|
-
}
|
|
10131
|
-
return value;
|
|
10132
|
-
}
|
|
10133
11339
|
var BUILT_IN_MCP = allServers();
|
|
10134
11340
|
var mcpCmd = async (args, deps) => {
|
|
10135
11341
|
const sub = args[0];
|
|
@@ -10180,7 +11386,7 @@ async function addMcpServer(args, deps) {
|
|
|
10180
11386
|
`);
|
|
10181
11387
|
if (Object.keys(deps.config.mcpServers ?? {}).length === 0)
|
|
10182
11388
|
for (const k of Object.keys(BUILT_IN_MCP)) {
|
|
10183
|
-
const s =
|
|
11389
|
+
const s = expectDefined(BUILT_IN_MCP[k]);
|
|
10184
11390
|
deps.renderer.write(` ${k.padEnd(20)} ${s.description}
|
|
10185
11391
|
`);
|
|
10186
11392
|
}
|
|
@@ -10458,12 +11664,6 @@ var projectsCmd = async (_args, deps) => {
|
|
|
10458
11664
|
return 0;
|
|
10459
11665
|
}
|
|
10460
11666
|
};
|
|
10461
|
-
function expectDefined11(value) {
|
|
10462
|
-
if (value === null || value === void 0) {
|
|
10463
|
-
throw new Error("Expected value to be defined");
|
|
10464
|
-
}
|
|
10465
|
-
return value;
|
|
10466
|
-
}
|
|
10467
11667
|
var providersCmd = async (args, deps) => {
|
|
10468
11668
|
const showAll = args.includes("--all");
|
|
10469
11669
|
const showUnsupported = args.includes("--unsupported");
|
|
@@ -10511,7 +11711,7 @@ ${color.dim(`Current: ${deps.config.provider ?? "<unset>"} / ${deps.config.model
|
|
|
10511
11711
|
function parseFlags2(args) {
|
|
10512
11712
|
const flags = {};
|
|
10513
11713
|
for (let i = 0; i < args.length; i++) {
|
|
10514
|
-
const a =
|
|
11714
|
+
const a = expectDefined(args[i]);
|
|
10515
11715
|
if (a.startsWith("--")) {
|
|
10516
11716
|
const eq = a.indexOf("=");
|
|
10517
11717
|
if (eq !== -1) {
|
|
@@ -10531,7 +11731,7 @@ function parseFlags2(args) {
|
|
|
10531
11731
|
function positionals(args) {
|
|
10532
11732
|
const out = [];
|
|
10533
11733
|
for (let i = 0; i < args.length; i++) {
|
|
10534
|
-
const a =
|
|
11734
|
+
const a = expectDefined(args[i]);
|
|
10535
11735
|
if (a.startsWith("--")) {
|
|
10536
11736
|
const eq = a.indexOf("=");
|
|
10537
11737
|
if (eq === -1) {
|
|
@@ -10686,7 +11886,7 @@ function parseSizeFlag(raw) {
|
|
|
10686
11886
|
const s = raw.trim().toLowerCase();
|
|
10687
11887
|
const match = /^(\d+(?:\.\d+)?)\s*(k|m|b)?$/.exec(s);
|
|
10688
11888
|
if (!match) return void 0;
|
|
10689
|
-
const num = Number.parseFloat(
|
|
11889
|
+
const num = Number.parseFloat(expectDefined(match[1]));
|
|
10690
11890
|
const unit = match[2];
|
|
10691
11891
|
if (unit === "b") return Math.round(num * 1e9);
|
|
10692
11892
|
if (unit === "m") return Math.round(num * 1e6);
|
|
@@ -11007,12 +12207,6 @@ Fleet Run: ${runId}
|
|
|
11007
12207
|
}
|
|
11008
12208
|
|
|
11009
12209
|
// src/subcommands/handlers/sessions-config.ts
|
|
11010
|
-
function expectDefined12(value) {
|
|
11011
|
-
if (value === null || value === void 0) {
|
|
11012
|
-
throw new Error("Expected value to be defined");
|
|
11013
|
-
}
|
|
11014
|
-
return value;
|
|
11015
|
-
}
|
|
11016
12210
|
var sessionsCmd = async (args, deps) => {
|
|
11017
12211
|
const sub = args[0];
|
|
11018
12212
|
if (sub === "fleet") {
|
|
@@ -11058,7 +12252,7 @@ var configCmd = async (args, deps) => {
|
|
|
11058
12252
|
};
|
|
11059
12253
|
function extractArg(args, key) {
|
|
11060
12254
|
const idx = args.indexOf(key);
|
|
11061
|
-
if (idx !== -1 && args[idx + 1] !== void 0) return
|
|
12255
|
+
if (idx !== -1 && args[idx + 1] !== void 0) return expectDefined(args[idx + 1]);
|
|
11062
12256
|
const eq = key.startsWith("--") ? args.find((a) => a.startsWith(`${key}=`)) : null;
|
|
11063
12257
|
if (eq) return eq.slice(eq.indexOf("=") + 1);
|
|
11064
12258
|
return null;
|
|
@@ -11134,12 +12328,6 @@ async function runRestore(args, deps) {
|
|
|
11134
12328
|
`);
|
|
11135
12329
|
return 0;
|
|
11136
12330
|
}
|
|
11137
|
-
function expectDefined13(value) {
|
|
11138
|
-
if (value === null || value === void 0) {
|
|
11139
|
-
throw new Error("Expected value to be defined");
|
|
11140
|
-
}
|
|
11141
|
-
return value;
|
|
11142
|
-
}
|
|
11143
12331
|
function parseRewindFlags(args) {
|
|
11144
12332
|
const flags = {};
|
|
11145
12333
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -11154,7 +12342,7 @@ function parseRewindFlags(args) {
|
|
|
11154
12342
|
}
|
|
11155
12343
|
function findSessionId(args) {
|
|
11156
12344
|
for (let i = 0; i < args.length; i++) {
|
|
11157
|
-
const a =
|
|
12345
|
+
const a = expectDefined(args[i]);
|
|
11158
12346
|
if (a === "--last" || a === "--to") {
|
|
11159
12347
|
i++;
|
|
11160
12348
|
continue;
|
|
@@ -11406,10 +12594,10 @@ var auditCmd = async (args, deps) => {
|
|
|
11406
12594
|
return verify.ok ? 0 : 1;
|
|
11407
12595
|
};
|
|
11408
12596
|
async function listAudits(log, dir, deps) {
|
|
11409
|
-
const
|
|
12597
|
+
const fs27 = await import('fs/promises');
|
|
11410
12598
|
let entries;
|
|
11411
12599
|
try {
|
|
11412
|
-
entries = await
|
|
12600
|
+
entries = await fs27.readdir(dir);
|
|
11413
12601
|
} catch {
|
|
11414
12602
|
deps.renderer.write(
|
|
11415
12603
|
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
@@ -11480,6 +12668,10 @@ var helpCmd = async (_args, deps) => {
|
|
|
11480
12668
|
" wstack sessions List recent sessions",
|
|
11481
12669
|
" wstack init Pick provider + model from models.dev",
|
|
11482
12670
|
" wstack auth Interactive key manager (list/add/update/delete)",
|
|
12671
|
+
" wstack auth list Quick listing of saved providers and keys",
|
|
12672
|
+
" wstack auth status <id> Detailed view of one provider",
|
|
12673
|
+
" wstack auth remove <id> Delete a provider (asks for confirmation)",
|
|
12674
|
+
" wstack auth <provider> Add a key for a provider (--label, --family, \u2026)",
|
|
11483
12675
|
" wstack config [show|edit] Show or edit effective config",
|
|
11484
12676
|
" wstack tools List registered tools",
|
|
11485
12677
|
" wstack skills List discovered skills",
|
|
@@ -11560,22 +12752,22 @@ function fmtDuration(ms) {
|
|
|
11560
12752
|
const remMin = m - h * 60;
|
|
11561
12753
|
return `${h}h${remMin}m`;
|
|
11562
12754
|
}
|
|
11563
|
-
function fmtTaskResultLine(r,
|
|
12755
|
+
function fmtTaskResultLine(r, color56) {
|
|
11564
12756
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
11565
12757
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
11566
12758
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
11567
12759
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
11568
|
-
const errKindChip = errKind ?
|
|
11569
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
12760
|
+
const errKindChip = errKind ? color56.dim(` [${errKind}]`) : "";
|
|
12761
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color56.dim(errTail)}` : "";
|
|
11570
12762
|
switch (r.status) {
|
|
11571
12763
|
case "success":
|
|
11572
|
-
return { mark:
|
|
12764
|
+
return { mark: color56.green("\u2713"), stats, tail: "" };
|
|
11573
12765
|
case "timeout":
|
|
11574
|
-
return { mark:
|
|
12766
|
+
return { mark: color56.yellow("\u23F1"), stats: `${color56.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
11575
12767
|
case "stopped":
|
|
11576
|
-
return { mark:
|
|
12768
|
+
return { mark: color56.dim("\u2298"), stats: `${color56.dim("stopped")} ${stats}`, tail: errSnip };
|
|
11577
12769
|
case "failed":
|
|
11578
|
-
return { mark:
|
|
12770
|
+
return { mark: color56.red("\u2717"), stats: `${color56.red("failed")} ${stats}`, tail: errSnip };
|
|
11579
12771
|
}
|
|
11580
12772
|
}
|
|
11581
12773
|
|
|
@@ -11697,6 +12889,7 @@ async function boot(argv) {
|
|
|
11697
12889
|
const isSingleShot = positional.length > 0 || typeof flags["prompt"] === "string";
|
|
11698
12890
|
const isInteractiveTTY = isStdinTTY() && !isSingleShot;
|
|
11699
12891
|
if (isInteractiveTTY) {
|
|
12892
|
+
await checkGitInCwd({ cwd, renderer, reader });
|
|
11700
12893
|
const cont = await runProjectCheck({ projectRoot, cwd, renderer, reader });
|
|
11701
12894
|
if (!cont) {
|
|
11702
12895
|
await reader.close();
|
|
@@ -11860,6 +13053,61 @@ async function boot(argv) {
|
|
|
11860
13053
|
updateInfo
|
|
11861
13054
|
};
|
|
11862
13055
|
}
|
|
13056
|
+
async function checkGitInCwd(opts) {
|
|
13057
|
+
const { cwd, renderer, reader } = opts;
|
|
13058
|
+
const cwdGit = path8.join(cwd, ".git");
|
|
13059
|
+
let hasCwdGit = false;
|
|
13060
|
+
try {
|
|
13061
|
+
await fsp4.access(cwdGit);
|
|
13062
|
+
hasCwdGit = true;
|
|
13063
|
+
} catch {
|
|
13064
|
+
}
|
|
13065
|
+
if (!hasCwdGit) {
|
|
13066
|
+
renderer.write(
|
|
13067
|
+
`
|
|
13068
|
+
${color.amber("\u25CB")} This folder has no ${color.bold(".git")} repository.
|
|
13069
|
+
`
|
|
13070
|
+
);
|
|
13071
|
+
const answer = (await reader.readLine(
|
|
13072
|
+
` ${color.amber("?")} Initialize one here? ${color.dim("[y/N]")} `
|
|
13073
|
+
)).trim().toLowerCase();
|
|
13074
|
+
if (answer === "y" || answer === "yes") {
|
|
13075
|
+
try {
|
|
13076
|
+
const { spawn: spawn4 } = await import('child_process');
|
|
13077
|
+
await new Promise((resolve5, reject) => {
|
|
13078
|
+
const child = spawn4("git", ["init"], {
|
|
13079
|
+
cwd,
|
|
13080
|
+
signal: AbortSignal.timeout(1e4)
|
|
13081
|
+
});
|
|
13082
|
+
child.on("error", reject);
|
|
13083
|
+
child.on(
|
|
13084
|
+
"close",
|
|
13085
|
+
(code) => code === 0 ? resolve5() : reject(new Error(`git init failed with ${code}`))
|
|
13086
|
+
);
|
|
13087
|
+
});
|
|
13088
|
+
renderer.write(` ${color.green("\u2713")} Git repository initialized
|
|
13089
|
+
`);
|
|
13090
|
+
hasCwdGit = true;
|
|
13091
|
+
} catch (err) {
|
|
13092
|
+
renderer.writeError(
|
|
13093
|
+
`git init failed: ${err instanceof Error ? err.message : String(err)}
|
|
13094
|
+
`
|
|
13095
|
+
);
|
|
13096
|
+
}
|
|
13097
|
+
}
|
|
13098
|
+
}
|
|
13099
|
+
const parentDir = path8.dirname(cwd);
|
|
13100
|
+
if (parentDir !== cwd) {
|
|
13101
|
+
try {
|
|
13102
|
+
await fsp4.access(path8.join(parentDir, ".git"));
|
|
13103
|
+
renderer.write(
|
|
13104
|
+
` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
|
|
13105
|
+
`
|
|
13106
|
+
);
|
|
13107
|
+
} catch {
|
|
13108
|
+
}
|
|
13109
|
+
}
|
|
13110
|
+
}
|
|
11863
13111
|
var CONTEXT_OVERFLOW_RE = /context window|exceeds the context|too many tokens|context.*tokens/i;
|
|
11864
13112
|
function contextOverflowHint(err) {
|
|
11865
13113
|
const structured = err.code === ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW || err.code === ERROR_CODES.AGENT_CONTEXT_OVERFLOW;
|
|
@@ -12128,7 +13376,7 @@ function parsePredictions(raw, max = 3) {
|
|
|
12128
13376
|
}
|
|
12129
13377
|
return out;
|
|
12130
13378
|
}
|
|
12131
|
-
function
|
|
13379
|
+
function extractText2(content) {
|
|
12132
13380
|
if (Array.isArray(content)) {
|
|
12133
13381
|
return content[0]?.text ?? "";
|
|
12134
13382
|
}
|
|
@@ -12159,7 +13407,7 @@ async function predictNextTasks(input, opts) {
|
|
|
12159
13407
|
},
|
|
12160
13408
|
{ signal: internal.signal }
|
|
12161
13409
|
);
|
|
12162
|
-
return parsePredictions(
|
|
13410
|
+
return parsePredictions(extractText2(resp.content), max);
|
|
12163
13411
|
} catch {
|
|
12164
13412
|
return [];
|
|
12165
13413
|
} finally {
|
|
@@ -12168,12 +13416,6 @@ async function predictNextTasks(input, opts) {
|
|
|
12168
13416
|
}
|
|
12169
13417
|
}
|
|
12170
13418
|
init_sdd();
|
|
12171
|
-
function expectDefined14(value) {
|
|
12172
|
-
if (value === null || value === void 0) {
|
|
12173
|
-
throw new Error("Expected value to be defined");
|
|
12174
|
-
}
|
|
12175
|
-
return value;
|
|
12176
|
-
}
|
|
12177
13419
|
async function runRepl(opts) {
|
|
12178
13420
|
if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
|
|
12179
13421
|
await renderGoalBanner(opts);
|
|
@@ -12689,7 +13931,7 @@ async function renderGoalBanner(opts) {
|
|
|
12689
13931
|
color.dim("Goal: ") + stateColor(summary) + color.dim(` [${goal.goalState}] (iter ${goal.iterations})`) + "\n"
|
|
12690
13932
|
);
|
|
12691
13933
|
if (goal.journal.length > 0) {
|
|
12692
|
-
const lastEntry =
|
|
13934
|
+
const lastEntry = expectDefined(goal.journal[goal.journal.length - 1]);
|
|
12693
13935
|
const statusIcon = lastEntry.status === "success" ? "\u2713" : lastEntry.status === "failure" ? "\u2717" : lastEntry.status === "aborted" ? "\u2298" : lastEntry.status === "skipped" ? "\u229D" : "\xB7";
|
|
12694
13936
|
opts.renderer.write(
|
|
12695
13937
|
color.dim(` Last: ${statusIcon} ${lastEntry.task} (${lastEntry.status})`) + "\n"
|
|
@@ -13159,8 +14401,8 @@ async function execute(deps) {
|
|
|
13159
14401
|
initialGoal: goalFlag,
|
|
13160
14402
|
initialAsk: askFlag,
|
|
13161
14403
|
projectRoot,
|
|
13162
|
-
getSDDContext: () => {
|
|
13163
|
-
const { getActiveSDDContext: getActiveSDDContext2 } = (init_sdd(),
|
|
14404
|
+
getSDDContext: async () => {
|
|
14405
|
+
const { getActiveSDDContext: getActiveSDDContext2 } = await Promise.resolve().then(() => (init_sdd(), sdd_exports));
|
|
13164
14406
|
return getActiveSDDContext2();
|
|
13165
14407
|
},
|
|
13166
14408
|
onSDDOutput: async (output) => {
|
|
@@ -13171,7 +14413,7 @@ async function execute(deps) {
|
|
|
13171
14413
|
autoDetectTaskCompletion: autoDetectTaskCompletion2,
|
|
13172
14414
|
getTaskProgress: getTaskProgress2,
|
|
13173
14415
|
getActiveSDDPhase: getActiveSDDPhase2
|
|
13174
|
-
} = (init_sdd(),
|
|
14416
|
+
} = await Promise.resolve().then(() => (init_sdd(), sdd_exports));
|
|
13175
14417
|
const messages = [];
|
|
13176
14418
|
const specSaved = await trySaveSpecFromAIOutput2(output);
|
|
13177
14419
|
if (specSaved)
|
|
@@ -13291,12 +14533,6 @@ async function execute(deps) {
|
|
|
13291
14533
|
}
|
|
13292
14534
|
return code;
|
|
13293
14535
|
}
|
|
13294
|
-
function expectDefined15(value) {
|
|
13295
|
-
if (value === null || value === void 0) {
|
|
13296
|
-
throw new Error("Expected value to be defined");
|
|
13297
|
-
}
|
|
13298
|
-
return value;
|
|
13299
|
-
}
|
|
13300
14536
|
function buildRoutingRunner(config, host) {
|
|
13301
14537
|
const standardRunner = makeAgentSubagentRunner({
|
|
13302
14538
|
factory: host.makeSubagentFactory(config),
|
|
@@ -13305,7 +14541,7 @@ function buildRoutingRunner(config, host) {
|
|
|
13305
14541
|
return async (task, ctx) => {
|
|
13306
14542
|
const subCfg = ctx.config;
|
|
13307
14543
|
if (subCfg.provider === "acp") {
|
|
13308
|
-
const cacheKey = subCfg.role ?? subCfg.name ??
|
|
14544
|
+
const cacheKey = subCfg.role ?? subCfg.name ?? expectDefined(subCfg.id);
|
|
13309
14545
|
return host.buildACPRunner(cacheKey).then((r) => r(task, ctx));
|
|
13310
14546
|
}
|
|
13311
14547
|
return standardRunner(task, ctx);
|
|
@@ -14163,7 +15399,8 @@ function gitText(args, cwd) {
|
|
|
14163
15399
|
const child = spawn("git", args, {
|
|
14164
15400
|
cwd,
|
|
14165
15401
|
env: buildChildEnv(),
|
|
14166
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
15402
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
15403
|
+
signal: AbortSignal.timeout(1e4)
|
|
14167
15404
|
});
|
|
14168
15405
|
child.stdout?.on("data", (c) => {
|
|
14169
15406
|
out += c.toString();
|
|
@@ -14186,7 +15423,8 @@ function runCmd(cmd, args, cwd, shell = false) {
|
|
|
14186
15423
|
cwd,
|
|
14187
15424
|
env: buildChildEnv(),
|
|
14188
15425
|
stdio: ["ignore", "pipe", "pipe"],
|
|
14189
|
-
shell: shell || process.platform === "win32"
|
|
15426
|
+
shell: shell || process.platform === "win32",
|
|
15427
|
+
signal: AbortSignal.timeout(3e4)
|
|
14190
15428
|
});
|
|
14191
15429
|
child.stdout?.on("data", (c) => {
|
|
14192
15430
|
out += c.toString();
|
|
@@ -14922,10 +16160,9 @@ async function setupCodebaseIndexing(deps) {
|
|
|
14922
16160
|
const onError = (err) => logger.debug(`codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
14923
16161
|
if (idx.onSessionStart) {
|
|
14924
16162
|
void runStartupIndex({ projectRoot }).then((r) => {
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
`);
|
|
16163
|
+
logger.info(
|
|
16164
|
+
`codebase index ready: ${r.symbolsIndexed} symbols \xB7 ${r.filesIndexed} files \xB7 ${r.durationMs}ms`
|
|
16165
|
+
);
|
|
14929
16166
|
}).catch((err) => {
|
|
14930
16167
|
logger.warn(
|
|
14931
16168
|
`codebase index (startup) failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -14959,7 +16196,7 @@ ${summary}
|
|
|
14959
16196
|
let watcher;
|
|
14960
16197
|
if (idx.watchExternal) {
|
|
14961
16198
|
try {
|
|
14962
|
-
watcher =
|
|
16199
|
+
watcher = fs13.watch(projectRoot, { recursive: true }, (_event, filename) => {
|
|
14963
16200
|
if (!filename) return;
|
|
14964
16201
|
const rel = filename.toString();
|
|
14965
16202
|
if (isIgnored(rel)) return;
|
|
@@ -15194,12 +16431,6 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
15194
16431
|
}
|
|
15195
16432
|
return { resolvedProvider, provider, providerRegistry };
|
|
15196
16433
|
}
|
|
15197
|
-
function expectDefined16(value) {
|
|
15198
|
-
if (value === null || value === void 0) {
|
|
15199
|
-
throw new Error("Expected value to be defined");
|
|
15200
|
-
}
|
|
15201
|
-
return value;
|
|
15202
|
-
}
|
|
15203
16434
|
async function setupSession(params) {
|
|
15204
16435
|
const {
|
|
15205
16436
|
config,
|
|
@@ -15265,7 +16496,7 @@ async function setupSession(params) {
|
|
|
15265
16496
|
const context = new Context({
|
|
15266
16497
|
systemPrompt,
|
|
15267
16498
|
provider,
|
|
15268
|
-
session:
|
|
16499
|
+
session: expectDefined(session),
|
|
15269
16500
|
signal: ctxSignal,
|
|
15270
16501
|
tokenCounter,
|
|
15271
16502
|
cwd,
|
|
@@ -15293,6 +16524,8 @@ async function setupSession(params) {
|
|
|
15293
16524
|
);
|
|
15294
16525
|
const planPath = path8.join(wpaths.projectSessions, `${session?.id}.plan.json`);
|
|
15295
16526
|
context.state.setMeta("plan.path", planPath);
|
|
16527
|
+
const taskPath = path8.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
|
|
16528
|
+
context.state.setMeta("task.path", taskPath);
|
|
15296
16529
|
let dirState;
|
|
15297
16530
|
if (resumeId) {
|
|
15298
16531
|
try {
|
|
@@ -15321,7 +16554,7 @@ async function setupSession(params) {
|
|
|
15321
16554
|
}
|
|
15322
16555
|
}
|
|
15323
16556
|
return {
|
|
15324
|
-
session:
|
|
16557
|
+
session: expectDefined(session),
|
|
15325
16558
|
sessionRef,
|
|
15326
16559
|
context,
|
|
15327
16560
|
restoredMessages,
|
|
@@ -15430,12 +16663,6 @@ async function launchEternalFromFlag(deps) {
|
|
|
15430
16663
|
}
|
|
15431
16664
|
|
|
15432
16665
|
// src/cli-main.ts
|
|
15433
|
-
function expectDefined17(value) {
|
|
15434
|
-
if (value === null || value === void 0) {
|
|
15435
|
-
throw new Error("Expected value to be defined");
|
|
15436
|
-
}
|
|
15437
|
-
return value;
|
|
15438
|
-
}
|
|
15439
16666
|
async function main(argv) {
|
|
15440
16667
|
const ctx = await boot(argv);
|
|
15441
16668
|
if (typeof ctx === "number") return ctx;
|
|
@@ -15455,11 +16682,14 @@ async function main(argv) {
|
|
|
15455
16682
|
} = ctx;
|
|
15456
16683
|
updateInfo = await printUpdateNotice(updateInfo);
|
|
15457
16684
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
16685
|
+
const events = new EventBus();
|
|
16686
|
+
events.setLogger(logger);
|
|
15458
16687
|
const container = createDefaultContainer({
|
|
15459
16688
|
config,
|
|
15460
16689
|
wpaths,
|
|
15461
16690
|
logger,
|
|
15462
16691
|
modelsRegistry,
|
|
16692
|
+
events,
|
|
15463
16693
|
permission: {
|
|
15464
16694
|
yolo: config.yolo,
|
|
15465
16695
|
yoloDestructive: flags["yolo-destructive"] === true || flags["force-all-yolo"] === true,
|
|
@@ -15558,9 +16788,9 @@ async function main(argv) {
|
|
|
15558
16788
|
if (config.features.memory) {
|
|
15559
16789
|
toolRegistry.register(rememberTool(memoryStore));
|
|
15560
16790
|
toolRegistry.register(forgetTool(memoryStore));
|
|
16791
|
+
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
16792
|
+
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
15561
16793
|
}
|
|
15562
|
-
const events = new EventBus();
|
|
15563
|
-
events.setLogger(logger);
|
|
15564
16794
|
const { metricsSink, healthRegistry } = (() => {
|
|
15565
16795
|
const ms = setupMetrics({
|
|
15566
16796
|
flags,
|
|
@@ -15574,35 +16804,40 @@ async function main(argv) {
|
|
|
15574
16804
|
const tuiOwnsScreen = flags.tui === true && flags["no-tui"] !== true;
|
|
15575
16805
|
const spinner = new Spinner(process.stderr, { enabled: !tuiOwnsScreen });
|
|
15576
16806
|
let lastInputTokens = 0;
|
|
15577
|
-
|
|
16807
|
+
const teardownHandlers = [];
|
|
16808
|
+
const evOn = (event, handler) => {
|
|
16809
|
+
events.on(event, handler);
|
|
16810
|
+
teardownHandlers.push(() => events.off(event, handler));
|
|
16811
|
+
};
|
|
16812
|
+
evOn("provider.response", (e) => {
|
|
15578
16813
|
lastInputTokens = e.usage?.input ?? 0;
|
|
15579
16814
|
updateSpinnerContext();
|
|
15580
16815
|
});
|
|
15581
|
-
|
|
16816
|
+
evOn("iteration.started", () => {
|
|
15582
16817
|
updateSpinnerContext();
|
|
15583
16818
|
spinner.start(color.dim(`${config.provider}/${config.model} thinking\u2026`));
|
|
15584
16819
|
});
|
|
15585
|
-
|
|
16820
|
+
evOn("provider.response", () => {
|
|
15586
16821
|
spinner.stop();
|
|
15587
16822
|
});
|
|
15588
|
-
|
|
16823
|
+
evOn("error", () => {
|
|
15589
16824
|
spinner.stop();
|
|
15590
16825
|
});
|
|
15591
16826
|
let streamingActive = false;
|
|
15592
|
-
|
|
16827
|
+
evOn("provider.text_delta", (p) => {
|
|
15593
16828
|
if (!streamingActive) {
|
|
15594
16829
|
spinner.stop();
|
|
15595
16830
|
streamingActive = true;
|
|
15596
16831
|
}
|
|
15597
16832
|
renderer.write(p.text);
|
|
15598
16833
|
});
|
|
15599
|
-
|
|
16834
|
+
evOn("iteration.completed", () => {
|
|
15600
16835
|
if (streamingActive) {
|
|
15601
16836
|
renderer.write("\n");
|
|
15602
16837
|
streamingActive = false;
|
|
15603
16838
|
}
|
|
15604
16839
|
});
|
|
15605
|
-
|
|
16840
|
+
evOn("provider.retry", (p) => {
|
|
15606
16841
|
spinner.stop();
|
|
15607
16842
|
if (streamingActive) {
|
|
15608
16843
|
renderer.write("\n");
|
|
@@ -15613,7 +16848,7 @@ async function main(argv) {
|
|
|
15613
16848
|
`));
|
|
15614
16849
|
spinner.start(color.dim(`${config.provider}/${config.model} thinking\u2026`));
|
|
15615
16850
|
});
|
|
15616
|
-
|
|
16851
|
+
evOn("provider.error", (p) => {
|
|
15617
16852
|
spinner.stop();
|
|
15618
16853
|
if (streamingActive) {
|
|
15619
16854
|
renderer.write("\n");
|
|
@@ -15666,7 +16901,7 @@ async function main(argv) {
|
|
|
15666
16901
|
);
|
|
15667
16902
|
const stats = new SessionStats(events, tokenCounter);
|
|
15668
16903
|
const errorRing = [];
|
|
15669
|
-
|
|
16904
|
+
evOn("error", (e) => {
|
|
15670
16905
|
const err = e.err;
|
|
15671
16906
|
const code = err && typeof err === "object" && "code" in err && typeof err.code === "string" ? err.code : "UNKNOWN";
|
|
15672
16907
|
const message = e.err instanceof Error ? e.err.message : String(e.err);
|
|
@@ -15681,7 +16916,7 @@ async function main(argv) {
|
|
|
15681
16916
|
}).catch(() => {
|
|
15682
16917
|
});
|
|
15683
16918
|
});
|
|
15684
|
-
|
|
16919
|
+
evOn("tool.started", (e) => {
|
|
15685
16920
|
sessionBridge.append({
|
|
15686
16921
|
type: "tool_call_start",
|
|
15687
16922
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15691,7 +16926,7 @@ async function main(argv) {
|
|
|
15691
16926
|
}).catch(() => {
|
|
15692
16927
|
});
|
|
15693
16928
|
});
|
|
15694
|
-
|
|
16929
|
+
evOn("tool.executed", (e) => {
|
|
15695
16930
|
sessionBridge.append({
|
|
15696
16931
|
type: "tool_call_end",
|
|
15697
16932
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15707,16 +16942,16 @@ async function main(argv) {
|
|
|
15707
16942
|
});
|
|
15708
16943
|
});
|
|
15709
16944
|
if (!tuiOwnsScreen) {
|
|
15710
|
-
|
|
16945
|
+
evOn("delegate.started", (e) => {
|
|
15711
16946
|
const task = e.task.length > 100 ? `${e.task.slice(0, 99)}\u2026` : e.task;
|
|
15712
16947
|
renderer.writeInfo(`\u{1F91D} Delegating \u2192 ${e.target}: ${task}`);
|
|
15713
16948
|
});
|
|
15714
|
-
|
|
16949
|
+
evOn("delegate.completed", (e) => {
|
|
15715
16950
|
const cost = e.costUsd && e.costUsd > 0 ? ` \xB7 $${e.costUsd.toFixed(4)}` : "";
|
|
15716
16951
|
renderer.writeInfo(`${e.ok ? "\u2705" : "\u274C"} ${e.summary}${cost}`);
|
|
15717
16952
|
});
|
|
15718
16953
|
}
|
|
15719
|
-
|
|
16954
|
+
evOn("tool.progress", (e) => {
|
|
15720
16955
|
sessionBridge.append({
|
|
15721
16956
|
type: "tool_progress",
|
|
15722
16957
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15726,7 +16961,7 @@ async function main(argv) {
|
|
|
15726
16961
|
}).catch(() => {
|
|
15727
16962
|
});
|
|
15728
16963
|
});
|
|
15729
|
-
|
|
16964
|
+
evOn("provider.retry", (e) => {
|
|
15730
16965
|
sessionBridge.append({
|
|
15731
16966
|
type: "provider_retry",
|
|
15732
16967
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15738,7 +16973,7 @@ async function main(argv) {
|
|
|
15738
16973
|
}).catch(() => {
|
|
15739
16974
|
});
|
|
15740
16975
|
});
|
|
15741
|
-
|
|
16976
|
+
evOn("provider.error", (e) => {
|
|
15742
16977
|
sessionBridge.append({
|
|
15743
16978
|
type: "provider_error",
|
|
15744
16979
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15870,6 +17105,13 @@ async function main(argv) {
|
|
|
15870
17105
|
logger
|
|
15871
17106
|
});
|
|
15872
17107
|
if (fallbackExtension) agent.extensions.register(fallbackExtension);
|
|
17108
|
+
if (config.features.memory && config.features.memoryConsolidation !== false) {
|
|
17109
|
+
agent.extensions.register(
|
|
17110
|
+
new SessionMemoryConsolidator({
|
|
17111
|
+
memoryStore
|
|
17112
|
+
})
|
|
17113
|
+
);
|
|
17114
|
+
}
|
|
15873
17115
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
15874
17116
|
try {
|
|
15875
17117
|
context.provider = buildProviderForId(providerId);
|
|
@@ -15916,10 +17158,10 @@ async function main(argv) {
|
|
|
15916
17158
|
}
|
|
15917
17159
|
};
|
|
15918
17160
|
const fleetRoot = directorMode ? path8.join(wpaths.projectSessions, session.id) : void 0;
|
|
15919
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path8.join(
|
|
15920
|
-
const sharedScratchpadPath = directorMode ? path8.join(
|
|
15921
|
-
const subagentSessionsRoot = directorMode ? path8.join(
|
|
15922
|
-
const stateCheckpointPath = directorMode ? path8.join(
|
|
17161
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path8.join(expectDefined(fleetRoot), "fleet.json") : void 0;
|
|
17162
|
+
const sharedScratchpadPath = directorMode ? path8.join(expectDefined(fleetRoot), "shared") : void 0;
|
|
17163
|
+
const subagentSessionsRoot = directorMode ? path8.join(expectDefined(fleetRoot), "subagents") : void 0;
|
|
17164
|
+
const stateCheckpointPath = directorMode ? path8.join(expectDefined(fleetRoot), "director-state.json") : void 0;
|
|
15923
17165
|
const fleetRootForPromotion = path8.join(wpaths.projectSessions, session.id);
|
|
15924
17166
|
const brainQueue = new BrainDecisionQueue(events);
|
|
15925
17167
|
const brain = new ObservableBrainArbiter(
|
|
@@ -16338,7 +17580,7 @@ async function main(argv) {
|
|
|
16338
17580
|
...matches.map((m) => ` ${m.subagentId} (${m.runId})`)
|
|
16339
17581
|
].join("\n");
|
|
16340
17582
|
}
|
|
16341
|
-
const t =
|
|
17583
|
+
const t = expectDefined(matches[0]);
|
|
16342
17584
|
const raw = await fsp4.readFile(t.file, "utf8");
|
|
16343
17585
|
if (mode === "raw") return raw;
|
|
16344
17586
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
@@ -16588,6 +17830,8 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
16588
17830
|
parallelEngine?.stop();
|
|
16589
17831
|
},
|
|
16590
17832
|
onExit: () => {
|
|
17833
|
+
for (const teardown of teardownHandlers) teardown();
|
|
17834
|
+
teardownHandlers.length = 0;
|
|
16591
17835
|
brainQueue.dispose();
|
|
16592
17836
|
void mcpRegistry.stopAll();
|
|
16593
17837
|
},
|
|
@@ -16597,7 +17841,8 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
16597
17841
|
(resolve5, reject) => {
|
|
16598
17842
|
const child = spawn("git", ["status", "--porcelain"], {
|
|
16599
17843
|
cwd: cwd2,
|
|
16600
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
17844
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
17845
|
+
signal: AbortSignal.timeout(5e3)
|
|
16601
17846
|
});
|
|
16602
17847
|
let stdout = "";
|
|
16603
17848
|
child.stdout?.on("data", (d) => {
|