@wrongstack/cli 0.5.7 → 0.6.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 +954 -312
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
2
|
+
import * as path23 from 'path';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import * as fsp2 from 'fs/promises';
|
|
5
5
|
import { readdir, readFile } from 'fs/promises';
|
|
6
|
-
import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus,
|
|
6
|
+
import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, loadGoal, goalFilePath, summarizeUsage, emptyGoal, saveGoal, formatGoal, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1 } from '@wrongstack/core';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os6 from 'os';
|
|
9
9
|
import os6__default from 'os';
|
|
@@ -11,15 +11,17 @@ import * as crypto from 'crypto';
|
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
13
13
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
14
|
-
import { writeFileSync } from 'fs';
|
|
15
14
|
import { MCPRegistry } from '@wrongstack/mcp';
|
|
16
15
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
17
16
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
18
17
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
19
18
|
import * as readline from 'readline';
|
|
20
19
|
import { spawn } from 'child_process';
|
|
20
|
+
import { buildGoalPreamble } from '@wrongstack/tui';
|
|
21
21
|
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
22
22
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
23
|
+
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
24
|
+
import { writeFileSync } from 'fs';
|
|
23
25
|
|
|
24
26
|
var __defProp = Object.defineProperty;
|
|
25
27
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -261,8 +263,8 @@ function buildSddCommand(opts) {
|
|
|
261
263
|
async run(args) {
|
|
262
264
|
const ctx = opts.context;
|
|
263
265
|
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
264
|
-
const specsDir =
|
|
265
|
-
const graphsDir =
|
|
266
|
+
const specsDir = path23.join(projectRoot, ".wrongstack", "specs");
|
|
267
|
+
const graphsDir = path23.join(projectRoot, ".wrongstack", "task-graphs");
|
|
266
268
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
267
269
|
new TaskGraphStore({ baseDir: graphsDir });
|
|
268
270
|
const versioning = new SpecVersioning();
|
|
@@ -278,7 +280,7 @@ function buildSddCommand(opts) {
|
|
|
278
280
|
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
279
281
|
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
280
282
|
if (!sddState.getBuilder() && !forceFlag) {
|
|
281
|
-
const sessionPath =
|
|
283
|
+
const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
282
284
|
try {
|
|
283
285
|
await fsp2.access(sessionPath);
|
|
284
286
|
const projectContext2 = await gatherProjectContext(projectRoot);
|
|
@@ -313,7 +315,7 @@ function buildSddCommand(opts) {
|
|
|
313
315
|
projectContext,
|
|
314
316
|
minQuestions: 2,
|
|
315
317
|
maxQuestions: 10,
|
|
316
|
-
sessionPath:
|
|
318
|
+
sessionPath: path23.join(projectRoot, ".wrongstack", "sdd-session.json")
|
|
317
319
|
}));
|
|
318
320
|
const builder = sddState.getBuilder();
|
|
319
321
|
builder.startSession(title);
|
|
@@ -581,7 +583,7 @@ Start executing the tasks one by one.`
|
|
|
581
583
|
};
|
|
582
584
|
}
|
|
583
585
|
case "cancel": {
|
|
584
|
-
const sessionPath =
|
|
586
|
+
const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
585
587
|
let deletedFromDisk = false;
|
|
586
588
|
try {
|
|
587
589
|
await fsp2.unlink(sessionPath);
|
|
@@ -605,7 +607,7 @@ Start executing the tasks one by one.`
|
|
|
605
607
|
if (sddState.getBuilder()) {
|
|
606
608
|
return { message: "An SDD session is already active. Use /sdd cancel first." };
|
|
607
609
|
}
|
|
608
|
-
const sessionPath =
|
|
610
|
+
const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
609
611
|
const projectContext = await gatherProjectContext(projectRoot);
|
|
610
612
|
sddState.setBuilder(new AISpecBuilder({
|
|
611
613
|
store: specStore,
|
|
@@ -831,7 +833,7 @@ function sddHelp() {
|
|
|
831
833
|
async function gatherProjectContext(projectRoot) {
|
|
832
834
|
const parts = [];
|
|
833
835
|
try {
|
|
834
|
-
const pkgPath =
|
|
836
|
+
const pkgPath = path23.join(projectRoot, "package.json");
|
|
835
837
|
const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
|
|
836
838
|
const pkg = JSON.parse(pkgRaw);
|
|
837
839
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -847,13 +849,13 @@ async function gatherProjectContext(projectRoot) {
|
|
|
847
849
|
} catch {
|
|
848
850
|
}
|
|
849
851
|
try {
|
|
850
|
-
const tsconfigPath =
|
|
852
|
+
const tsconfigPath = path23.join(projectRoot, "tsconfig.json");
|
|
851
853
|
await fsp2.access(tsconfigPath);
|
|
852
854
|
parts.push("Language: TypeScript");
|
|
853
855
|
} catch {
|
|
854
856
|
}
|
|
855
857
|
try {
|
|
856
|
-
const srcDir =
|
|
858
|
+
const srcDir = path23.join(projectRoot, "src");
|
|
857
859
|
const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
|
|
858
860
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
859
861
|
if (dirs.length > 0) {
|
|
@@ -976,7 +978,7 @@ __export(update_check_exports, {
|
|
|
976
978
|
getUpdateNotification: () => getUpdateNotification
|
|
977
979
|
});
|
|
978
980
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
979
|
-
return
|
|
981
|
+
return path23.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
980
982
|
}
|
|
981
983
|
function currentVersion() {
|
|
982
984
|
const req2 = createRequire(import.meta.url);
|
|
@@ -1013,7 +1015,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1013
1015
|
}
|
|
1014
1016
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1015
1017
|
try {
|
|
1016
|
-
const dir =
|
|
1018
|
+
const dir = path23.dirname(cachePath(homeFn));
|
|
1017
1019
|
await fsp2.mkdir(dir, { recursive: true });
|
|
1018
1020
|
await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1019
1021
|
} catch {
|
|
@@ -1196,6 +1198,25 @@ async function runWebUI(opts) {
|
|
|
1196
1198
|
});
|
|
1197
1199
|
})
|
|
1198
1200
|
);
|
|
1201
|
+
if (opts.subscribeEternalIteration) {
|
|
1202
|
+
eventUnsubscribers.push(
|
|
1203
|
+
opts.subscribeEternalIteration((entry) => {
|
|
1204
|
+
broadcast({
|
|
1205
|
+
type: "eternal.iteration",
|
|
1206
|
+
payload: {
|
|
1207
|
+
iteration: entry.iteration,
|
|
1208
|
+
at: entry.at,
|
|
1209
|
+
source: entry.source,
|
|
1210
|
+
task: entry.task,
|
|
1211
|
+
status: entry.status,
|
|
1212
|
+
note: entry.note,
|
|
1213
|
+
tokens: entry.tokens,
|
|
1214
|
+
costUsd: entry.costUsd
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1199
1220
|
}
|
|
1200
1221
|
return new Promise((resolve4) => {
|
|
1201
1222
|
wss.on("listening", () => {
|
|
@@ -1584,26 +1605,40 @@ async function runWebUI(opts) {
|
|
|
1584
1605
|
return {};
|
|
1585
1606
|
}
|
|
1586
1607
|
if (!parsed.providers) return {};
|
|
1587
|
-
const keyFile =
|
|
1608
|
+
const keyFile = path23.join(path23.dirname(opts.globalConfigPath), ".key");
|
|
1588
1609
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1589
1610
|
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
1590
1611
|
}
|
|
1591
1612
|
async function saveProviders(providers) {
|
|
1592
1613
|
if (!opts.globalConfigPath) return;
|
|
1593
1614
|
let raw;
|
|
1615
|
+
let fileExists = true;
|
|
1594
1616
|
try {
|
|
1595
1617
|
raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
|
|
1596
|
-
} catch {
|
|
1618
|
+
} catch (err) {
|
|
1619
|
+
if (err.code !== "ENOENT") {
|
|
1620
|
+
throw new Error(
|
|
1621
|
+
`Refusing to mutate ${opts.globalConfigPath}: ${err.message}`,
|
|
1622
|
+
{ cause: err }
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
fileExists = false;
|
|
1597
1626
|
raw = "{}";
|
|
1598
1627
|
}
|
|
1599
1628
|
let parsed;
|
|
1600
1629
|
try {
|
|
1601
1630
|
parsed = JSON.parse(raw);
|
|
1602
|
-
} catch {
|
|
1631
|
+
} catch (err) {
|
|
1632
|
+
if (fileExists) {
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`Refusing to overwrite corrupt config at ${opts.globalConfigPath} (${err.message}). Fix or move the file aside before retrying.`,
|
|
1635
|
+
{ cause: err }
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1603
1638
|
parsed = {};
|
|
1604
1639
|
}
|
|
1605
1640
|
parsed.providers = providers;
|
|
1606
|
-
const keyFile =
|
|
1641
|
+
const keyFile = path23.join(path23.dirname(opts.globalConfigPath), ".key");
|
|
1607
1642
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1608
1643
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
1609
1644
|
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
@@ -1618,19 +1653,6 @@ var init_webui_server = __esm({
|
|
|
1618
1653
|
}
|
|
1619
1654
|
});
|
|
1620
1655
|
|
|
1621
|
-
// src/plugin-api-factory.ts
|
|
1622
|
-
var plugin_api_factory_exports = {};
|
|
1623
|
-
__export(plugin_api_factory_exports, {
|
|
1624
|
-
default: () => createApi
|
|
1625
|
-
});
|
|
1626
|
-
function createApi(ownerName, base) {
|
|
1627
|
-
return new DefaultPluginAPI({ ownerName, ...base });
|
|
1628
|
-
}
|
|
1629
|
-
var init_plugin_api_factory = __esm({
|
|
1630
|
-
"src/plugin-api-factory.ts"() {
|
|
1631
|
-
}
|
|
1632
|
-
});
|
|
1633
|
-
|
|
1634
1656
|
// src/slash-commands/commit-llm.ts
|
|
1635
1657
|
async function generateCommitMessageWithLLM(diff, opts) {
|
|
1636
1658
|
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";
|
|
@@ -1788,7 +1810,7 @@ function parseSpawnFlags(input) {
|
|
|
1788
1810
|
return { description: rest.trim(), opts };
|
|
1789
1811
|
}
|
|
1790
1812
|
async function bootConfig(flags) {
|
|
1791
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
1813
|
+
const cwd = typeof flags["cwd"] === "string" ? path23.resolve(flags["cwd"]) : process.cwd();
|
|
1792
1814
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
1793
1815
|
const projectRoot = pathResolver.projectRoot;
|
|
1794
1816
|
const userHome = os6.homedir();
|
|
@@ -1855,7 +1877,7 @@ var ReadlineInputReader = class {
|
|
|
1855
1877
|
history = [];
|
|
1856
1878
|
pending = false;
|
|
1857
1879
|
constructor(opts = {}) {
|
|
1858
|
-
this.historyFile = opts.historyFile ??
|
|
1880
|
+
this.historyFile = opts.historyFile ?? path23.join(os6.homedir(), ".wrongstack", "history");
|
|
1859
1881
|
}
|
|
1860
1882
|
async loadHistory() {
|
|
1861
1883
|
try {
|
|
@@ -1867,7 +1889,7 @@ var ReadlineInputReader = class {
|
|
|
1867
1889
|
}
|
|
1868
1890
|
async saveHistory() {
|
|
1869
1891
|
try {
|
|
1870
|
-
await fsp2.mkdir(
|
|
1892
|
+
await fsp2.mkdir(path23.dirname(this.historyFile), { recursive: true });
|
|
1871
1893
|
await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
1872
1894
|
} catch {
|
|
1873
1895
|
}
|
|
@@ -2094,20 +2116,20 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
2094
2116
|
if (PROTECTED_BASENAMES.has(filename)) {
|
|
2095
2117
|
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
2096
2118
|
}
|
|
2097
|
-
if (filename !==
|
|
2119
|
+
if (filename !== path23.basename(filename)) {
|
|
2098
2120
|
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
2099
2121
|
}
|
|
2100
2122
|
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
2101
2123
|
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
2102
2124
|
}
|
|
2103
|
-
const resolvedParent =
|
|
2125
|
+
const resolvedParent = path23.resolve(parentDir);
|
|
2104
2126
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
2105
2127
|
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
2106
2128
|
}
|
|
2107
2129
|
}
|
|
2108
2130
|
async function safeDelete(filePath) {
|
|
2109
|
-
const dir =
|
|
2110
|
-
const filename =
|
|
2131
|
+
const dir = path23.dirname(filePath);
|
|
2132
|
+
const filename = path23.basename(filePath);
|
|
2111
2133
|
try {
|
|
2112
2134
|
assertSafeToDelete(filename, dir);
|
|
2113
2135
|
await fsp2.unlink(filePath);
|
|
@@ -2152,16 +2174,16 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
2152
2174
|
}
|
|
2153
2175
|
var defaultHomeDir = () => os6__default.homedir();
|
|
2154
2176
|
function historyDir(homeFn = defaultHomeDir) {
|
|
2155
|
-
return
|
|
2177
|
+
return path23.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
2156
2178
|
}
|
|
2157
2179
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
2158
|
-
return
|
|
2180
|
+
return path23.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
2159
2181
|
}
|
|
2160
2182
|
function configPath(homeFn = defaultHomeDir) {
|
|
2161
|
-
return
|
|
2183
|
+
return path23.join(homeFn(), ".wrongstack", "config.json");
|
|
2162
2184
|
}
|
|
2163
2185
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
2164
|
-
return
|
|
2186
|
+
return path23.join(homeFn(), ".wrongstack", "config.json.last");
|
|
2165
2187
|
}
|
|
2166
2188
|
function entryId(ts) {
|
|
2167
2189
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -2179,7 +2201,7 @@ async function readIndex(homeFn = defaultHomeDir) {
|
|
|
2179
2201
|
}
|
|
2180
2202
|
async function writeIndex(idx, homeFn = defaultHomeDir) {
|
|
2181
2203
|
await ensureHistoryDir(homeFn);
|
|
2182
|
-
await
|
|
2204
|
+
await atomicWrite(historyIndexPath(homeFn), JSON.stringify(idx, null, 2));
|
|
2183
2205
|
}
|
|
2184
2206
|
async function backupCurrent(homeFn = defaultHomeDir) {
|
|
2185
2207
|
const cfg = configPath(homeFn);
|
|
@@ -2198,17 +2220,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
2198
2220
|
}
|
|
2199
2221
|
if (content !== void 0) {
|
|
2200
2222
|
try {
|
|
2201
|
-
const bakPath =
|
|
2223
|
+
const bakPath = path23.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
2202
2224
|
await atomicWrite(bakPath, content);
|
|
2203
2225
|
} catch {
|
|
2204
2226
|
}
|
|
2205
2227
|
}
|
|
2206
2228
|
try {
|
|
2207
|
-
const dir =
|
|
2229
|
+
const dir = path23.join(homeFn(), ".wrongstack");
|
|
2208
2230
|
const files = await fsp2.readdir(dir);
|
|
2209
2231
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
2210
2232
|
for (const f of baks.slice(10)) {
|
|
2211
|
-
await safeDelete(
|
|
2233
|
+
await safeDelete(path23.join(dir, f));
|
|
2212
2234
|
}
|
|
2213
2235
|
} catch {
|
|
2214
2236
|
}
|
|
@@ -2225,7 +2247,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
2225
2247
|
diffSummary: diffSummary(oldCfg, newCfg)
|
|
2226
2248
|
};
|
|
2227
2249
|
await fsp2.writeFile(
|
|
2228
|
-
|
|
2250
|
+
path23.join(historyDir(homeFn), `${id}.json`),
|
|
2229
2251
|
JSON.stringify(entry, null, 2),
|
|
2230
2252
|
"utf8"
|
|
2231
2253
|
);
|
|
@@ -2240,7 +2262,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
2240
2262
|
}
|
|
2241
2263
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
2242
2264
|
try {
|
|
2243
|
-
const raw = await fsp2.readFile(
|
|
2265
|
+
const raw = await fsp2.readFile(path23.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
2244
2266
|
return JSON.parse(raw);
|
|
2245
2267
|
} catch {
|
|
2246
2268
|
return null;
|
|
@@ -2299,11 +2321,11 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
2299
2321
|
var theme = { primary: color.amber };
|
|
2300
2322
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
|
|
2301
2323
|
try {
|
|
2302
|
-
const { atomicWrite:
|
|
2303
|
-
const
|
|
2324
|
+
const { atomicWrite: atomicWrite8 } = await import('@wrongstack/core');
|
|
2325
|
+
const fs19 = await import('fs/promises');
|
|
2304
2326
|
let existing = {};
|
|
2305
2327
|
try {
|
|
2306
|
-
const raw = await
|
|
2328
|
+
const raw = await fs19.readFile(configPath2, "utf8");
|
|
2307
2329
|
existing = JSON.parse(raw);
|
|
2308
2330
|
} catch {
|
|
2309
2331
|
}
|
|
@@ -2311,7 +2333,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
|
|
|
2311
2333
|
existing.provider = provider;
|
|
2312
2334
|
existing.model = model;
|
|
2313
2335
|
await backupCurrent(homeFn);
|
|
2314
|
-
await
|
|
2336
|
+
await atomicWrite8(configPath2, JSON.stringify(existing, null, 2));
|
|
2315
2337
|
try {
|
|
2316
2338
|
await appendHistory(
|
|
2317
2339
|
oldCfg,
|
|
@@ -2440,8 +2462,12 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
|
|
|
2440
2462
|
const defaultHint = defaultIdx !== void 0 && defaultProvider ? ` ${color.dim(`[Enter = ${defaultProvider}]`)}` : "";
|
|
2441
2463
|
const providerAnswer = (await reader.readLine(
|
|
2442
2464
|
`
|
|
2443
|
-
${color.amber("?")} Select provider (1-${ordered.length})${defaultHint}: `
|
|
2465
|
+
${color.amber("?")} Select provider (1-${ordered.length})${defaultHint} ${color.dim("[q to quit]")}: `
|
|
2444
2466
|
)).trim();
|
|
2467
|
+
if (providerAnswer.toLowerCase() === "q") {
|
|
2468
|
+
renderer.write(color.dim("Cancelled.\n"));
|
|
2469
|
+
return void 0;
|
|
2470
|
+
}
|
|
2445
2471
|
if (!providerAnswer) {
|
|
2446
2472
|
if (defaultIdx !== void 0) {
|
|
2447
2473
|
const def = ordered[defaultIdx - 1];
|
|
@@ -2503,15 +2529,23 @@ async function pickModel(provider, registry, renderer, reader, defaultModel) {
|
|
|
2503
2529
|
if (offset < models.length) {
|
|
2504
2530
|
const more = (await reader.readLine(
|
|
2505
2531
|
`
|
|
2506
|
-
${color.amber("?")} Showing ${Math.min(offset, models.length)}/${models.length} \u2014 Enter number
|
|
2532
|
+
${color.amber("?")} Showing ${Math.min(offset, models.length)}/${models.length} \u2014 Enter number, ${color.dim("Enter")} for more, or ${color.dim("q")} to quit: `
|
|
2507
2533
|
)).trim();
|
|
2534
|
+
if (more.toLowerCase() === "q") {
|
|
2535
|
+
renderer.write(color.dim("Cancelled.\n"));
|
|
2536
|
+
return void 0;
|
|
2537
|
+
}
|
|
2508
2538
|
if (!more) continue;
|
|
2509
2539
|
return resolveModelSelection(more, models, provider, registry, renderer);
|
|
2510
2540
|
}
|
|
2511
2541
|
}
|
|
2512
2542
|
const defaultHint = defaultIdxInModels >= 0 && defaultModel ? ` ${color.dim(`[Enter = ${defaultModel}]`)}` : "";
|
|
2513
2543
|
const answer = (await reader.readLine(`
|
|
2514
|
-
${color.amber("?")} Select model (1-${models.length})${defaultHint}: `)).trim();
|
|
2544
|
+
${color.amber("?")} Select model (1-${models.length})${defaultHint} ${color.dim("[q to quit]")}: `)).trim();
|
|
2545
|
+
if (answer.toLowerCase() === "q") {
|
|
2546
|
+
renderer.write(color.dim("Cancelled.\n"));
|
|
2547
|
+
return void 0;
|
|
2548
|
+
}
|
|
2515
2549
|
if (!answer) {
|
|
2516
2550
|
if (defaultIdxInModels >= 0 && defaultModel) {
|
|
2517
2551
|
renderer.write(
|
|
@@ -2569,10 +2603,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2569
2603
|
const name = declared.split("@")[0];
|
|
2570
2604
|
if (name) return name;
|
|
2571
2605
|
}
|
|
2572
|
-
if (await pathExists(
|
|
2573
|
-
if (await pathExists(
|
|
2574
|
-
if (await pathExists(
|
|
2575
|
-
if (await pathExists(
|
|
2606
|
+
if (await pathExists(path23.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2607
|
+
if (await pathExists(path23.join(root, "bun.lockb"))) return "bun";
|
|
2608
|
+
if (await pathExists(path23.join(root, "bun.lock"))) return "bun";
|
|
2609
|
+
if (await pathExists(path23.join(root, "yarn.lock"))) return "yarn";
|
|
2576
2610
|
return "npm";
|
|
2577
2611
|
}
|
|
2578
2612
|
function hasUsableScript(scripts, name) {
|
|
@@ -2593,7 +2627,7 @@ function parseMakeTargets(makefile) {
|
|
|
2593
2627
|
async function detectProjectFacts(root) {
|
|
2594
2628
|
const facts = { hints: [] };
|
|
2595
2629
|
try {
|
|
2596
|
-
const pkg = JSON.parse(await fsp2.readFile(
|
|
2630
|
+
const pkg = JSON.parse(await fsp2.readFile(path23.join(root, "package.json"), "utf8"));
|
|
2597
2631
|
const scripts = pkg.scripts ?? {};
|
|
2598
2632
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2599
2633
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2607,14 +2641,14 @@ async function detectProjectFacts(root) {
|
|
|
2607
2641
|
} catch {
|
|
2608
2642
|
}
|
|
2609
2643
|
try {
|
|
2610
|
-
if (!await pathExists(
|
|
2644
|
+
if (!await pathExists(path23.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2611
2645
|
facts.test ??= "pytest";
|
|
2612
2646
|
facts.lint ??= "ruff check .";
|
|
2613
2647
|
facts.hints.push("pyproject.toml");
|
|
2614
2648
|
} catch {
|
|
2615
2649
|
}
|
|
2616
2650
|
try {
|
|
2617
|
-
if (!await pathExists(
|
|
2651
|
+
if (!await pathExists(path23.join(root, "go.mod"))) throw new Error("not go");
|
|
2618
2652
|
facts.build ??= "go build ./...";
|
|
2619
2653
|
facts.test ??= "go test ./...";
|
|
2620
2654
|
facts.run ??= "go run .";
|
|
@@ -2622,7 +2656,7 @@ async function detectProjectFacts(root) {
|
|
|
2622
2656
|
} catch {
|
|
2623
2657
|
}
|
|
2624
2658
|
try {
|
|
2625
|
-
if (!await pathExists(
|
|
2659
|
+
if (!await pathExists(path23.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2626
2660
|
facts.build ??= "cargo build";
|
|
2627
2661
|
facts.test ??= "cargo test";
|
|
2628
2662
|
facts.lint ??= "cargo clippy";
|
|
@@ -2631,7 +2665,7 @@ async function detectProjectFacts(root) {
|
|
|
2631
2665
|
} catch {
|
|
2632
2666
|
}
|
|
2633
2667
|
try {
|
|
2634
|
-
const makefile = await fsp2.readFile(
|
|
2668
|
+
const makefile = await fsp2.readFile(path23.join(root, "Makefile"), "utf8");
|
|
2635
2669
|
const targets = parseMakeTargets(makefile);
|
|
2636
2670
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2637
2671
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -3267,8 +3301,8 @@ function buildInitCommand(opts) {
|
|
|
3267
3301
|
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3268
3302
|
async run(args, ctx) {
|
|
3269
3303
|
const force = args.trim() === "--force";
|
|
3270
|
-
const dir =
|
|
3271
|
-
const file =
|
|
3304
|
+
const dir = path23.join(ctx.projectRoot, ".wrongstack");
|
|
3305
|
+
const file = path23.join(dir, "AGENTS.md");
|
|
3272
3306
|
try {
|
|
3273
3307
|
await fsp2.access(file);
|
|
3274
3308
|
if (!force) {
|
|
@@ -3769,21 +3803,24 @@ function buildAutonomyCommand(opts) {
|
|
|
3769
3803
|
description: "Toggle or query autonomy mode (self-driving agent).",
|
|
3770
3804
|
help: [
|
|
3771
3805
|
"Usage:",
|
|
3772
|
-
" /autonomy
|
|
3773
|
-
" /autonomy off
|
|
3774
|
-
" /autonomy suggest
|
|
3775
|
-
" /autonomy on
|
|
3776
|
-
" /autonomy
|
|
3806
|
+
" /autonomy Show current autonomy status",
|
|
3807
|
+
" /autonomy off Disabled \u2014 agent stops after each turn (default)",
|
|
3808
|
+
" /autonomy suggest Show next-step suggestions after each turn",
|
|
3809
|
+
" /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
|
|
3810
|
+
" /autonomy eternal Sittin-sene mode \u2014 runs forever against /goal",
|
|
3811
|
+
" /autonomy stop Stop eternal mode (no-op for other modes)",
|
|
3812
|
+
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 eternal \u2192 off",
|
|
3777
3813
|
"",
|
|
3778
3814
|
"Modes:",
|
|
3779
3815
|
" off \u2014 Normal interactive mode. Agent stops and waits.",
|
|
3780
3816
|
" suggest \u2014 After each turn, agent suggests next steps. You pick.",
|
|
3781
3817
|
" auto \u2014 After each turn, agent picks the best next step and continues.",
|
|
3782
3818
|
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
3819
|
+
" eternal \u2014 Goal-driven sense/decide/execute/reflect loop. Requires /goal.",
|
|
3820
|
+
" Force-enables YOLO. Runs until /autonomy stop or Ctrl+C twice.",
|
|
3783
3821
|
"",
|
|
3784
|
-
"In auto
|
|
3785
|
-
"Ctrl+C to stop
|
|
3786
|
-
"the conversation history."
|
|
3822
|
+
"In auto/eternal modes the agent works autonomously. Press Esc to redirect,",
|
|
3823
|
+
"Ctrl+C to stop the active iteration. /autonomy stop ends the eternal loop."
|
|
3787
3824
|
].join("\n"),
|
|
3788
3825
|
async run(args) {
|
|
3789
3826
|
const arg = args.trim().toLowerCase();
|
|
@@ -3792,14 +3829,64 @@ function buildAutonomyCommand(opts) {
|
|
|
3792
3829
|
opts.renderer.writeWarning(msg2);
|
|
3793
3830
|
return { message: msg2 };
|
|
3794
3831
|
}
|
|
3795
|
-
if (!arg) {
|
|
3832
|
+
if (!arg || arg === "status") {
|
|
3796
3833
|
const current = opts.onAutonomy();
|
|
3797
3834
|
const labels2 = {
|
|
3798
3835
|
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
3799
3836
|
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
3800
|
-
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}
|
|
3837
|
+
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
|
|
3838
|
+
eternal: `${color.red("ETERNAL")} ${color.dim("(sittin-sene \u2014 goal-driven, YOLO, until /autonomy stop)")}`
|
|
3801
3839
|
};
|
|
3802
|
-
const
|
|
3840
|
+
const lines = [`Autonomy mode: ${labels2[current]}`];
|
|
3841
|
+
try {
|
|
3842
|
+
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3843
|
+
if (goal) {
|
|
3844
|
+
const u = summarizeUsage(goal);
|
|
3845
|
+
lines.push(color.dim(` Goal: ${goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal}`));
|
|
3846
|
+
lines.push(color.dim(` Engine state: ${goal.engineState} \xB7 iterations: ${goal.iterations} \xB7 journal: ${goal.journal.length}`));
|
|
3847
|
+
if (u.iterationsWithUsage > 0) {
|
|
3848
|
+
lines.push(
|
|
3849
|
+
color.dim(
|
|
3850
|
+
` Spent: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens`
|
|
3851
|
+
)
|
|
3852
|
+
);
|
|
3853
|
+
}
|
|
3854
|
+
const recent = goal.journal.slice(-10);
|
|
3855
|
+
const failed = recent.filter((e) => e.status === "failure").length;
|
|
3856
|
+
if (failed > 0) {
|
|
3857
|
+
lines.push(color.amber(` Recent failures: ${failed} of last ${recent.length} iterations`));
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
} catch {
|
|
3861
|
+
}
|
|
3862
|
+
const msg2 = lines.join("\n");
|
|
3863
|
+
opts.renderer.write(msg2);
|
|
3864
|
+
return { message: msg2 };
|
|
3865
|
+
}
|
|
3866
|
+
if (arg === "stop" || arg === "halt" || arg === "kill") {
|
|
3867
|
+
if (!opts.onEternalStop) {
|
|
3868
|
+
const msg3 = "No eternal-mode controller wired in this session.";
|
|
3869
|
+
opts.renderer.writeWarning(msg3);
|
|
3870
|
+
return { message: msg3 };
|
|
3871
|
+
}
|
|
3872
|
+
opts.onEternalStop();
|
|
3873
|
+
opts.onAutonomy("off");
|
|
3874
|
+
let summaryLine = "";
|
|
3875
|
+
try {
|
|
3876
|
+
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3877
|
+
if (goal) {
|
|
3878
|
+
const u = summarizeUsage(goal);
|
|
3879
|
+
if (u.iterationsWithUsage > 0) {
|
|
3880
|
+
summaryLine = "\n" + color.dim(
|
|
3881
|
+
` Spent so far: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens \xB7 ${goal.iterations} total iterations.`
|
|
3882
|
+
);
|
|
3883
|
+
} else if (goal.iterations > 0) {
|
|
3884
|
+
summaryLine = "\n" + color.dim(` Total iterations: ${goal.iterations}.`);
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
} catch {
|
|
3888
|
+
}
|
|
3889
|
+
const msg2 = `${color.amber("Eternal mode stop requested.")} The current iteration will finish, then the loop exits.${summaryLine}`;
|
|
3803
3890
|
opts.renderer.write(msg2);
|
|
3804
3891
|
return { message: msg2 };
|
|
3805
3892
|
}
|
|
@@ -3810,20 +3897,47 @@ function buildAutonomyCommand(opts) {
|
|
|
3810
3897
|
newMode = "off";
|
|
3811
3898
|
} else if (arg === "suggest" || arg === "suggestions") {
|
|
3812
3899
|
newMode = "suggest";
|
|
3900
|
+
} else if (arg === "eternal" || arg === "forever" || arg === "infinite" || arg === "sittinsene") {
|
|
3901
|
+
newMode = "eternal";
|
|
3813
3902
|
} else if (arg === "toggle" || arg === "cycle") {
|
|
3814
3903
|
const current = opts.onAutonomy() ?? "off";
|
|
3815
|
-
const cycle = ["off", "suggest", "auto"];
|
|
3904
|
+
const cycle = ["off", "suggest", "auto", "eternal"];
|
|
3816
3905
|
newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
|
|
3817
3906
|
} else {
|
|
3818
|
-
const msg2 = `Unknown argument: ${arg}. Use /autonomy on,
|
|
3907
|
+
const msg2 = `Unknown argument: ${arg}. Use /autonomy on, off, suggest, eternal, stop, or toggle.`;
|
|
3819
3908
|
opts.renderer.writeWarning(msg2);
|
|
3820
3909
|
return { message: msg2 };
|
|
3821
3910
|
}
|
|
3911
|
+
if (newMode === "eternal") {
|
|
3912
|
+
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3913
|
+
if (!goal) {
|
|
3914
|
+
const msg3 = `${color.red("Eternal mode requires a goal.")} Run \`/goal set <mission>\` first.`;
|
|
3915
|
+
opts.renderer.writeWarning(msg3);
|
|
3916
|
+
return { message: msg3 };
|
|
3917
|
+
}
|
|
3918
|
+
if (!opts.onEternalStart) {
|
|
3919
|
+
const msg3 = "Eternal mode controller is not wired in this session.";
|
|
3920
|
+
opts.renderer.writeWarning(msg3);
|
|
3921
|
+
return { message: msg3 };
|
|
3922
|
+
}
|
|
3923
|
+
if (opts.onYolo) opts.onYolo(true);
|
|
3924
|
+
opts.onAutonomy(newMode);
|
|
3925
|
+
opts.onEternalStart();
|
|
3926
|
+
const msg2 = `${color.red("Autonomy mode: ETERNAL")} \u2014 engine launching against goal: ${color.bold(goal.goal)}
|
|
3927
|
+
${color.dim("YOLO forced ON. Use /autonomy stop to end. Journal at /goal journal.")}`;
|
|
3928
|
+
opts.renderer.write(msg2);
|
|
3929
|
+
return { message: msg2 };
|
|
3930
|
+
}
|
|
3931
|
+
const previous = opts.onAutonomy();
|
|
3932
|
+
if (previous === "eternal" && opts.onEternalStop) {
|
|
3933
|
+
opts.onEternalStop();
|
|
3934
|
+
}
|
|
3822
3935
|
opts.onAutonomy(newMode);
|
|
3823
3936
|
const labels = {
|
|
3824
3937
|
off: `${color.green("OFF")} \u2014 agent stops after each turn`,
|
|
3825
3938
|
suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
|
|
3826
|
-
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically
|
|
3939
|
+
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
|
|
3940
|
+
eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`
|
|
3827
3941
|
};
|
|
3828
3942
|
const msg = `Autonomy mode: ${labels[newMode]}`;
|
|
3829
3943
|
opts.renderer.write(msg);
|
|
@@ -3831,6 +3945,124 @@ function buildAutonomyCommand(opts) {
|
|
|
3831
3945
|
}
|
|
3832
3946
|
};
|
|
3833
3947
|
}
|
|
3948
|
+
var KNOWN_VERBS = /* @__PURE__ */ new Set([
|
|
3949
|
+
"",
|
|
3950
|
+
"show",
|
|
3951
|
+
"status",
|
|
3952
|
+
"set",
|
|
3953
|
+
"new",
|
|
3954
|
+
"clear",
|
|
3955
|
+
"reset",
|
|
3956
|
+
"journal",
|
|
3957
|
+
"log"
|
|
3958
|
+
]);
|
|
3959
|
+
function buildGoalCommand(opts) {
|
|
3960
|
+
return {
|
|
3961
|
+
name: "goal",
|
|
3962
|
+
description: "Set, inspect, or clear the long-running autonomous mission used by /autonomy eternal.",
|
|
3963
|
+
help: [
|
|
3964
|
+
"Usage:",
|
|
3965
|
+
" /goal Show current goal + recent journal",
|
|
3966
|
+
" /goal set <text> Set a new goal (overwrites previous)",
|
|
3967
|
+
" /goal clear Clear the goal (stops eternal mode if running)",
|
|
3968
|
+
" /goal status Same as /goal (alias)",
|
|
3969
|
+
" /goal journal [N] Show last N journal entries (default 25)",
|
|
3970
|
+
"",
|
|
3971
|
+
"Goals live in <projectRoot>/.wrongstack/goal.json and persist across sessions.",
|
|
3972
|
+
"A goal is the prerequisite for /autonomy eternal \u2014 the engine consults it on",
|
|
3973
|
+
"every iteration to decide what to do next."
|
|
3974
|
+
].join("\n"),
|
|
3975
|
+
async run(args) {
|
|
3976
|
+
const trimmed = args.trim();
|
|
3977
|
+
const [verbRaw, ...rest] = trimmed.split(/\s+/);
|
|
3978
|
+
const verb = (verbRaw ?? "").toLowerCase();
|
|
3979
|
+
const restJoined = rest.join(" ").trim();
|
|
3980
|
+
const goalPath = goalFilePath(opts.projectRoot);
|
|
3981
|
+
const verbForDispatch = verb && !KNOWN_VERBS.has(verb) ? "set" : verb;
|
|
3982
|
+
const setText = verbForDispatch === "set" && !KNOWN_VERBS.has(verb) ? trimmed : restJoined;
|
|
3983
|
+
switch (verbForDispatch) {
|
|
3984
|
+
case "":
|
|
3985
|
+
case "show":
|
|
3986
|
+
case "status": {
|
|
3987
|
+
const current = await loadGoal(goalPath);
|
|
3988
|
+
if (!current) {
|
|
3989
|
+
const msg2 = "No goal set. Use `/goal set <mission text>` to create one.";
|
|
3990
|
+
opts.renderer.write(msg2);
|
|
3991
|
+
return { message: msg2 };
|
|
3992
|
+
}
|
|
3993
|
+
const msg = formatGoal(current);
|
|
3994
|
+
opts.renderer.write(msg);
|
|
3995
|
+
return { message: msg };
|
|
3996
|
+
}
|
|
3997
|
+
case "set":
|
|
3998
|
+
case "new": {
|
|
3999
|
+
if (!setText) {
|
|
4000
|
+
const msg2 = "Usage: /goal set <mission text>";
|
|
4001
|
+
opts.renderer.writeWarning(msg2);
|
|
4002
|
+
return { message: msg2 };
|
|
4003
|
+
}
|
|
4004
|
+
const existing = await loadGoal(goalPath);
|
|
4005
|
+
const next = existing ? { ...existing, goal: setText, setAt: (/* @__PURE__ */ new Date()).toISOString(), lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() } : emptyGoal(setText);
|
|
4006
|
+
await saveGoal(goalPath, next);
|
|
4007
|
+
const shortGoal = setText.length > 80 ? `${setText.slice(0, 80)}\u2026` : setText;
|
|
4008
|
+
const msg = `\u{1F3AF} ${color.green("Goal locked:")} ${shortGoal}
|
|
4009
|
+
${color.dim(`Stored in ${goalPath} \u2014 Esc / /steer to redirect, Ctrl+C to stop.`)}`;
|
|
4010
|
+
opts.renderer.write(msg);
|
|
4011
|
+
return { message: msg, runText: buildGoalPreamble(setText) };
|
|
4012
|
+
}
|
|
4013
|
+
case "clear":
|
|
4014
|
+
case "reset": {
|
|
4015
|
+
const existing = await loadGoal(goalPath);
|
|
4016
|
+
if (!existing) {
|
|
4017
|
+
const msg2 = "No goal to clear.";
|
|
4018
|
+
opts.renderer.write(msg2);
|
|
4019
|
+
return { message: msg2 };
|
|
4020
|
+
}
|
|
4021
|
+
const { unlink: unlink4 } = await import('fs/promises');
|
|
4022
|
+
try {
|
|
4023
|
+
await unlink4(goalPath);
|
|
4024
|
+
} catch {
|
|
4025
|
+
}
|
|
4026
|
+
if (opts.onEternalStop) opts.onEternalStop();
|
|
4027
|
+
const msg = `${color.amber("Goal cleared.")} Eternal mode will stop on next cycle.`;
|
|
4028
|
+
opts.renderer.write(msg);
|
|
4029
|
+
return { message: msg };
|
|
4030
|
+
}
|
|
4031
|
+
case "journal":
|
|
4032
|
+
case "log": {
|
|
4033
|
+
const current = await loadGoal(goalPath);
|
|
4034
|
+
if (!current) {
|
|
4035
|
+
const msg2 = "No goal set.";
|
|
4036
|
+
opts.renderer.write(msg2);
|
|
4037
|
+
return { message: msg2 };
|
|
4038
|
+
}
|
|
4039
|
+
const n = restJoined ? Math.max(1, Number.parseInt(restJoined, 10) || 25) : 25;
|
|
4040
|
+
if (current.journal.length === 0) {
|
|
4041
|
+
const msg2 = "Journal is empty.";
|
|
4042
|
+
opts.renderer.write(msg2);
|
|
4043
|
+
return { message: msg2 };
|
|
4044
|
+
}
|
|
4045
|
+
const tail = current.journal.slice(-n);
|
|
4046
|
+
const lines = tail.map((e) => {
|
|
4047
|
+
const mark = e.status === "success" ? color.green("\u2713") : e.status === "failure" ? color.red("\u2717") : e.status === "aborted" ? color.amber("\u2298") : color.dim("\xB7");
|
|
4048
|
+
const note = e.note ? color.dim(` \u2014 ${e.note}`) : "";
|
|
4049
|
+
return `${color.dim(`#${e.iteration}`)} ${mark} ${color.dim(`[${e.source}]`)} ${e.task}${note}`;
|
|
4050
|
+
});
|
|
4051
|
+
const header = `Journal (last ${tail.length} of ${current.journal.length}):`;
|
|
4052
|
+
const msg = `${header}
|
|
4053
|
+
${lines.join("\n")}`;
|
|
4054
|
+
opts.renderer.write(msg);
|
|
4055
|
+
return { message: msg };
|
|
4056
|
+
}
|
|
4057
|
+
default: {
|
|
4058
|
+
const msg = `Unknown subcommand "${verb}". Try: show | set <text> | clear | journal [N]`;
|
|
4059
|
+
opts.renderer.writeWarning(msg);
|
|
4060
|
+
return { message: msg };
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
3834
4066
|
|
|
3835
4067
|
// src/slash-commands/mode.ts
|
|
3836
4068
|
function buildModeCommand(opts) {
|
|
@@ -3940,6 +4172,15 @@ ${lines.join("\n\n")}
|
|
|
3940
4172
|
}
|
|
3941
4173
|
};
|
|
3942
4174
|
}
|
|
4175
|
+
function getProviderFromContext(ctx, opts) {
|
|
4176
|
+
if (opts.llmProvider && typeof opts.llmProvider.complete === "function") {
|
|
4177
|
+
return { provider: opts.llmProvider, model: opts.llmModel };
|
|
4178
|
+
}
|
|
4179
|
+
if (ctx.provider && typeof ctx.provider.complete === "function") {
|
|
4180
|
+
return { provider: ctx.provider, model: ctx.model };
|
|
4181
|
+
}
|
|
4182
|
+
return null;
|
|
4183
|
+
}
|
|
3943
4184
|
function buildSecurityCommand(opts) {
|
|
3944
4185
|
return {
|
|
3945
4186
|
name: "security",
|
|
@@ -3991,11 +4232,11 @@ async function handleScan(args, ctx, opts) {
|
|
|
3991
4232
|
const options = parseArgs2(args);
|
|
3992
4233
|
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
3993
4234
|
try {
|
|
3994
|
-
const
|
|
3995
|
-
if (!
|
|
4235
|
+
const providerInfo = getProviderFromContext(ctx, opts);
|
|
4236
|
+
if (!providerInfo) {
|
|
3996
4237
|
return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
|
|
3997
4238
|
}
|
|
3998
|
-
const result = await defaultOrchestrator.run(
|
|
4239
|
+
const result = await defaultOrchestrator.run(providerInfo, {
|
|
3999
4240
|
projectRoot,
|
|
4000
4241
|
scanOptions: {
|
|
4001
4242
|
depth: options.depth || "standard",
|
|
@@ -4044,11 +4285,11 @@ async function handleScan(args, ctx, opts) {
|
|
|
4044
4285
|
async function handleAudit(ctx, opts) {
|
|
4045
4286
|
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
4046
4287
|
try {
|
|
4047
|
-
const
|
|
4048
|
-
if (!
|
|
4288
|
+
const providerInfo = getProviderFromContext(ctx, opts);
|
|
4289
|
+
if (!providerInfo) {
|
|
4049
4290
|
return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
|
|
4050
4291
|
}
|
|
4051
|
-
const result = await defaultOrchestrator.run(
|
|
4292
|
+
const result = await defaultOrchestrator.run(providerInfo, {
|
|
4052
4293
|
projectRoot,
|
|
4053
4294
|
reportOptions: { format: "markdown" }
|
|
4054
4295
|
});
|
|
@@ -4159,12 +4400,111 @@ function getHelpMessage() {
|
|
|
4159
4400
|
|
|
4160
4401
|
Run \`/security scan\` to start.`;
|
|
4161
4402
|
}
|
|
4403
|
+
var CONFIG_ENV = "WRONGSTACK_STATUSLINE_CONFIG";
|
|
4404
|
+
var DEFAULTS = {
|
|
4405
|
+
todos: true,
|
|
4406
|
+
plan: true,
|
|
4407
|
+
fleet: true,
|
|
4408
|
+
git: true,
|
|
4409
|
+
elapsed: true,
|
|
4410
|
+
context: true,
|
|
4411
|
+
cost: true
|
|
4412
|
+
};
|
|
4413
|
+
function resolveConfigPath() {
|
|
4414
|
+
return process.env[CONFIG_ENV] ?? path23.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
4415
|
+
}
|
|
4416
|
+
async function loadStatuslineConfig() {
|
|
4417
|
+
const p = resolveConfigPath();
|
|
4418
|
+
try {
|
|
4419
|
+
const raw = await fsp2.readFile(p, "utf8");
|
|
4420
|
+
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
4421
|
+
} catch {
|
|
4422
|
+
return { ...DEFAULTS };
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
async function saveStatuslineConfig(cfg) {
|
|
4426
|
+
const p = resolveConfigPath();
|
|
4427
|
+
await fsp2.mkdir(path23.dirname(p), { recursive: true });
|
|
4428
|
+
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
4429
|
+
}
|
|
4430
|
+
function buildStatuslineCommand(deps) {
|
|
4431
|
+
return {
|
|
4432
|
+
name: "statusline",
|
|
4433
|
+
aliases: ["sl"],
|
|
4434
|
+
description: "Customize status bar chips: /statusline [item] [on|off] or /statusline reset",
|
|
4435
|
+
help: [
|
|
4436
|
+
"Usage: /statusline [item] [on|off]",
|
|
4437
|
+
" /statusline \u2014 show current config",
|
|
4438
|
+
" /statusline <item> on \u2014 enable a chip",
|
|
4439
|
+
" /statusline <item> off \u2014 disable a chip",
|
|
4440
|
+
" /statusline reset \u2014 restore defaults",
|
|
4441
|
+
"",
|
|
4442
|
+
"Available items: todos, plan, fleet, git, elapsed, context, cost",
|
|
4443
|
+
"Persistent across sessions (saved to ~/.wrongstack/statusline.json)."
|
|
4444
|
+
].join("\n"),
|
|
4445
|
+
async run(args) {
|
|
4446
|
+
const cfg = await deps.getConfig();
|
|
4447
|
+
const trimmed = args.trim();
|
|
4448
|
+
const parts = trimmed.split(/\s+/);
|
|
4449
|
+
const [item, action] = parts;
|
|
4450
|
+
if (!item) {
|
|
4451
|
+
const lines = ["StatusBar chips:"];
|
|
4452
|
+
const items = [
|
|
4453
|
+
"todos",
|
|
4454
|
+
"plan",
|
|
4455
|
+
"fleet",
|
|
4456
|
+
"git",
|
|
4457
|
+
"elapsed",
|
|
4458
|
+
"context",
|
|
4459
|
+
"cost"
|
|
4460
|
+
];
|
|
4461
|
+
for (const k of items) {
|
|
4462
|
+
const val = cfg[k];
|
|
4463
|
+
if (val === void 0) continue;
|
|
4464
|
+
lines.push(` ${val ? "\u25CF" : "\u25CB"} ${k}`);
|
|
4465
|
+
}
|
|
4466
|
+
return { message: lines.join("\n") };
|
|
4467
|
+
}
|
|
4468
|
+
if (item === "reset") {
|
|
4469
|
+
await deps.setConfig({ ...DEFAULTS });
|
|
4470
|
+
deps.setHiddenItems([]);
|
|
4471
|
+
return { message: "StatusBar config reset to defaults." };
|
|
4472
|
+
}
|
|
4473
|
+
const validItems = [
|
|
4474
|
+
"todos",
|
|
4475
|
+
"plan",
|
|
4476
|
+
"fleet",
|
|
4477
|
+
"git",
|
|
4478
|
+
"elapsed",
|
|
4479
|
+
"context",
|
|
4480
|
+
"cost"
|
|
4481
|
+
];
|
|
4482
|
+
if (!validItems.includes(item)) {
|
|
4483
|
+
return {
|
|
4484
|
+
message: `Unknown item "${item}". Available: ${validItems.join(", ")}. Usage: /statusline <item> on|off`
|
|
4485
|
+
};
|
|
4486
|
+
}
|
|
4487
|
+
const onOff = action?.toLowerCase();
|
|
4488
|
+
if (!onOff || onOff !== "on" && onOff !== "off") {
|
|
4489
|
+
return { message: `Usage: /statusline ${item} on|off` };
|
|
4490
|
+
}
|
|
4491
|
+
const next = { ...cfg, [item]: onOff === "on" };
|
|
4492
|
+
await deps.setConfig(next);
|
|
4493
|
+
if (onOff === "off") {
|
|
4494
|
+
deps.setHiddenItems([...deps.hiddenItems, item]);
|
|
4495
|
+
} else {
|
|
4496
|
+
deps.setHiddenItems(deps.hiddenItems.filter((i) => i !== item));
|
|
4497
|
+
}
|
|
4498
|
+
return { message: `statusline ${item}: ${onOff}` };
|
|
4499
|
+
}
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4162
4502
|
function makeInstaller(opts, projectRoot, global) {
|
|
4163
|
-
const globalRoot =
|
|
4503
|
+
const globalRoot = path23.join(os6.homedir(), ".wrongstack");
|
|
4164
4504
|
return new SkillInstaller({
|
|
4165
|
-
manifestPath:
|
|
4166
|
-
projectSkillsDir:
|
|
4167
|
-
globalSkillsDir:
|
|
4505
|
+
manifestPath: path23.join(globalRoot, "installed-skills.json"),
|
|
4506
|
+
projectSkillsDir: path23.join(projectRoot, ".wrongstack", "skills"),
|
|
4507
|
+
globalSkillsDir: path23.join(globalRoot, "skills"),
|
|
4168
4508
|
projectHash: projectHash(projectRoot),
|
|
4169
4509
|
skillLoader: opts.skillLoader
|
|
4170
4510
|
});
|
|
@@ -4352,12 +4692,22 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
4352
4692
|
buildLoadCommand(opts),
|
|
4353
4693
|
buildYoloCommand(opts),
|
|
4354
4694
|
buildAutonomyCommand(opts),
|
|
4695
|
+
buildGoalCommand(opts),
|
|
4355
4696
|
buildModeCommand(opts),
|
|
4356
4697
|
buildExitCommand(opts),
|
|
4357
4698
|
buildCommitCommand(),
|
|
4358
4699
|
buildGitcheckCommand(),
|
|
4359
4700
|
buildPushCommand(),
|
|
4360
|
-
buildSecurityCommand(opts)
|
|
4701
|
+
buildSecurityCommand(opts),
|
|
4702
|
+
buildStatuslineCommand({
|
|
4703
|
+
cwd: opts.cwd,
|
|
4704
|
+
hiddenItems: opts.statuslineHiddenItems ?? [],
|
|
4705
|
+
setHiddenItems: opts.setStatuslineHiddenItems ?? (() => {
|
|
4706
|
+
}),
|
|
4707
|
+
getConfig: opts.statuslineConfig?.get ?? (async () => ({})),
|
|
4708
|
+
setConfig: opts.statuslineConfig?.set ?? (async () => {
|
|
4709
|
+
})
|
|
4710
|
+
})
|
|
4361
4711
|
];
|
|
4362
4712
|
}
|
|
4363
4713
|
|
|
@@ -4376,13 +4726,13 @@ var MANIFESTS = [
|
|
|
4376
4726
|
];
|
|
4377
4727
|
async function detectProjectKind(projectRoot) {
|
|
4378
4728
|
try {
|
|
4379
|
-
await fsp2.access(
|
|
4729
|
+
await fsp2.access(path23.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
4380
4730
|
return "initialized";
|
|
4381
4731
|
} catch {
|
|
4382
4732
|
}
|
|
4383
4733
|
for (const m of MANIFESTS) {
|
|
4384
4734
|
try {
|
|
4385
|
-
await fsp2.access(
|
|
4735
|
+
await fsp2.access(path23.join(projectRoot, m));
|
|
4386
4736
|
return "project";
|
|
4387
4737
|
} catch {
|
|
4388
4738
|
}
|
|
@@ -4390,8 +4740,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
4390
4740
|
return "empty";
|
|
4391
4741
|
}
|
|
4392
4742
|
async function scaffoldAgentsMd(projectRoot) {
|
|
4393
|
-
const dir =
|
|
4394
|
-
const file =
|
|
4743
|
+
const dir = path23.join(projectRoot, ".wrongstack");
|
|
4744
|
+
const file = path23.join(dir, "AGENTS.md");
|
|
4395
4745
|
const facts = await detectProjectFacts(projectRoot);
|
|
4396
4746
|
const body = renderAgentsTemplate(facts);
|
|
4397
4747
|
await fsp2.mkdir(dir, { recursive: true });
|
|
@@ -4404,7 +4754,7 @@ async function runProjectCheck(opts) {
|
|
|
4404
4754
|
if (kind === "initialized") {
|
|
4405
4755
|
renderer.write(
|
|
4406
4756
|
`
|
|
4407
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
4757
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path23.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
4408
4758
|
`
|
|
4409
4759
|
);
|
|
4410
4760
|
return true;
|
|
@@ -4416,8 +4766,12 @@ async function runProjectCheck(opts) {
|
|
|
4416
4766
|
`
|
|
4417
4767
|
);
|
|
4418
4768
|
const answer2 = (await reader.readLine(
|
|
4419
|
-
` ${color.amber("?")} Scaffold ${color.bold("AGENTS.md")} now? ${color.dim("[y/N]")} `
|
|
4769
|
+
` ${color.amber("?")} Scaffold ${color.bold("AGENTS.md")} now? ${color.dim("[y/N/q]")} `
|
|
4420
4770
|
)).trim().toLowerCase();
|
|
4771
|
+
if (answer2 === "q") {
|
|
4772
|
+
renderer.write(color.dim(" Cancelled.\n"));
|
|
4773
|
+
return false;
|
|
4774
|
+
}
|
|
4421
4775
|
if (answer2 === "y" || answer2 === "yes") {
|
|
4422
4776
|
try {
|
|
4423
4777
|
const file = await scaffoldAgentsMd(projectRoot);
|
|
@@ -4431,7 +4785,7 @@ async function runProjectCheck(opts) {
|
|
|
4431
4785
|
}
|
|
4432
4786
|
return true;
|
|
4433
4787
|
}
|
|
4434
|
-
const gitDir =
|
|
4788
|
+
const gitDir = path23.join(projectRoot, ".git");
|
|
4435
4789
|
let hasGit = false;
|
|
4436
4790
|
try {
|
|
4437
4791
|
await fsp2.access(gitDir);
|
|
@@ -4445,8 +4799,12 @@ async function runProjectCheck(opts) {
|
|
|
4445
4799
|
`
|
|
4446
4800
|
);
|
|
4447
4801
|
const answer2 = (await reader.readLine(
|
|
4448
|
-
` ${color.amber("?")} No git repo found. ${color.bold("Initialize git?")} ${color.dim("[y/N]")} `
|
|
4802
|
+
` ${color.amber("?")} No git repo found. ${color.bold("Initialize git?")} ${color.dim("[y/N/q]")} `
|
|
4449
4803
|
)).trim().toLowerCase();
|
|
4804
|
+
if (answer2 === "q") {
|
|
4805
|
+
renderer.write(color.dim(" Cancelled.\n"));
|
|
4806
|
+
return false;
|
|
4807
|
+
}
|
|
4450
4808
|
if (answer2 === "y" || answer2 === "yes") {
|
|
4451
4809
|
try {
|
|
4452
4810
|
const { spawn: spawn3 } = await import('child_process');
|
|
@@ -4468,8 +4826,8 @@ async function runProjectCheck(opts) {
|
|
|
4468
4826
|
`
|
|
4469
4827
|
);
|
|
4470
4828
|
}
|
|
4471
|
-
const answer = (await reader.readLine(` ${color.amber("?")} Continue anyway? ${color.dim("[Y/n]")} `)).trim().toLowerCase();
|
|
4472
|
-
if (answer === "n" || answer === "no") {
|
|
4829
|
+
const answer = (await reader.readLine(` ${color.amber("?")} Continue anyway? ${color.dim("[Y/n/q]")} `)).trim().toLowerCase();
|
|
4830
|
+
if (answer === "q" || answer === "n" || answer === "no") {
|
|
4473
4831
|
renderer.write(color.dim(" Cancelled.\n"));
|
|
4474
4832
|
return false;
|
|
4475
4833
|
}
|
|
@@ -4483,8 +4841,12 @@ async function runLaunchPrompts(opts) {
|
|
|
4483
4841
|
} else {
|
|
4484
4842
|
const answer = (await reader.readLine(
|
|
4485
4843
|
`
|
|
4486
|
-
${color.amber("?")} Interactive mode: ${color.bold("T")}UI / ${color.bold("R")}EPL ${color.dim("[T/r]")} `
|
|
4844
|
+
${color.amber("?")} Interactive mode: ${color.bold("T")}UI / ${color.bold("R")}EPL ${color.dim("[T/r/q]")} `
|
|
4487
4845
|
)).trim().toLowerCase();
|
|
4846
|
+
if (answer === "q") {
|
|
4847
|
+
renderer.write(color.dim(" Goodbye!\n"));
|
|
4848
|
+
process.exit(0);
|
|
4849
|
+
}
|
|
4488
4850
|
mode = answer === "r" || answer === "repl" ? "repl" : "tui";
|
|
4489
4851
|
}
|
|
4490
4852
|
let yolo;
|
|
@@ -4492,8 +4854,12 @@ async function runLaunchPrompts(opts) {
|
|
|
4492
4854
|
yolo = yoloPinned;
|
|
4493
4855
|
} else {
|
|
4494
4856
|
const answer = (await reader.readLine(
|
|
4495
|
-
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[Y/n]")} `
|
|
4857
|
+
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[Y/n/q]")} `
|
|
4496
4858
|
)).trim().toLowerCase();
|
|
4859
|
+
if (answer === "q") {
|
|
4860
|
+
renderer.write(color.dim(" Goodbye!\n"));
|
|
4861
|
+
process.exit(0);
|
|
4862
|
+
}
|
|
4497
4863
|
yolo = answer !== "n" && answer !== "no";
|
|
4498
4864
|
}
|
|
4499
4865
|
renderer.write(
|
|
@@ -4732,14 +5098,14 @@ function summarize(value, name) {
|
|
|
4732
5098
|
if (typeof v === "object" && v !== null) {
|
|
4733
5099
|
const o = v;
|
|
4734
5100
|
if (name === "edit") {
|
|
4735
|
-
const
|
|
5101
|
+
const path24 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4736
5102
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
4737
|
-
return `${
|
|
5103
|
+
return `${path24} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
4738
5104
|
}
|
|
4739
5105
|
if (name === "write") {
|
|
4740
|
-
const
|
|
5106
|
+
const path24 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4741
5107
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
4742
|
-
return bytes !== void 0 ? `${
|
|
5108
|
+
return bytes !== void 0 ? `${path24} ${bytes}B` : path24;
|
|
4743
5109
|
}
|
|
4744
5110
|
if (typeof o["count"] === "number") {
|
|
4745
5111
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -4889,10 +5255,12 @@ ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.am
|
|
|
4889
5255
|
deps.renderer.write(` ${color.bold("x")} Remove this provider entirely
|
|
4890
5256
|
`);
|
|
4891
5257
|
deps.renderer.write(` ${color.bold("b")} Back
|
|
5258
|
+
`);
|
|
5259
|
+
deps.renderer.write(` ${color.bold("q")} Quit
|
|
4892
5260
|
`);
|
|
4893
5261
|
const raw = (await deps.reader.readLine(`
|
|
4894
5262
|
${color.amber("?")} ${providerId} > `)).trim();
|
|
4895
|
-
if (!raw || raw === "b" || raw === "back") return;
|
|
5263
|
+
if (!raw || raw === "b" || raw === "back" || raw === "q" || raw === "quit") return;
|
|
4896
5264
|
const [verb, argRaw] = raw.split(/\s+/, 2);
|
|
4897
5265
|
const arg = argRaw ? Number.parseInt(argRaw, 10) : Number.NaN;
|
|
4898
5266
|
if (verb === "a" || verb === "add") {
|
|
@@ -4901,8 +5269,9 @@ ${color.amber("?")} ${providerId} > `)).trim();
|
|
|
4901
5269
|
}
|
|
4902
5270
|
if (verb === "x" || verb === "remove") {
|
|
4903
5271
|
const confirm = (await deps.reader.readLine(
|
|
4904
|
-
` ${color.amber("?")} Remove provider "${providerId}" and ${keys.length} key(s)? ${color.dim("[y/N]")} `
|
|
5272
|
+
` ${color.amber("?")} Remove provider "${providerId}" and ${keys.length} key(s)? ${color.dim("[y/N/q]")} `
|
|
4905
5273
|
)).trim().toLowerCase();
|
|
5274
|
+
if (confirm === "q") continue;
|
|
4906
5275
|
if (confirm === "y" || confirm === "yes") {
|
|
4907
5276
|
await mutateProviders(deps, (all) => {
|
|
4908
5277
|
delete all[providerId];
|
|
@@ -4940,8 +5309,9 @@ ${color.amber("?")} ${providerId} > `)).trim();
|
|
|
4940
5309
|
}
|
|
4941
5310
|
const target = keys[arg - 1];
|
|
4942
5311
|
const confirm = (await deps.reader.readLine(
|
|
4943
|
-
` ${color.amber("?")} Delete key "${target.label}" (${maskedKey(target.apiKey)})? ${color.dim("[y/N]")} `
|
|
5312
|
+
` ${color.amber("?")} Delete key "${target.label}" (${maskedKey(target.apiKey)})? ${color.dim("[y/N/q]")} `
|
|
4944
5313
|
)).trim().toLowerCase();
|
|
5314
|
+
if (confirm === "q") continue;
|
|
4945
5315
|
if (confirm !== "y" && confirm !== "yes") continue;
|
|
4946
5316
|
await mutateProviders(deps, (all) => {
|
|
4947
5317
|
const p = all[providerId];
|
|
@@ -5038,8 +5408,8 @@ async function addForNewProvider(deps) {
|
|
|
5038
5408
|
deps.renderer.writeWarning("Catalog unavailable \u2014 falling back to manual entry.");
|
|
5039
5409
|
}
|
|
5040
5410
|
if (catalog.length === 0) {
|
|
5041
|
-
const pid = (await deps.reader.readLine(` ${color.amber("?")} Provider id: `)).trim();
|
|
5042
|
-
if (!pid) return;
|
|
5411
|
+
const pid = (await deps.reader.readLine(` ${color.amber("?")} Provider id ${color.dim("[q to quit]")}: `)).trim();
|
|
5412
|
+
if (!pid || pid === "q") return;
|
|
5043
5413
|
const fam = (await deps.reader.readLine(
|
|
5044
5414
|
` ${color.amber("?")} Family (anthropic/openai/openai-compatible/google): `
|
|
5045
5415
|
)).trim();
|
|
@@ -5059,8 +5429,9 @@ async function addForNewProvider(deps) {
|
|
|
5059
5429
|
)
|
|
5060
5430
|
);
|
|
5061
5431
|
const filterRaw = (await deps.reader.readLine(
|
|
5062
|
-
` ${color.amber("?")} Filter ${color.dim('(substring
|
|
5432
|
+
` ${color.amber("?")} Filter ${color.dim('(substring, "s" for unsaved-only, q to quit)')}: `
|
|
5063
5433
|
)).trim();
|
|
5434
|
+
if (filterRaw === "q") return;
|
|
5064
5435
|
const filterLc = filterRaw.toLowerCase();
|
|
5065
5436
|
const showUnsavedOnly = filterLc === "s" || filterLc === "unsaved";
|
|
5066
5437
|
const matches = (p) => {
|
|
@@ -5114,9 +5485,9 @@ async function addForNewProvider(deps) {
|
|
|
5114
5485
|
`);
|
|
5115
5486
|
const answer = (await deps.reader.readLine(
|
|
5116
5487
|
`
|
|
5117
|
-
${color.amber("?")} Pick (1-${ordered.length}) or type provider id: `
|
|
5488
|
+
${color.amber("?")} Pick (1-${ordered.length}) or type provider id ${color.dim("[q to quit]")}: `
|
|
5118
5489
|
)).trim();
|
|
5119
|
-
if (!answer) return;
|
|
5490
|
+
if (!answer || answer === "q") return;
|
|
5120
5491
|
let chosen;
|
|
5121
5492
|
const num = Number.parseInt(answer, 10);
|
|
5122
5493
|
if (!Number.isNaN(num) && num >= 1 && num <= ordered.length) {
|
|
@@ -5133,7 +5504,8 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id: `
|
|
|
5133
5504
|
Defaults from models.dev \u2014 press Enter to keep, or type a new value.
|
|
5134
5505
|
`)
|
|
5135
5506
|
);
|
|
5136
|
-
const famRaw = (await deps.reader.readLine(` ${color.amber("?")} Family ${color.dim(`[${chosen.family}]`)}: `)).trim();
|
|
5507
|
+
const famRaw = (await deps.reader.readLine(` ${color.amber("?")} Family ${color.dim(`[${chosen.family}]`)} ${color.dim("(q to quit)")}: `)).trim();
|
|
5508
|
+
if (famRaw === "q") return;
|
|
5137
5509
|
let family = chosen.family;
|
|
5138
5510
|
if (famRaw) {
|
|
5139
5511
|
if (!["anthropic", "openai", "openai-compatible", "google"].includes(famRaw)) {
|
|
@@ -5145,8 +5517,9 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id: `
|
|
|
5145
5517
|
family = famRaw;
|
|
5146
5518
|
}
|
|
5147
5519
|
const baseRaw = (await deps.reader.readLine(
|
|
5148
|
-
` ${color.amber("?")} Base URL ${color.dim(`[${chosen.apiBase ?? "unset"}]`)}: `
|
|
5520
|
+
` ${color.amber("?")} Base URL ${color.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color.dim("(q to quit)")}: `
|
|
5149
5521
|
)).trim();
|
|
5522
|
+
if (baseRaw === "q") return;
|
|
5150
5523
|
const baseUrl = baseRaw || chosen.apiBase;
|
|
5151
5524
|
const providersNow = await loadProviders(deps);
|
|
5152
5525
|
let suggestedAlias = chosen.id;
|
|
@@ -5191,17 +5564,18 @@ ${color.bold("Custom provider")} ${color.dim("\u2014 for local models or proxies
|
|
|
5191
5564
|
`
|
|
5192
5565
|
);
|
|
5193
5566
|
const type = (await deps.reader.readLine(
|
|
5194
|
-
` ${color.amber("?")} Provider id ${color.dim('(e.g. "local-llama", "my-proxy")')}: `
|
|
5567
|
+
` ${color.amber("?")} Provider id ${color.dim('(e.g. "local-llama", "my-proxy", q to quit)')}: `
|
|
5195
5568
|
)).trim();
|
|
5196
|
-
if (!type) return;
|
|
5569
|
+
if (!type || type === "q") return;
|
|
5197
5570
|
const existing = (await loadProviders(deps))[type];
|
|
5198
5571
|
if (existing) {
|
|
5199
5572
|
deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
|
|
5200
5573
|
return;
|
|
5201
5574
|
}
|
|
5202
5575
|
const familyRaw = (await deps.reader.readLine(
|
|
5203
|
-
` ${color.amber("?")} Wire family ${color.dim("(anthropic | openai | openai-compatible | google)")}: `
|
|
5576
|
+
` ${color.amber("?")} Wire family ${color.dim("(anthropic | openai | openai-compatible | google)")} ${color.dim("(q to quit)")}: `
|
|
5204
5577
|
)).trim();
|
|
5578
|
+
if (familyRaw === "q") return;
|
|
5205
5579
|
if (!["anthropic", "openai", "openai-compatible", "google"].includes(familyRaw)) {
|
|
5206
5580
|
deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
|
|
5207
5581
|
return;
|
|
@@ -5336,13 +5710,21 @@ async function loadProviders(deps) {
|
|
|
5336
5710
|
let raw;
|
|
5337
5711
|
try {
|
|
5338
5712
|
raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
|
|
5339
|
-
} catch {
|
|
5713
|
+
} catch (err) {
|
|
5714
|
+
if (err.code !== "ENOENT") {
|
|
5715
|
+
deps.renderer.writeWarning(
|
|
5716
|
+
`Could not read ${deps.globalConfigPath}: ${err.message}. Treating as empty.`
|
|
5717
|
+
);
|
|
5718
|
+
}
|
|
5340
5719
|
return {};
|
|
5341
5720
|
}
|
|
5342
5721
|
let parsed = {};
|
|
5343
5722
|
try {
|
|
5344
5723
|
parsed = JSON.parse(raw);
|
|
5345
|
-
} catch {
|
|
5724
|
+
} catch (err) {
|
|
5725
|
+
deps.renderer.writeWarning(
|
|
5726
|
+
`Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`
|
|
5727
|
+
);
|
|
5346
5728
|
return {};
|
|
5347
5729
|
}
|
|
5348
5730
|
const decrypted = decryptConfigSecrets(parsed, deps.vault);
|
|
@@ -5350,15 +5732,29 @@ async function loadProviders(deps) {
|
|
|
5350
5732
|
}
|
|
5351
5733
|
async function mutateProviders(deps, mutator) {
|
|
5352
5734
|
let raw;
|
|
5735
|
+
let fileExists = true;
|
|
5353
5736
|
try {
|
|
5354
5737
|
raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
|
|
5355
|
-
} catch {
|
|
5738
|
+
} catch (err) {
|
|
5739
|
+
if (err.code !== "ENOENT") {
|
|
5740
|
+
throw new Error(
|
|
5741
|
+
`Refusing to mutate ${deps.globalConfigPath}: ${err.message}`,
|
|
5742
|
+
{ cause: err }
|
|
5743
|
+
);
|
|
5744
|
+
}
|
|
5745
|
+
fileExists = false;
|
|
5356
5746
|
raw = "{}";
|
|
5357
5747
|
}
|
|
5358
5748
|
let parsed;
|
|
5359
5749
|
try {
|
|
5360
5750
|
parsed = JSON.parse(raw);
|
|
5361
|
-
} catch {
|
|
5751
|
+
} catch (err) {
|
|
5752
|
+
if (fileExists) {
|
|
5753
|
+
throw new Error(
|
|
5754
|
+
`Refusing to overwrite corrupt config at ${deps.globalConfigPath} (${err.message}). Fix or move the file aside before retrying.`,
|
|
5755
|
+
{ cause: err }
|
|
5756
|
+
);
|
|
5757
|
+
}
|
|
5362
5758
|
parsed = {};
|
|
5363
5759
|
}
|
|
5364
5760
|
const decrypted = decryptConfigSecrets(parsed, deps.vault);
|
|
@@ -5562,7 +5958,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
5562
5958
|
}
|
|
5563
5959
|
try {
|
|
5564
5960
|
await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
5565
|
-
const probe =
|
|
5961
|
+
const probe = path23.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
5566
5962
|
await fsp2.writeFile(probe, "");
|
|
5567
5963
|
await fsp2.unlink(probe);
|
|
5568
5964
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -5665,8 +6061,8 @@ var exportCmd = async (args, deps) => {
|
|
|
5665
6061
|
return 1;
|
|
5666
6062
|
}
|
|
5667
6063
|
if (output) {
|
|
5668
|
-
await fsp2.mkdir(
|
|
5669
|
-
await fsp2.writeFile(
|
|
6064
|
+
await fsp2.mkdir(path23.dirname(path23.resolve(deps.cwd, output)), { recursive: true });
|
|
6065
|
+
await fsp2.writeFile(path23.resolve(deps.cwd, output), rendered, "utf8");
|
|
5670
6066
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
5671
6067
|
`);
|
|
5672
6068
|
} else {
|
|
@@ -5695,7 +6091,12 @@ var initCmd = async (_args, deps) => {
|
|
|
5695
6091
|
`
|
|
5696
6092
|
);
|
|
5697
6093
|
const defaultId = ranked[0]?.id ?? "anthropic";
|
|
5698
|
-
const
|
|
6094
|
+
const providerAnswer = (await deps.reader.readLine(`Provider [${defaultId}]: `)).trim();
|
|
6095
|
+
if (providerAnswer === "q") {
|
|
6096
|
+
deps.renderer.write(color.dim("Cancelled.\n"));
|
|
6097
|
+
return 0;
|
|
6098
|
+
}
|
|
6099
|
+
const providerId = providerAnswer || defaultId;
|
|
5699
6100
|
const provider = await deps.modelsRegistry.getProvider(providerId);
|
|
5700
6101
|
if (!provider) {
|
|
5701
6102
|
deps.renderer.writeError(`Provider "${providerId}" not found in models.dev catalog.`);
|
|
@@ -5709,7 +6110,12 @@ var initCmd = async (_args, deps) => {
|
|
|
5709
6110
|
}
|
|
5710
6111
|
const suggestedModel = await deps.modelsRegistry.suggestModel(providerId) ?? "";
|
|
5711
6112
|
const modelHint = suggestedModel ? ` [${suggestedModel}]` : "";
|
|
5712
|
-
const
|
|
6113
|
+
const modelAnswer = (await deps.reader.readLine(`Model${modelHint}: `)).trim();
|
|
6114
|
+
if (modelAnswer === "q") {
|
|
6115
|
+
deps.renderer.write(color.dim("Cancelled.\n"));
|
|
6116
|
+
return 0;
|
|
6117
|
+
}
|
|
6118
|
+
const modelId = modelAnswer || suggestedModel;
|
|
5713
6119
|
if (!modelId) {
|
|
5714
6120
|
deps.renderer.writeError("No model selected. Aborting.");
|
|
5715
6121
|
return 1;
|
|
@@ -5726,12 +6132,12 @@ var initCmd = async (_args, deps) => {
|
|
|
5726
6132
|
await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
5727
6133
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
5728
6134
|
if (apiKey) config.apiKey = apiKey;
|
|
5729
|
-
const keyFile =
|
|
6135
|
+
const keyFile = path23.join(path23.dirname(deps.paths.globalConfig), ".key");
|
|
5730
6136
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
5731
6137
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
5732
6138
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
5733
|
-
await fsp2.mkdir(
|
|
5734
|
-
const agentsFile =
|
|
6139
|
+
await fsp2.mkdir(path23.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
6140
|
+
const agentsFile = path23.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
5735
6141
|
try {
|
|
5736
6142
|
await fsp2.access(agentsFile);
|
|
5737
6143
|
} catch {
|
|
@@ -6041,7 +6447,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
6041
6447
|
return 0;
|
|
6042
6448
|
};
|
|
6043
6449
|
var projectsCmd = async (_args, deps) => {
|
|
6044
|
-
const projectsRoot =
|
|
6450
|
+
const projectsRoot = path23.join(deps.paths.globalRoot, "projects");
|
|
6045
6451
|
try {
|
|
6046
6452
|
const entries = await fsp2.readdir(projectsRoot);
|
|
6047
6453
|
if (entries.length === 0) {
|
|
@@ -6051,7 +6457,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
6051
6457
|
for (const hash of entries) {
|
|
6052
6458
|
try {
|
|
6053
6459
|
const meta = JSON.parse(
|
|
6054
|
-
await fsp2.readFile(
|
|
6460
|
+
await fsp2.readFile(path23.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
6055
6461
|
);
|
|
6056
6462
|
deps.renderer.write(
|
|
6057
6463
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -6212,7 +6618,7 @@ async function listFleetRuns(deps) {
|
|
|
6212
6618
|
}
|
|
6213
6619
|
const runs = [];
|
|
6214
6620
|
for (const id of entries) {
|
|
6215
|
-
const runDir =
|
|
6621
|
+
const runDir = path23.join(deps.paths.projectSessions, id);
|
|
6216
6622
|
let stat3;
|
|
6217
6623
|
try {
|
|
6218
6624
|
stat3 = await fsp2.stat(runDir);
|
|
@@ -6225,17 +6631,17 @@ async function listFleetRuns(deps) {
|
|
|
6225
6631
|
let subagentCount = 0;
|
|
6226
6632
|
let subagentsDir;
|
|
6227
6633
|
try {
|
|
6228
|
-
await fsp2.access(
|
|
6634
|
+
await fsp2.access(path23.join(runDir, "fleet.json"));
|
|
6229
6635
|
manifest = true;
|
|
6230
6636
|
} catch {
|
|
6231
6637
|
}
|
|
6232
6638
|
try {
|
|
6233
|
-
await fsp2.access(
|
|
6639
|
+
await fsp2.access(path23.join(runDir, "checkpoint.json"));
|
|
6234
6640
|
checkpoint = true;
|
|
6235
6641
|
} catch {
|
|
6236
6642
|
}
|
|
6237
6643
|
try {
|
|
6238
|
-
subagentsDir =
|
|
6644
|
+
subagentsDir = path23.join(runDir, "subagents");
|
|
6239
6645
|
const files = await fsp2.readdir(subagentsDir);
|
|
6240
6646
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
6241
6647
|
} catch {
|
|
@@ -6264,7 +6670,7 @@ async function listFleetRuns(deps) {
|
|
|
6264
6670
|
return 0;
|
|
6265
6671
|
}
|
|
6266
6672
|
async function showFleetRun(runId, deps) {
|
|
6267
|
-
const runDir =
|
|
6673
|
+
const runDir = path23.join(deps.paths.projectSessions, runId);
|
|
6268
6674
|
let stat3;
|
|
6269
6675
|
try {
|
|
6270
6676
|
stat3 = await fsp2.stat(runDir);
|
|
@@ -6281,7 +6687,7 @@ async function showFleetRun(runId, deps) {
|
|
|
6281
6687
|
deps.renderer.write(color.bold(`
|
|
6282
6688
|
Fleet Run: ${runId}
|
|
6283
6689
|
`) + "\n");
|
|
6284
|
-
const manifestPath =
|
|
6690
|
+
const manifestPath = path23.join(runDir, "fleet.json");
|
|
6285
6691
|
let manifestData = null;
|
|
6286
6692
|
try {
|
|
6287
6693
|
manifestData = await fsp2.readFile(manifestPath, "utf8");
|
|
@@ -6297,7 +6703,7 @@ Fleet Run: ${runId}
|
|
|
6297
6703
|
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
6298
6704
|
`);
|
|
6299
6705
|
}
|
|
6300
|
-
const checkpointPath =
|
|
6706
|
+
const checkpointPath = path23.join(runDir, "checkpoint.json");
|
|
6301
6707
|
let checkpointData = null;
|
|
6302
6708
|
try {
|
|
6303
6709
|
checkpointData = await fsp2.readFile(checkpointPath, "utf8");
|
|
@@ -6344,7 +6750,7 @@ Fleet Run: ${runId}
|
|
|
6344
6750
|
} catch {
|
|
6345
6751
|
}
|
|
6346
6752
|
}
|
|
6347
|
-
const subagentsDir =
|
|
6753
|
+
const subagentsDir = path23.join(runDir, "subagents");
|
|
6348
6754
|
let subagentFiles = [];
|
|
6349
6755
|
try {
|
|
6350
6756
|
subagentFiles = await fsp2.readdir(subagentsDir);
|
|
@@ -6356,7 +6762,7 @@ Fleet Run: ${runId}
|
|
|
6356
6762
|
Subagent transcripts (${subagentFiles.length}):
|
|
6357
6763
|
`);
|
|
6358
6764
|
for (const f of subagentFiles.sort()) {
|
|
6359
|
-
const filePath =
|
|
6765
|
+
const filePath = path23.join(subagentsDir, f);
|
|
6360
6766
|
let size;
|
|
6361
6767
|
try {
|
|
6362
6768
|
const s = await fsp2.stat(filePath);
|
|
@@ -6373,7 +6779,7 @@ Fleet Run: ${runId}
|
|
|
6373
6779
|
${color.dim("\u25CB")} No subagent transcripts
|
|
6374
6780
|
`);
|
|
6375
6781
|
}
|
|
6376
|
-
const sharedDir =
|
|
6782
|
+
const sharedDir = path23.join(runDir, "shared");
|
|
6377
6783
|
try {
|
|
6378
6784
|
const files = await fsp2.readdir(sharedDir);
|
|
6379
6785
|
deps.renderer.write(`
|
|
@@ -6529,7 +6935,7 @@ function parseRewindFlags(args) {
|
|
|
6529
6935
|
var rewindCmd = async (args, deps) => {
|
|
6530
6936
|
const flags = parseRewindFlags(args);
|
|
6531
6937
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
6532
|
-
const sessionsDir =
|
|
6938
|
+
const sessionsDir = path23.join(wpaths.globalRoot, "sessions");
|
|
6533
6939
|
const rewind = new DefaultSessionRewinder(sessionsDir);
|
|
6534
6940
|
let sessionId = args.find((a) => !a.startsWith("--"));
|
|
6535
6941
|
if (!sessionId) {
|
|
@@ -6672,6 +7078,7 @@ var helpCmd = async (_args, deps) => {
|
|
|
6672
7078
|
"",
|
|
6673
7079
|
" wstack Start REPL",
|
|
6674
7080
|
' wstack "<task>" Run task and exit',
|
|
7081
|
+
' wstack --eternal "<mission>" Launch eternal-autonomy loop against a goal \u2014 Ctrl+C to stop',
|
|
6675
7082
|
" wstack resume [<id>] Resume a session",
|
|
6676
7083
|
" wstack sessions List recent sessions",
|
|
6677
7084
|
" wstack init Pick provider + model from models.dev",
|
|
@@ -6739,22 +7146,22 @@ function fmtDuration(ms) {
|
|
|
6739
7146
|
const remMin = m - h * 60;
|
|
6740
7147
|
return `${h}h${remMin}m`;
|
|
6741
7148
|
}
|
|
6742
|
-
function fmtTaskResultLine(r,
|
|
7149
|
+
function fmtTaskResultLine(r, color34) {
|
|
6743
7150
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
6744
7151
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
6745
7152
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
6746
7153
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
6747
|
-
const errKindChip = errKind ?
|
|
6748
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
7154
|
+
const errKindChip = errKind ? color34.dim(` [${errKind}]`) : "";
|
|
7155
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color34.dim(errTail)}` : "";
|
|
6749
7156
|
switch (r.status) {
|
|
6750
7157
|
case "success":
|
|
6751
|
-
return { mark:
|
|
7158
|
+
return { mark: color34.green("\u2713"), stats, tail: "" };
|
|
6752
7159
|
case "timeout":
|
|
6753
|
-
return { mark:
|
|
7160
|
+
return { mark: color34.yellow("\u23F1"), stats: `${color34.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
6754
7161
|
case "stopped":
|
|
6755
|
-
return { mark:
|
|
7162
|
+
return { mark: color34.dim("\u2298"), stats: `${color34.dim("stopped")} ${stats}`, tail: errSnip };
|
|
6756
7163
|
case "failed":
|
|
6757
|
-
return { mark:
|
|
7164
|
+
return { mark: color34.red("\u2717"), stats: `${color34.red("failed")} ${stats}`, tail: errSnip };
|
|
6758
7165
|
}
|
|
6759
7166
|
}
|
|
6760
7167
|
|
|
@@ -6764,7 +7171,7 @@ function resolveBundledSkillsDir() {
|
|
|
6764
7171
|
try {
|
|
6765
7172
|
const req2 = createRequire(import.meta.url);
|
|
6766
7173
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
6767
|
-
return
|
|
7174
|
+
return path23.join(path23.dirname(corePkg), "skills");
|
|
6768
7175
|
} catch {
|
|
6769
7176
|
return void 0;
|
|
6770
7177
|
}
|
|
@@ -6917,6 +7324,7 @@ async function boot(argv) {
|
|
|
6917
7324
|
init_sdd();
|
|
6918
7325
|
async function runRepl(opts) {
|
|
6919
7326
|
if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
|
|
7327
|
+
await renderGoalBanner(opts);
|
|
6920
7328
|
let activeCtrl;
|
|
6921
7329
|
let interrupts = 0;
|
|
6922
7330
|
const onSigint = () => {
|
|
@@ -6925,6 +7333,12 @@ async function runRepl(opts) {
|
|
|
6925
7333
|
opts.renderer.writeWarning("Exiting.");
|
|
6926
7334
|
process.exit(130);
|
|
6927
7335
|
}
|
|
7336
|
+
const engine = opts.getEternalEngine?.();
|
|
7337
|
+
if (engine && opts.getAutonomy?.() === "eternal") {
|
|
7338
|
+
engine.stop();
|
|
7339
|
+
opts.renderer.writeWarning("Eternal mode stop requested. Press Ctrl+C again to exit.");
|
|
7340
|
+
return;
|
|
7341
|
+
}
|
|
6928
7342
|
if (activeCtrl) {
|
|
6929
7343
|
activeCtrl.abort();
|
|
6930
7344
|
opts.renderer.writeWarning("Iteration cancelled. Press Ctrl+C again to exit.");
|
|
@@ -6936,6 +7350,42 @@ async function runRepl(opts) {
|
|
|
6936
7350
|
const builder = new InputBuilder({ store: opts.attachments });
|
|
6937
7351
|
try {
|
|
6938
7352
|
for (; ; ) {
|
|
7353
|
+
if (opts.getAutonomy?.() === "eternal") {
|
|
7354
|
+
const engine = opts.getEternalEngine?.();
|
|
7355
|
+
if (!engine) {
|
|
7356
|
+
opts.renderer.writeWarning("Eternal mode set but no engine wired \u2014 falling back to off.");
|
|
7357
|
+
} else {
|
|
7358
|
+
const beforeGoal = await loadGoalSafe(opts);
|
|
7359
|
+
const beforeIter = beforeGoal?.iterations ?? 0;
|
|
7360
|
+
opts.renderer.write(
|
|
7361
|
+
color.dim(`
|
|
7362
|
+
\u21B3 [eternal #${beforeIter + 1}] running iteration\u2026
|
|
7363
|
+
`)
|
|
7364
|
+
);
|
|
7365
|
+
interrupts = 0;
|
|
7366
|
+
try {
|
|
7367
|
+
const ok = await engine.runOneIteration();
|
|
7368
|
+
const afterGoal = await loadGoalSafe(opts);
|
|
7369
|
+
const last = afterGoal?.journal[afterGoal.journal.length - 1];
|
|
7370
|
+
if (!ok && !last) {
|
|
7371
|
+
opts.renderer.write(color.dim(" \u21B3 [eternal] iteration produced no progress.\n"));
|
|
7372
|
+
} else if (last) {
|
|
7373
|
+
const mark = last.status === "success" ? color.green("\u2713") : last.status === "failure" ? color.red("\u2717") : color.amber("\u2298");
|
|
7374
|
+
const tail = last.note ? color.dim(` \u2014 ${last.note.slice(0, 80)}`) : "";
|
|
7375
|
+
opts.renderer.write(
|
|
7376
|
+
` ${mark} ${color.dim(`#${last.iteration}`)} ${color.dim(`[${last.source}]`)} ${last.task}${tail}
|
|
7377
|
+
`
|
|
7378
|
+
);
|
|
7379
|
+
}
|
|
7380
|
+
} catch (err) {
|
|
7381
|
+
opts.renderer.writeError(
|
|
7382
|
+
`[eternal] ${err instanceof Error ? err.message : String(err)}`
|
|
7383
|
+
);
|
|
7384
|
+
}
|
|
7385
|
+
await new Promise((resolve4) => setTimeout(resolve4, 250));
|
|
7386
|
+
continue;
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
6939
7389
|
let raw;
|
|
6940
7390
|
try {
|
|
6941
7391
|
raw = await readPossiblyMultiline(opts);
|
|
@@ -6948,6 +7398,10 @@ async function runRepl(opts) {
|
|
|
6948
7398
|
continue;
|
|
6949
7399
|
}
|
|
6950
7400
|
interrupts = 0;
|
|
7401
|
+
if (trimmed === "q") {
|
|
7402
|
+
opts.renderer.write(color.dim(" Goodbye!\n"));
|
|
7403
|
+
break;
|
|
7404
|
+
}
|
|
6951
7405
|
if (trimmed === "/image" || trimmed === "/paste-image" || raw === "\x1Bv") {
|
|
6952
7406
|
await pasteClipboardImage(builder, opts);
|
|
6953
7407
|
continue;
|
|
@@ -7219,6 +7673,48 @@ async function pasteClipboardImage(builder, opts) {
|
|
|
7219
7673
|
);
|
|
7220
7674
|
}
|
|
7221
7675
|
}
|
|
7676
|
+
async function loadGoalSafe(opts) {
|
|
7677
|
+
if (!opts.projectRoot) return null;
|
|
7678
|
+
try {
|
|
7679
|
+
return await loadGoal(goalFilePath(opts.projectRoot));
|
|
7680
|
+
} catch {
|
|
7681
|
+
return null;
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
async function renderGoalBanner(opts) {
|
|
7685
|
+
const goal = await loadGoalSafe(opts);
|
|
7686
|
+
if (!goal) return;
|
|
7687
|
+
const summary = goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal;
|
|
7688
|
+
opts.renderer.write(
|
|
7689
|
+
color.dim("Goal: ") + color.bold(summary) + color.dim(` (iter ${goal.iterations})`) + "\n"
|
|
7690
|
+
);
|
|
7691
|
+
if (goal.engineState === "running") {
|
|
7692
|
+
opts.renderer.write(
|
|
7693
|
+
color.amber(" \u21BA Eternal engine was running when last session ended.") + "\n"
|
|
7694
|
+
);
|
|
7695
|
+
try {
|
|
7696
|
+
const answer = (await opts.reader.readLine(color.dim(" Resume eternal mode? [y/N] "))).trim().toLowerCase();
|
|
7697
|
+
if (answer === "y" || answer === "yes") {
|
|
7698
|
+
try {
|
|
7699
|
+
await opts.slashRegistry.dispatch("/autonomy eternal", opts.agent.ctx);
|
|
7700
|
+
} catch (err) {
|
|
7701
|
+
opts.renderer.writeError(
|
|
7702
|
+
`Auto-resume failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7703
|
+
);
|
|
7704
|
+
}
|
|
7705
|
+
} else {
|
|
7706
|
+
opts.renderer.write(
|
|
7707
|
+
color.dim(" Not resuming. Use `/autonomy eternal` later to continue.") + "\n"
|
|
7708
|
+
);
|
|
7709
|
+
}
|
|
7710
|
+
} catch {
|
|
7711
|
+
opts.renderer.write(
|
|
7712
|
+
color.dim(" Use `/autonomy eternal` to resume.") + "\n"
|
|
7713
|
+
);
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
7716
|
+
opts.renderer.write("\n");
|
|
7717
|
+
}
|
|
7222
7718
|
async function readPossiblyMultiline(opts) {
|
|
7223
7719
|
const firstPrompt = theme2.primary("\u203A ");
|
|
7224
7720
|
const contPrompt = color.dim("\xB7 ");
|
|
@@ -7266,7 +7762,7 @@ function printBanner(renderer, projectName) {
|
|
|
7266
7762
|
if (projectName && projectName.length > 0) {
|
|
7267
7763
|
lines.push(color.dim("Project: ") + theme2.bold(projectName));
|
|
7268
7764
|
}
|
|
7269
|
-
lines.push(color.dim("Type /help for commands, /exit to quit."), "");
|
|
7765
|
+
lines.push(color.dim("Type /help for commands, /exit or q to quit."), "");
|
|
7270
7766
|
renderer.write(`${lines.join("\n")}
|
|
7271
7767
|
`);
|
|
7272
7768
|
}
|
|
@@ -7302,8 +7798,12 @@ async function execute(deps) {
|
|
|
7302
7798
|
director,
|
|
7303
7799
|
fleetRoster,
|
|
7304
7800
|
fleetStreamController,
|
|
7801
|
+
statuslineHiddenItems,
|
|
7802
|
+
setStatuslineHiddenItems,
|
|
7305
7803
|
getYolo,
|
|
7306
7804
|
getAutonomy,
|
|
7805
|
+
getEternalEngine,
|
|
7806
|
+
subscribeEternalIteration,
|
|
7307
7807
|
skillLoader
|
|
7308
7808
|
} = deps;
|
|
7309
7809
|
let code = 0;
|
|
@@ -7411,6 +7911,9 @@ async function execute(deps) {
|
|
|
7411
7911
|
queueStore,
|
|
7412
7912
|
yolo: !!config.yolo,
|
|
7413
7913
|
getYolo,
|
|
7914
|
+
getAutonomy,
|
|
7915
|
+
getEternalEngine,
|
|
7916
|
+
subscribeEternalIteration,
|
|
7414
7917
|
appVersion: CLI_VERSION,
|
|
7415
7918
|
provider: config.provider,
|
|
7416
7919
|
family: banneredFamily,
|
|
@@ -7436,6 +7939,8 @@ async function execute(deps) {
|
|
|
7436
7939
|
dispatch({ type: "resetContextChip" });
|
|
7437
7940
|
},
|
|
7438
7941
|
fleetStreamController,
|
|
7942
|
+
statuslineHiddenItems,
|
|
7943
|
+
setStatuslineHiddenItems,
|
|
7439
7944
|
initialGoal: goalFlag,
|
|
7440
7945
|
initialAsk: askFlag,
|
|
7441
7946
|
getSDDContext: () => {
|
|
@@ -7479,7 +7984,8 @@ async function execute(deps) {
|
|
|
7479
7984
|
session,
|
|
7480
7985
|
port: Number.parseInt(String(flags.port ?? "3457"), 10),
|
|
7481
7986
|
modelsRegistry,
|
|
7482
|
-
globalConfigPath: wpaths.globalConfig
|
|
7987
|
+
globalConfigPath: wpaths.globalConfig,
|
|
7988
|
+
subscribeEternalIteration
|
|
7483
7989
|
});
|
|
7484
7990
|
try {
|
|
7485
7991
|
code = await runRepl({
|
|
@@ -7492,8 +7998,10 @@ async function execute(deps) {
|
|
|
7492
7998
|
supportsVision,
|
|
7493
7999
|
attachments,
|
|
7494
8000
|
effectiveMaxContext,
|
|
7495
|
-
projectName:
|
|
8001
|
+
projectName: path23.basename(projectRoot) || void 0,
|
|
8002
|
+
projectRoot,
|
|
7496
8003
|
getAutonomy,
|
|
8004
|
+
getEternalEngine,
|
|
7497
8005
|
skillLoader
|
|
7498
8006
|
});
|
|
7499
8007
|
} finally {
|
|
@@ -7510,7 +8018,7 @@ async function execute(deps) {
|
|
|
7510
8018
|
supportsVision,
|
|
7511
8019
|
attachments,
|
|
7512
8020
|
effectiveMaxContext,
|
|
7513
|
-
projectName:
|
|
8021
|
+
projectName: path23.basename(projectRoot) || void 0,
|
|
7514
8022
|
getAutonomy,
|
|
7515
8023
|
skillLoader
|
|
7516
8024
|
});
|
|
@@ -7601,7 +8109,7 @@ var MultiAgentHost = class {
|
|
|
7601
8109
|
doneCondition: { type: "all_tasks_done" },
|
|
7602
8110
|
maxConcurrent: 8
|
|
7603
8111
|
};
|
|
7604
|
-
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ?
|
|
8112
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path23.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
7605
8113
|
this.director = new Director({
|
|
7606
8114
|
config: coordinatorConfig,
|
|
7607
8115
|
manifestPath: this.opts.manifestPath,
|
|
@@ -7687,6 +8195,16 @@ var MultiAgentHost = class {
|
|
|
7687
8195
|
model: subCfg.model ?? config.model,
|
|
7688
8196
|
tools: this.filterTools(subCfg.tools)
|
|
7689
8197
|
});
|
|
8198
|
+
const toolExecutor = new ToolExecutor(this.subagentToolRegistry(subCfg.tools), {
|
|
8199
|
+
permissionPolicy: new AutoApprovePermissionPolicy(),
|
|
8200
|
+
secretScrubber: this.deps.secretScrubber,
|
|
8201
|
+
renderer: this.deps.renderer,
|
|
8202
|
+
events,
|
|
8203
|
+
confirmAwaiter: void 0,
|
|
8204
|
+
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
|
|
8205
|
+
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
|
|
8206
|
+
tracer: void 0
|
|
8207
|
+
});
|
|
7690
8208
|
const agent = new Agent({
|
|
7691
8209
|
container: this.deps.container,
|
|
7692
8210
|
tools: this.subagentToolRegistry(subCfg.tools),
|
|
@@ -7698,7 +8216,8 @@ var MultiAgentHost = class {
|
|
|
7698
8216
|
// run under a director, not the user. Auto-approve everything
|
|
7699
8217
|
// (except tool-level hard denies); the user already authorized
|
|
7700
8218
|
// the work when they invoked the leader.
|
|
7701
|
-
permissionPolicy: new AutoApprovePermissionPolicy()
|
|
8219
|
+
permissionPolicy: new AutoApprovePermissionPolicy(),
|
|
8220
|
+
toolExecutor
|
|
7702
8221
|
});
|
|
7703
8222
|
const hostEvents = this.deps.events;
|
|
7704
8223
|
const offToolBridge = events.on("tool.executed", (e) => {
|
|
@@ -7774,7 +8293,7 @@ var MultiAgentHost = class {
|
|
|
7774
8293
|
model: opts?.model,
|
|
7775
8294
|
tools: opts?.tools
|
|
7776
8295
|
};
|
|
7777
|
-
const transcriptPath = this.sessionFactory ?
|
|
8296
|
+
const transcriptPath = this.sessionFactory ? path23.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
7778
8297
|
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
|
|
7779
8298
|
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
7780
8299
|
this.deps.events.emit("subagent.spawned", {
|
|
@@ -7917,16 +8436,16 @@ var MultiAgentHost = class {
|
|
|
7917
8436
|
if (this.director) return this.director;
|
|
7918
8437
|
this.opts.directorMode = true;
|
|
7919
8438
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
7920
|
-
this.opts.manifestPath =
|
|
8439
|
+
this.opts.manifestPath = path23.join(this.opts.fleetRoot, "fleet.json");
|
|
7921
8440
|
}
|
|
7922
8441
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
7923
|
-
this.opts.sharedScratchpadPath =
|
|
8442
|
+
this.opts.sharedScratchpadPath = path23.join(this.opts.fleetRoot, "shared");
|
|
7924
8443
|
}
|
|
7925
8444
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
7926
|
-
this.opts.sessionsRoot =
|
|
8445
|
+
this.opts.sessionsRoot = path23.join(this.opts.fleetRoot, "subagents");
|
|
7927
8446
|
}
|
|
7928
8447
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
7929
|
-
this.opts.stateCheckpointPath =
|
|
8448
|
+
this.opts.stateCheckpointPath = path23.join(this.opts.fleetRoot, "director-state.json");
|
|
7930
8449
|
}
|
|
7931
8450
|
await this.ensureDirector();
|
|
7932
8451
|
return this.director ?? null;
|
|
@@ -8047,11 +8566,11 @@ var SessionStats = class {
|
|
|
8047
8566
|
if (e.name === "bash") this.bashCommands++;
|
|
8048
8567
|
else if (e.name === "fetch") this.fetches++;
|
|
8049
8568
|
if (!e.ok) return;
|
|
8050
|
-
const
|
|
8051
|
-
if (e.name === "read" &&
|
|
8052
|
-
else if (e.name === "edit" &&
|
|
8053
|
-
else if (e.name === "write" &&
|
|
8054
|
-
this.writtenPaths.add(
|
|
8569
|
+
const path24 = typeof input?.path === "string" ? input.path : void 0;
|
|
8570
|
+
if (e.name === "read" && path24) this.readPaths.add(path24);
|
|
8571
|
+
else if (e.name === "edit" && path24) this.editedPaths.add(path24);
|
|
8572
|
+
else if (e.name === "write" && path24) {
|
|
8573
|
+
this.writtenPaths.add(path24);
|
|
8055
8574
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
8056
8575
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
8057
8576
|
}
|
|
@@ -8277,6 +8796,19 @@ async function setupCompaction(params) {
|
|
|
8277
8796
|
return { effectiveMaxContext, autoCompactor };
|
|
8278
8797
|
}
|
|
8279
8798
|
function createAgent(params) {
|
|
8799
|
+
const secretScrubber = params.container.resolve(TOKENS.SecretScrubber);
|
|
8800
|
+
const renderer = params.container.has(TOKENS.Renderer) ? params.container.resolve(TOKENS.Renderer) : void 0;
|
|
8801
|
+
params.container.resolve(TOKENS.Logger);
|
|
8802
|
+
const toolExecutor = new ToolExecutor(params.tools, {
|
|
8803
|
+
permissionPolicy: params.permissionPolicy ?? params.container.resolve(TOKENS.PermissionPolicy),
|
|
8804
|
+
secretScrubber,
|
|
8805
|
+
renderer,
|
|
8806
|
+
events: params.events,
|
|
8807
|
+
confirmAwaiter: params.confirmAwaiter,
|
|
8808
|
+
iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
|
|
8809
|
+
perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
|
|
8810
|
+
tracer: params.tracer
|
|
8811
|
+
});
|
|
8280
8812
|
return new Agent({
|
|
8281
8813
|
container: params.container,
|
|
8282
8814
|
tools: params.tools,
|
|
@@ -8288,8 +8820,143 @@ function createAgent(params) {
|
|
|
8288
8820
|
iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
|
|
8289
8821
|
executionStrategy: params.config.tools.defaultExecutionStrategy,
|
|
8290
8822
|
perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
|
|
8291
|
-
confirmAwaiter: params.confirmAwaiter
|
|
8823
|
+
confirmAwaiter: params.confirmAwaiter,
|
|
8824
|
+
toolExecutor,
|
|
8825
|
+
tracer: params.tracer
|
|
8826
|
+
});
|
|
8827
|
+
}
|
|
8828
|
+
function setupMetrics(params) {
|
|
8829
|
+
const { flags, wpaths, events, logger, config } = params;
|
|
8830
|
+
let metricsSink;
|
|
8831
|
+
let healthRegistry;
|
|
8832
|
+
let metricsServerHandle;
|
|
8833
|
+
const metricsPortFlag = flags["metrics-port"];
|
|
8834
|
+
const metricsPort = typeof metricsPortFlag === "string" && metricsPortFlag.length > 0 ? Number.parseInt(metricsPortFlag, 10) : void 0;
|
|
8835
|
+
if (metricsPort !== void 0 && !flags.metrics) flags.metrics = true;
|
|
8836
|
+
if (!flags.metrics) return { metricsSink, healthRegistry, metricsServerHandle };
|
|
8837
|
+
metricsSink = new InMemoryMetricsSink();
|
|
8838
|
+
wireMetricsToEvents(events, metricsSink);
|
|
8839
|
+
healthRegistry = new DefaultHealthRegistry();
|
|
8840
|
+
healthRegistry.register({
|
|
8841
|
+
name: "session-store",
|
|
8842
|
+
check: async () => {
|
|
8843
|
+
try {
|
|
8844
|
+
await fsp2.access(wpaths.projectSessions);
|
|
8845
|
+
return { status: "healthy" };
|
|
8846
|
+
} catch (e) {
|
|
8847
|
+
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8292
8850
|
});
|
|
8851
|
+
healthRegistry.register({
|
|
8852
|
+
name: "provider",
|
|
8853
|
+
check: async () => ({
|
|
8854
|
+
status: "healthy",
|
|
8855
|
+
data: { id: config.provider, model: config.model }
|
|
8856
|
+
})
|
|
8857
|
+
});
|
|
8858
|
+
const dumpMetrics = () => {
|
|
8859
|
+
if (!metricsSink) return;
|
|
8860
|
+
try {
|
|
8861
|
+
const out = path23.join(wpaths.projectSessions, "metrics.json");
|
|
8862
|
+
const snap = metricsSink.snapshot();
|
|
8863
|
+
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
8864
|
+
} catch {
|
|
8865
|
+
}
|
|
8866
|
+
};
|
|
8867
|
+
process.on("exit", dumpMetrics);
|
|
8868
|
+
if (metricsPort !== void 0 && Number.isFinite(metricsPort)) {
|
|
8869
|
+
try {
|
|
8870
|
+
metricsServerHandle = startMetricsServer({
|
|
8871
|
+
port: metricsPort,
|
|
8872
|
+
host: process.env["METRICS_HOST"] ?? "127.0.0.1",
|
|
8873
|
+
sink: metricsSink,
|
|
8874
|
+
healthRegistry
|
|
8875
|
+
});
|
|
8876
|
+
logger.info(
|
|
8877
|
+
`metrics endpoint listening on ${metricsServerHandle.url} (healthz on same port)`
|
|
8878
|
+
);
|
|
8879
|
+
process.on("exit", () => {
|
|
8880
|
+
void metricsServerHandle?.close().catch(() => {
|
|
8881
|
+
});
|
|
8882
|
+
});
|
|
8883
|
+
} catch (err) {
|
|
8884
|
+
logger.warn(
|
|
8885
|
+
`metrics endpoint failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
8886
|
+
);
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
return { metricsSink, healthRegistry, metricsServerHandle };
|
|
8890
|
+
}
|
|
8891
|
+
function createApi(ownerName, base) {
|
|
8892
|
+
return new DefaultPluginAPI({ ownerName, ...base });
|
|
8893
|
+
}
|
|
8894
|
+
|
|
8895
|
+
// src/wiring/plugins.ts
|
|
8896
|
+
async function setupPlugins(params) {
|
|
8897
|
+
const {
|
|
8898
|
+
config,
|
|
8899
|
+
container,
|
|
8900
|
+
events,
|
|
8901
|
+
toolRegistry,
|
|
8902
|
+
providerRegistry,
|
|
8903
|
+
slashCommandRegistry,
|
|
8904
|
+
mcpRegistry,
|
|
8905
|
+
log,
|
|
8906
|
+
agent,
|
|
8907
|
+
sessionWriter,
|
|
8908
|
+
metricsSink,
|
|
8909
|
+
configStore,
|
|
8910
|
+
pipelines
|
|
8911
|
+
} = params;
|
|
8912
|
+
if (!config.features.plugins || !config.plugins || config.plugins.length === 0) return;
|
|
8913
|
+
const resolvedPlugins = [];
|
|
8914
|
+
for (const p of config.plugins) {
|
|
8915
|
+
if (typeof p === "object" && p.enabled === false) continue;
|
|
8916
|
+
const spec = typeof p === "string" ? p : p.name;
|
|
8917
|
+
try {
|
|
8918
|
+
const mod = await import(spec);
|
|
8919
|
+
if (mod.default) resolvedPlugins.push(mod.default);
|
|
8920
|
+
} catch (err) {
|
|
8921
|
+
log.warn(`Plugin "${spec}" failed to load`, err);
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
if (resolvedPlugins.length === 0) return;
|
|
8925
|
+
const pluginOptions = buildPluginOptions(config);
|
|
8926
|
+
const pluginConfig = Object.keys(pluginOptions).length > 0 ? patchConfig(config, { extensions: pluginOptions }) : config;
|
|
8927
|
+
await loadPlugins(resolvedPlugins, {
|
|
8928
|
+
log,
|
|
8929
|
+
pluginOptions,
|
|
8930
|
+
apiFactory: (plugin) => createApi(plugin.name, {
|
|
8931
|
+
container,
|
|
8932
|
+
events,
|
|
8933
|
+
pipelines,
|
|
8934
|
+
toolRegistry,
|
|
8935
|
+
providerRegistry,
|
|
8936
|
+
slashCommandRegistry,
|
|
8937
|
+
mcpRegistry,
|
|
8938
|
+
config: pluginConfig,
|
|
8939
|
+
log,
|
|
8940
|
+
extensions: agent.extensions,
|
|
8941
|
+
sessionWriter: {
|
|
8942
|
+
transcriptPath: sessionWriter.transcriptPath,
|
|
8943
|
+
append: (e) => sessionWriter.append(e)
|
|
8944
|
+
},
|
|
8945
|
+
metricsSink,
|
|
8946
|
+
configStore
|
|
8947
|
+
})
|
|
8948
|
+
});
|
|
8949
|
+
}
|
|
8950
|
+
function buildPluginOptions(config) {
|
|
8951
|
+
const options = {};
|
|
8952
|
+
for (const entry of config.plugins ?? []) {
|
|
8953
|
+
if (typeof entry !== "object") continue;
|
|
8954
|
+
if (entry.options) options[entry.name] = { ...entry.options };
|
|
8955
|
+
}
|
|
8956
|
+
for (const [name, value] of Object.entries(config.extensions ?? {})) {
|
|
8957
|
+
options[name] = { ...options[name] ?? {}, ...value };
|
|
8958
|
+
}
|
|
8959
|
+
return options;
|
|
8293
8960
|
}
|
|
8294
8961
|
async function setupProvider(params) {
|
|
8295
8962
|
const { config, modelsRegistry, logger } = params;
|
|
@@ -8382,12 +9049,12 @@ async function setupSession(params) {
|
|
|
8382
9049
|
}
|
|
8383
9050
|
const sessionRef = { current: session };
|
|
8384
9051
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
8385
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
8386
|
-
const queueStore = new QueueStore({ dir:
|
|
9052
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path23.join(wpaths.projectSessions, session.id, "attachments") });
|
|
9053
|
+
const queueStore = new QueueStore({ dir: path23.join(wpaths.projectSessions, session.id) });
|
|
8387
9054
|
const ctxSignal = new AbortController().signal;
|
|
8388
9055
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
8389
9056
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
8390
|
-
const todosCheckpointPath =
|
|
9057
|
+
const todosCheckpointPath = path23.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
8391
9058
|
if (resumeId) {
|
|
8392
9059
|
try {
|
|
8393
9060
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -8399,13 +9066,13 @@ async function setupSession(params) {
|
|
|
8399
9066
|
}
|
|
8400
9067
|
}
|
|
8401
9068
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
8402
|
-
const planPath =
|
|
9069
|
+
const planPath = path23.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
8403
9070
|
context.state.setMeta("plan.path", planPath);
|
|
8404
9071
|
let dirState;
|
|
8405
9072
|
if (resumeId) {
|
|
8406
9073
|
try {
|
|
8407
|
-
const fleetRoot =
|
|
8408
|
-
dirState = await loadDirectorState(
|
|
9074
|
+
const fleetRoot = path23.join(wpaths.projectSessions, session.id);
|
|
9075
|
+
dirState = await loadDirectorState(path23.join(fleetRoot, "director-state.json"));
|
|
8409
9076
|
if (dirState) {
|
|
8410
9077
|
const tCounts = {};
|
|
8411
9078
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -8432,22 +9099,11 @@ function resolveBundledSkillsDir2() {
|
|
|
8432
9099
|
try {
|
|
8433
9100
|
const req2 = createRequire(import.meta.url);
|
|
8434
9101
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
8435
|
-
return
|
|
9102
|
+
return path23.join(path23.dirname(corePkg), "skills");
|
|
8436
9103
|
} catch {
|
|
8437
9104
|
return void 0;
|
|
8438
9105
|
}
|
|
8439
9106
|
}
|
|
8440
|
-
function buildPluginOptions(config) {
|
|
8441
|
-
const options = {};
|
|
8442
|
-
for (const entry of config.plugins ?? []) {
|
|
8443
|
-
if (typeof entry !== "object") continue;
|
|
8444
|
-
if (entry.options) options[entry.name] = { ...entry.options };
|
|
8445
|
-
}
|
|
8446
|
-
for (const [name, value] of Object.entries(config.extensions ?? {})) {
|
|
8447
|
-
options[name] = { ...options[name] ?? {}, ...value };
|
|
8448
|
-
}
|
|
8449
|
-
return options;
|
|
8450
|
-
}
|
|
8451
9107
|
async function main(argv) {
|
|
8452
9108
|
const ctx = await boot(argv);
|
|
8453
9109
|
if (typeof ctx === "number") return ctx;
|
|
@@ -8539,7 +9195,7 @@ async function main(argv) {
|
|
|
8539
9195
|
modeId,
|
|
8540
9196
|
modePrompt,
|
|
8541
9197
|
modelCapabilities,
|
|
8542
|
-
planPath: () => sessionRef.current ?
|
|
9198
|
+
planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
8543
9199
|
})
|
|
8544
9200
|
);
|
|
8545
9201
|
const toolRegistry = new ToolRegistry();
|
|
@@ -8553,72 +9209,10 @@ async function main(argv) {
|
|
|
8553
9209
|
}
|
|
8554
9210
|
const events = new EventBus();
|
|
8555
9211
|
events.setLogger(logger);
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
const metricsPort = typeof metricsPortFlag === "string" && metricsPortFlag.length > 0 ? Number.parseInt(metricsPortFlag, 10) : void 0;
|
|
8561
|
-
if (metricsPort !== void 0 && !flags.metrics) flags.metrics = true;
|
|
8562
|
-
if (flags.metrics) {
|
|
8563
|
-
metricsSink = new InMemoryMetricsSink();
|
|
8564
|
-
wireMetricsToEvents(events, metricsSink);
|
|
8565
|
-
healthRegistry = new DefaultHealthRegistry();
|
|
8566
|
-
healthRegistry.register({
|
|
8567
|
-
name: "session-store",
|
|
8568
|
-
check: async () => {
|
|
8569
|
-
try {
|
|
8570
|
-
await fsp2.access(wpaths.projectSessions);
|
|
8571
|
-
return { status: "healthy" };
|
|
8572
|
-
} catch (e) {
|
|
8573
|
-
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
8574
|
-
}
|
|
8575
|
-
}
|
|
8576
|
-
});
|
|
8577
|
-
healthRegistry.register({
|
|
8578
|
-
name: "provider",
|
|
8579
|
-
check: async () => ({
|
|
8580
|
-
status: "healthy",
|
|
8581
|
-
data: { id: config.provider, model: config.model }
|
|
8582
|
-
})
|
|
8583
|
-
});
|
|
8584
|
-
const dumpMetrics = () => {
|
|
8585
|
-
if (!metricsSink) return;
|
|
8586
|
-
try {
|
|
8587
|
-
const out = path21.join(wpaths.projectSessions, "metrics.json");
|
|
8588
|
-
const snap = metricsSink.snapshot();
|
|
8589
|
-
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
8590
|
-
} catch {
|
|
8591
|
-
}
|
|
8592
|
-
};
|
|
8593
|
-
process.on("exit", dumpMetrics);
|
|
8594
|
-
process.on("SIGINT", () => {
|
|
8595
|
-
dumpMetrics();
|
|
8596
|
-
process.exit(130);
|
|
8597
|
-
});
|
|
8598
|
-
if (metricsPort !== void 0 && Number.isFinite(metricsPort)) {
|
|
8599
|
-
try {
|
|
8600
|
-
metricsServerHandle = await startMetricsServer({
|
|
8601
|
-
port: metricsPort,
|
|
8602
|
-
host: process.env.METRICS_HOST ?? "127.0.0.1",
|
|
8603
|
-
sink: metricsSink,
|
|
8604
|
-
// V2-C: mount /healthz on the same listener so k8s probes can
|
|
8605
|
-
// hit one endpoint per pod for both observability and liveness.
|
|
8606
|
-
healthRegistry
|
|
8607
|
-
});
|
|
8608
|
-
logger.info(
|
|
8609
|
-
`metrics endpoint listening on ${metricsServerHandle.url} (healthz on same port)`
|
|
8610
|
-
);
|
|
8611
|
-
process.on("exit", () => {
|
|
8612
|
-
void metricsServerHandle?.close().catch(() => {
|
|
8613
|
-
});
|
|
8614
|
-
});
|
|
8615
|
-
} catch (err) {
|
|
8616
|
-
logger.warn(
|
|
8617
|
-
`metrics endpoint failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
8618
|
-
);
|
|
8619
|
-
}
|
|
8620
|
-
}
|
|
8621
|
-
}
|
|
9212
|
+
const { metricsSink, healthRegistry, metricsServerHandle } = (() => {
|
|
9213
|
+
const ms = setupMetrics({ flags, wpaths, events, logger, config: { provider: config.provider, model: config.model } });
|
|
9214
|
+
return ms;
|
|
9215
|
+
})();
|
|
8622
9216
|
const spinner = new Spinner();
|
|
8623
9217
|
let lastInputTokens = 0;
|
|
8624
9218
|
events.on("provider.response", (e) => {
|
|
@@ -8737,49 +9331,21 @@ async function main(argv) {
|
|
|
8737
9331
|
}
|
|
8738
9332
|
}
|
|
8739
9333
|
const slashRegistry = new SlashCommandRegistry();
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
const pluginConfig = Object.keys(pluginOptions).length > 0 ? patchConfig(config, { extensions: pluginOptions }) : config;
|
|
8756
|
-
await loadPlugins(resolvedPlugins, {
|
|
8757
|
-
log: logger,
|
|
8758
|
-
// Each plugin's `configSchema` is validated against merged
|
|
8759
|
-
// options from `plugins[].options` and `extensions[name]`.
|
|
8760
|
-
// The merged view is also exposed as `api.config.extensions`.
|
|
8761
|
-
pluginOptions,
|
|
8762
|
-
apiFactory: (plugin) => createApi2(plugin.name, {
|
|
8763
|
-
container,
|
|
8764
|
-
events,
|
|
8765
|
-
pipelines,
|
|
8766
|
-
toolRegistry,
|
|
8767
|
-
providerRegistry,
|
|
8768
|
-
slashCommandRegistry: slashRegistry,
|
|
8769
|
-
mcpRegistry,
|
|
8770
|
-
config: pluginConfig,
|
|
8771
|
-
log: logger,
|
|
8772
|
-
extensions: agent.extensions,
|
|
8773
|
-
sessionWriter: {
|
|
8774
|
-
transcriptPath: context.session.transcriptPath,
|
|
8775
|
-
append: (e) => context.session.append(e)
|
|
8776
|
-
},
|
|
8777
|
-
metricsSink,
|
|
8778
|
-
configStore
|
|
8779
|
-
})
|
|
8780
|
-
});
|
|
8781
|
-
}
|
|
8782
|
-
}
|
|
9334
|
+
await setupPlugins({
|
|
9335
|
+
config,
|
|
9336
|
+
container,
|
|
9337
|
+
events,
|
|
9338
|
+
pipelines,
|
|
9339
|
+
toolRegistry,
|
|
9340
|
+
providerRegistry,
|
|
9341
|
+
slashCommandRegistry: slashRegistry,
|
|
9342
|
+
mcpRegistry,
|
|
9343
|
+
log: logger,
|
|
9344
|
+
agent,
|
|
9345
|
+
sessionWriter: context.session,
|
|
9346
|
+
metricsSink,
|
|
9347
|
+
configStore
|
|
9348
|
+
});
|
|
8783
9349
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
8784
9350
|
try {
|
|
8785
9351
|
const savedCfg = config.providers?.[providerId];
|
|
@@ -8804,12 +9370,22 @@ async function main(argv) {
|
|
|
8804
9370
|
const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
|
|
8805
9371
|
let director = null;
|
|
8806
9372
|
let autonomyMode = "off";
|
|
8807
|
-
|
|
8808
|
-
const
|
|
8809
|
-
const
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
9373
|
+
let eternalEngine = null;
|
|
9374
|
+
const eternalListeners = /* @__PURE__ */ new Set();
|
|
9375
|
+
const broadcastEternalIteration = (entry) => {
|
|
9376
|
+
for (const fn of eternalListeners) {
|
|
9377
|
+
try {
|
|
9378
|
+
fn(entry);
|
|
9379
|
+
} catch {
|
|
9380
|
+
}
|
|
9381
|
+
}
|
|
9382
|
+
};
|
|
9383
|
+
const fleetRoot = directorMode ? path23.join(wpaths.projectSessions, session.id) : void 0;
|
|
9384
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path23.join(fleetRoot, "fleet.json") : void 0;
|
|
9385
|
+
const sharedScratchpadPath = directorMode ? path23.join(fleetRoot, "shared") : void 0;
|
|
9386
|
+
const subagentSessionsRoot = directorMode ? path23.join(fleetRoot, "subagents") : void 0;
|
|
9387
|
+
const stateCheckpointPath = directorMode ? path23.join(fleetRoot, "director-state.json") : void 0;
|
|
9388
|
+
const fleetRootForPromotion = path23.join(wpaths.projectSessions, session.id);
|
|
8813
9389
|
const multiAgentHost = new MultiAgentHost(
|
|
8814
9390
|
{
|
|
8815
9391
|
container,
|
|
@@ -8821,7 +9397,8 @@ async function main(argv) {
|
|
|
8821
9397
|
session,
|
|
8822
9398
|
tokenCounter,
|
|
8823
9399
|
projectRoot,
|
|
8824
|
-
cwd
|
|
9400
|
+
cwd,
|
|
9401
|
+
secretScrubber: container.resolve(TOKENS.SecretScrubber)
|
|
8825
9402
|
},
|
|
8826
9403
|
{
|
|
8827
9404
|
directorMode,
|
|
@@ -8868,6 +9445,20 @@ async function main(argv) {
|
|
|
8868
9445
|
this.enabled = enabled;
|
|
8869
9446
|
}
|
|
8870
9447
|
};
|
|
9448
|
+
const statuslineConfigDeps = {
|
|
9449
|
+
get: () => loadStatuslineConfig(),
|
|
9450
|
+
set: (cfg) => saveStatuslineConfig(cfg)
|
|
9451
|
+
};
|
|
9452
|
+
const hiddenItemsFromConfig = await loadStatuslineConfig();
|
|
9453
|
+
const hiddenItemsList = [];
|
|
9454
|
+
const ALL_ITEMS = ["todos", "plan", "fleet", "git", "elapsed", "context", "cost"];
|
|
9455
|
+
for (const k of ALL_ITEMS) {
|
|
9456
|
+
if (!hiddenItemsFromConfig[k]) hiddenItemsList.push(k);
|
|
9457
|
+
}
|
|
9458
|
+
const statuslineHiddenItems = hiddenItemsList;
|
|
9459
|
+
[...statuslineHiddenItems];
|
|
9460
|
+
const setStatuslineHiddenItems = (items) => {
|
|
9461
|
+
};
|
|
8871
9462
|
const slashCmds = buildBuiltinSlashCommands({
|
|
8872
9463
|
registry: slashRegistry,
|
|
8873
9464
|
toolRegistry,
|
|
@@ -8887,6 +9478,7 @@ async function main(argv) {
|
|
|
8887
9478
|
fleetStreamController,
|
|
8888
9479
|
llmProvider: provider,
|
|
8889
9480
|
llmModel: config.model,
|
|
9481
|
+
statuslineConfig: statuslineConfigDeps,
|
|
8890
9482
|
onSpawn: async (description, spawnOpts) => {
|
|
8891
9483
|
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
8892
9484
|
const tags = [];
|
|
@@ -8993,7 +9585,7 @@ async function main(argv) {
|
|
|
8993
9585
|
return `Unknown fleet action: ${action}`;
|
|
8994
9586
|
},
|
|
8995
9587
|
onFleetLog: async (subagentId, mode) => {
|
|
8996
|
-
const subagentsRoot =
|
|
9588
|
+
const subagentsRoot = path23.join(fleetRootForPromotion, "subagents");
|
|
8997
9589
|
let runDirs;
|
|
8998
9590
|
try {
|
|
8999
9591
|
runDirs = await fsp2.readdir(subagentsRoot);
|
|
@@ -9002,7 +9594,7 @@ async function main(argv) {
|
|
|
9002
9594
|
}
|
|
9003
9595
|
const found = [];
|
|
9004
9596
|
for (const runId of runDirs) {
|
|
9005
|
-
const runDir =
|
|
9597
|
+
const runDir = path23.join(subagentsRoot, runId);
|
|
9006
9598
|
let files;
|
|
9007
9599
|
try {
|
|
9008
9600
|
files = await fsp2.readdir(runDir);
|
|
@@ -9011,7 +9603,7 @@ async function main(argv) {
|
|
|
9011
9603
|
}
|
|
9012
9604
|
for (const f of files) {
|
|
9013
9605
|
if (!f.endsWith(".jsonl")) continue;
|
|
9014
|
-
const full =
|
|
9606
|
+
const full = path23.join(runDir, f);
|
|
9015
9607
|
try {
|
|
9016
9608
|
const stat3 = await fsp2.stat(full);
|
|
9017
9609
|
found.push({
|
|
@@ -9108,7 +9700,7 @@ async function main(argv) {
|
|
|
9108
9700
|
}
|
|
9109
9701
|
const dir = await multiAgentHost.ensureDirector();
|
|
9110
9702
|
if (!dir) return "Director is not available.";
|
|
9111
|
-
const dirStatePath =
|
|
9703
|
+
const dirStatePath = path23.join(fleetRootForPromotion, "director-state.json");
|
|
9112
9704
|
const prior = await loadDirectorState(dirStatePath);
|
|
9113
9705
|
if (!prior) {
|
|
9114
9706
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -9179,9 +9771,9 @@ async function main(argv) {
|
|
|
9179
9771
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
9180
9772
|
toolRegistry.register(tool);
|
|
9181
9773
|
}
|
|
9182
|
-
const mp =
|
|
9183
|
-
const sp =
|
|
9184
|
-
const ss =
|
|
9774
|
+
const mp = path23.join(fleetRootForPromotion, "fleet.json");
|
|
9775
|
+
const sp = path23.join(fleetRootForPromotion, "shared");
|
|
9776
|
+
const ss = path23.join(fleetRootForPromotion, "subagents");
|
|
9185
9777
|
const lines = [
|
|
9186
9778
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
9187
9779
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -9224,6 +9816,26 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9224
9816
|
}
|
|
9225
9817
|
return autonomyMode;
|
|
9226
9818
|
},
|
|
9819
|
+
onEternalStart: () => {
|
|
9820
|
+
if (!eternalEngine) {
|
|
9821
|
+
eternalEngine = new EternalAutonomyEngine({
|
|
9822
|
+
agent,
|
|
9823
|
+
projectRoot,
|
|
9824
|
+
// Wire the same compactor the manual /compact command uses so
|
|
9825
|
+
// multi-day eternal loops don't overflow the provider's context.
|
|
9826
|
+
// effectiveMaxContext is set up earlier with a model-specific
|
|
9827
|
+
// value; pass it through so aggressive-mode compact triggers
|
|
9828
|
+
// before the next iteration would actually overflow.
|
|
9829
|
+
compactor: container.resolve(TOKENS.Compactor),
|
|
9830
|
+
maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
|
|
9831
|
+
onIteration: broadcastEternalIteration
|
|
9832
|
+
});
|
|
9833
|
+
}
|
|
9834
|
+
void eternalEngine.prime();
|
|
9835
|
+
},
|
|
9836
|
+
onEternalStop: () => {
|
|
9837
|
+
eternalEngine?.stop();
|
|
9838
|
+
},
|
|
9227
9839
|
onExit: () => {
|
|
9228
9840
|
void mcpRegistry.stopAll();
|
|
9229
9841
|
},
|
|
@@ -9281,6 +9893,29 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9281
9893
|
}
|
|
9282
9894
|
});
|
|
9283
9895
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
9896
|
+
const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";
|
|
9897
|
+
if (eternalFlag.length > 0) {
|
|
9898
|
+
const { saveGoal: saveGoal2, emptyGoal: emptyGoal2, goalFilePath: goalFilePath4, loadGoal: loadGoal4 } = await import('@wrongstack/core');
|
|
9899
|
+
const goalPath = goalFilePath4(projectRoot);
|
|
9900
|
+
const prior = await loadGoal4(goalPath);
|
|
9901
|
+
const next = prior ? { ...prior, goal: eternalFlag, setAt: (/* @__PURE__ */ new Date()).toISOString(), lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() } : emptyGoal2(eternalFlag);
|
|
9902
|
+
await saveGoal2(goalPath, next);
|
|
9903
|
+
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
9904
|
+
policy.setYolo(true);
|
|
9905
|
+
config = patchConfig(config, { yolo: true });
|
|
9906
|
+
eternalEngine = new EternalAutonomyEngine({
|
|
9907
|
+
agent,
|
|
9908
|
+
projectRoot,
|
|
9909
|
+
compactor: container.resolve(TOKENS.Compactor),
|
|
9910
|
+
maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
|
|
9911
|
+
onIteration: broadcastEternalIteration
|
|
9912
|
+
});
|
|
9913
|
+
await eternalEngine.prime();
|
|
9914
|
+
autonomyMode = "eternal";
|
|
9915
|
+
renderer.write(
|
|
9916
|
+
color.red("Eternal mode launching from --eternal flag.") + color.dim(` Goal: ${eternalFlag.slice(0, 80)}${eternalFlag.length > 80 ? "\u2026" : ""}`) + "\n"
|
|
9917
|
+
);
|
|
9918
|
+
}
|
|
9284
9919
|
const savedProviderCfg = config.providers?.[config.provider];
|
|
9285
9920
|
return execute({
|
|
9286
9921
|
agent,
|
|
@@ -9311,11 +9946,18 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9311
9946
|
director: director ?? null,
|
|
9312
9947
|
fleetRoster: FLEET_ROSTER,
|
|
9313
9948
|
fleetStreamController,
|
|
9949
|
+
statuslineHiddenItems,
|
|
9950
|
+
setStatuslineHiddenItems,
|
|
9314
9951
|
getYolo: () => {
|
|
9315
9952
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
9316
9953
|
return policy.getYolo();
|
|
9317
9954
|
},
|
|
9318
9955
|
getAutonomy: () => autonomyMode,
|
|
9956
|
+
getEternalEngine: () => eternalEngine,
|
|
9957
|
+
subscribeEternalIteration: (fn) => {
|
|
9958
|
+
eternalListeners.add(fn);
|
|
9959
|
+
return () => eternalListeners.delete(fn);
|
|
9960
|
+
},
|
|
9319
9961
|
skillLoader: config.features.skills ? skillLoader : void 0
|
|
9320
9962
|
});
|
|
9321
9963
|
}
|