@wrongstack/cli 0.5.6 → 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 +1058 -454
- 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;
|
|
@@ -7339,6 +7839,8 @@ async function execute(deps) {
|
|
|
7339
7839
|
result = await agent.run(query, { signal: ctrl.signal });
|
|
7340
7840
|
} finally {
|
|
7341
7841
|
process.off("SIGINT", onSigint);
|
|
7842
|
+
const { getProcessRegistry } = await import('@wrongstack/tools');
|
|
7843
|
+
getProcessRegistry().killAll();
|
|
7342
7844
|
}
|
|
7343
7845
|
const after = tokenCounter.total();
|
|
7344
7846
|
const costAfter = tokenCounter.estimateCost().total;
|
|
@@ -7409,6 +7911,9 @@ async function execute(deps) {
|
|
|
7409
7911
|
queueStore,
|
|
7410
7912
|
yolo: !!config.yolo,
|
|
7411
7913
|
getYolo,
|
|
7914
|
+
getAutonomy,
|
|
7915
|
+
getEternalEngine,
|
|
7916
|
+
subscribeEternalIteration,
|
|
7412
7917
|
appVersion: CLI_VERSION,
|
|
7413
7918
|
provider: config.provider,
|
|
7414
7919
|
family: banneredFamily,
|
|
@@ -7434,6 +7939,8 @@ async function execute(deps) {
|
|
|
7434
7939
|
dispatch({ type: "resetContextChip" });
|
|
7435
7940
|
},
|
|
7436
7941
|
fleetStreamController,
|
|
7942
|
+
statuslineHiddenItems,
|
|
7943
|
+
setStatuslineHiddenItems,
|
|
7437
7944
|
initialGoal: goalFlag,
|
|
7438
7945
|
initialAsk: askFlag,
|
|
7439
7946
|
getSDDContext: () => {
|
|
@@ -7477,7 +7984,8 @@ async function execute(deps) {
|
|
|
7477
7984
|
session,
|
|
7478
7985
|
port: Number.parseInt(String(flags.port ?? "3457"), 10),
|
|
7479
7986
|
modelsRegistry,
|
|
7480
|
-
globalConfigPath: wpaths.globalConfig
|
|
7987
|
+
globalConfigPath: wpaths.globalConfig,
|
|
7988
|
+
subscribeEternalIteration
|
|
7481
7989
|
});
|
|
7482
7990
|
try {
|
|
7483
7991
|
code = await runRepl({
|
|
@@ -7490,8 +7998,10 @@ async function execute(deps) {
|
|
|
7490
7998
|
supportsVision,
|
|
7491
7999
|
attachments,
|
|
7492
8000
|
effectiveMaxContext,
|
|
7493
|
-
projectName:
|
|
8001
|
+
projectName: path23.basename(projectRoot) || void 0,
|
|
8002
|
+
projectRoot,
|
|
7494
8003
|
getAutonomy,
|
|
8004
|
+
getEternalEngine,
|
|
7495
8005
|
skillLoader
|
|
7496
8006
|
});
|
|
7497
8007
|
} finally {
|
|
@@ -7508,7 +8018,7 @@ async function execute(deps) {
|
|
|
7508
8018
|
supportsVision,
|
|
7509
8019
|
attachments,
|
|
7510
8020
|
effectiveMaxContext,
|
|
7511
|
-
projectName:
|
|
8021
|
+
projectName: path23.basename(projectRoot) || void 0,
|
|
7512
8022
|
getAutonomy,
|
|
7513
8023
|
skillLoader
|
|
7514
8024
|
});
|
|
@@ -7537,22 +8047,18 @@ var MultiAgentHost = class {
|
|
|
7537
8047
|
this.opts = opts;
|
|
7538
8048
|
}
|
|
7539
8049
|
deps;
|
|
7540
|
-
coordinator;
|
|
7541
|
-
/** Lazily built when `opts.directorMode` is set. Owns its own internal
|
|
7542
|
-
* coordinator; the host's `coordinator` field still points at it so
|
|
7543
|
-
* the rest of the methods don't need to branch. */
|
|
7544
8050
|
director;
|
|
8051
|
+
/** Own FleetManager — created in buildDirector(), used for pending task
|
|
8052
|
+
* tracking so status() can show descriptions without host-side state. */
|
|
8053
|
+
fleetManager;
|
|
7545
8054
|
/** Lazily built alongside the director — produces per-subagent JSONL
|
|
7546
|
-
* writers under `<sessionsRoot>/<runId>/`. Null
|
|
8055
|
+
* writers under `<sessionsRoot>/<runId>/`. Null without sessionsRoot. */
|
|
7547
8056
|
sessionFactory;
|
|
7548
|
-
pending = /* @__PURE__ */ new Map();
|
|
7549
|
-
results = [];
|
|
7550
8057
|
opts;
|
|
7551
8058
|
/**
|
|
7552
|
-
* Populated by `promoteToDirector` when it refuses to promote
|
|
7553
|
-
* because a non-director coordinator is already running). The delegate
|
|
8059
|
+
* Populated by `promoteToDirector` when it refuses to promote. The delegate
|
|
7554
8060
|
* tool reads this through `getPromotionBlockReason` to render an
|
|
7555
|
-
* actionable error instead of a generic "could not
|
|
8061
|
+
* actionable error instead of a generic "Director could not be activated".
|
|
7556
8062
|
*/
|
|
7557
8063
|
promotionBlockReason = null;
|
|
7558
8064
|
/**
|
|
@@ -7565,14 +8071,34 @@ var MultiAgentHost = class {
|
|
|
7565
8071
|
* orchestration tools and `--director` becomes a no-op.
|
|
7566
8072
|
*/
|
|
7567
8073
|
async ensureDirector() {
|
|
8074
|
+
if (this.director) return this.director;
|
|
7568
8075
|
if (!this.opts.directorMode) return null;
|
|
7569
|
-
await this.
|
|
8076
|
+
await this.buildDirector();
|
|
7570
8077
|
return this.director ?? null;
|
|
7571
8078
|
}
|
|
7572
|
-
|
|
7573
|
-
|
|
8079
|
+
/** Access the Director's internal coordinator. Returns the concrete
|
|
8080
|
+
* `DefaultMultiAgentCoordinator` so callers can use class-only surface
|
|
8081
|
+
* (`on`, `setRunner`) that isn't part of the `MultiAgentCoordinator`
|
|
8082
|
+
* interface. */
|
|
8083
|
+
getCoordinator() {
|
|
8084
|
+
return this.director.coordinator;
|
|
8085
|
+
}
|
|
8086
|
+
async buildDirector() {
|
|
8087
|
+
if (this.director) return;
|
|
7574
8088
|
const config = this.deps.configStore.get();
|
|
7575
|
-
|
|
8089
|
+
const fleetManager = new FleetManager({
|
|
8090
|
+
manifestPath: this.opts.manifestPath,
|
|
8091
|
+
sessionsRoot: this.opts.sessionsRoot,
|
|
8092
|
+
directorRunId: this.opts.directorRunId,
|
|
8093
|
+
stateCheckpointPath: this.opts.stateCheckpointPath,
|
|
8094
|
+
sessionWriter: this.opts.sessionWriter,
|
|
8095
|
+
directorBudget: this.opts.directorBudget,
|
|
8096
|
+
manifestDebounceMs: 2e3,
|
|
8097
|
+
checkpointDebounceMs: this.opts.checkpointDebounceMs ?? 250,
|
|
8098
|
+
maxSpawnDepth: 5
|
|
8099
|
+
});
|
|
8100
|
+
this.fleetManager = fleetManager;
|
|
8101
|
+
if (this.opts.sessionsRoot && !this.sessionFactory) {
|
|
7576
8102
|
this.sessionFactory = makeDirectorSessionFactory({
|
|
7577
8103
|
sessionsRoot: this.opts.sessionsRoot,
|
|
7578
8104
|
directorRunId: this.opts.directorRunId
|
|
@@ -7582,75 +8108,45 @@ var MultiAgentHost = class {
|
|
|
7582
8108
|
coordinatorId: randomUUID(),
|
|
7583
8109
|
doneCondition: { type: "all_tasks_done" },
|
|
7584
8110
|
maxConcurrent: 8
|
|
7585
|
-
// No defaultBudget. Caps land on a subagent ONLY when the
|
|
7586
|
-
// orchestrator (delegate-tool / spawn_subagent) or the user
|
|
7587
|
-
// (CLI flag) sets them explicitly. The prior defaults
|
|
7588
|
-
// (1000 tools / 200 iter / 4h) silently killed long autonomous
|
|
7589
|
-
// runs; for a "work until done" director we want no implicit
|
|
7590
|
-
// ceilings. The orchestrator can still cap a single subagent
|
|
7591
|
-
// by passing maxToolCalls/maxIterations through the spawn tool.
|
|
7592
8111
|
};
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
8112
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path23.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
8113
|
+
this.director = new Director({
|
|
8114
|
+
config: coordinatorConfig,
|
|
8115
|
+
manifestPath: this.opts.manifestPath,
|
|
8116
|
+
sharedScratchpadPath: defaultScratchpad,
|
|
8117
|
+
stateCheckpointPath: this.opts.stateCheckpointPath,
|
|
8118
|
+
sessionWriter: this.opts.sessionWriter,
|
|
8119
|
+
directorBudget: this.opts.directorBudget,
|
|
8120
|
+
maxBudgetExtensions: this.opts.maxBudgetExtensions,
|
|
8121
|
+
checkpointDebounceMs: this.opts.checkpointDebounceMs,
|
|
8122
|
+
sessionsRoot: this.opts.sessionsRoot,
|
|
8123
|
+
directorRunId: this.opts.directorRunId,
|
|
8124
|
+
maxSpawnDepth: 5,
|
|
8125
|
+
fleetManager
|
|
8126
|
+
// pass so director.fleetManager is never undefined
|
|
8127
|
+
});
|
|
8128
|
+
this.director.on("task.completed", ({ task, result }) => {
|
|
8129
|
+
this.fleetManager?.removePendingTask(task.id);
|
|
8130
|
+
this.emitLifecycleCompleted(task.id, result);
|
|
8131
|
+
});
|
|
8132
|
+
this.director.fleet.filter("budget.threshold_reached", (e) => {
|
|
8133
|
+
const payload = e.payload;
|
|
8134
|
+
this.deps.events.emit("subagent.budget_warning", {
|
|
8135
|
+
subagentId: e.subagentId,
|
|
8136
|
+
kind: payload.kind,
|
|
8137
|
+
used: payload.used,
|
|
8138
|
+
limit: payload.limit
|
|
7618
8139
|
});
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
limit: payload.limit
|
|
7626
|
-
});
|
|
8140
|
+
});
|
|
8141
|
+
this.getCoordinator().on("task.assigned", ({ task, subagentId }) => {
|
|
8142
|
+
this.deps.events.emit("subagent.task_started", {
|
|
8143
|
+
subagentId,
|
|
8144
|
+
taskId: task.id,
|
|
8145
|
+
description: task.description
|
|
7627
8146
|
});
|
|
7628
|
-
|
|
7629
|
-
} else {
|
|
7630
|
-
this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
|
|
7631
|
-
this.coordinator.on(
|
|
7632
|
-
"task.completed",
|
|
7633
|
-
({ task, result }) => {
|
|
7634
|
-
this.results.push(result);
|
|
7635
|
-
this.pending.delete(task.id);
|
|
7636
|
-
this.emitLifecycleCompleted(task.id, result);
|
|
7637
|
-
}
|
|
7638
|
-
);
|
|
7639
|
-
}
|
|
7640
|
-
this.coordinator.on(
|
|
7641
|
-
"task.assigned",
|
|
7642
|
-
({ task, subagentId }) => {
|
|
7643
|
-
this.deps.events.emit("subagent.task_started", {
|
|
7644
|
-
subagentId,
|
|
7645
|
-
taskId: task.id,
|
|
7646
|
-
description: task.description
|
|
7647
|
-
});
|
|
7648
|
-
}
|
|
7649
|
-
);
|
|
8147
|
+
});
|
|
7650
8148
|
const runner = this.buildSubagentRunner(config);
|
|
7651
|
-
|
|
7652
|
-
innerCoord.setRunner(runner);
|
|
7653
|
-
return this.coordinator;
|
|
8149
|
+
this.getCoordinator().setRunner(runner);
|
|
7654
8150
|
}
|
|
7655
8151
|
/**
|
|
7656
8152
|
* Build the per-subagent runner: agent factory → runner. Extracted so
|
|
@@ -7699,6 +8195,16 @@ var MultiAgentHost = class {
|
|
|
7699
8195
|
model: subCfg.model ?? config.model,
|
|
7700
8196
|
tools: this.filterTools(subCfg.tools)
|
|
7701
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
|
+
});
|
|
7702
8208
|
const agent = new Agent({
|
|
7703
8209
|
container: this.deps.container,
|
|
7704
8210
|
tools: this.subagentToolRegistry(subCfg.tools),
|
|
@@ -7710,7 +8216,8 @@ var MultiAgentHost = class {
|
|
|
7710
8216
|
// run under a director, not the user. Auto-approve everything
|
|
7711
8217
|
// (except tool-level hard denies); the user already authorized
|
|
7712
8218
|
// the work when they invoked the leader.
|
|
7713
|
-
permissionPolicy: new AutoApprovePermissionPolicy()
|
|
8219
|
+
permissionPolicy: new AutoApprovePermissionPolicy(),
|
|
8220
|
+
toolExecutor
|
|
7714
8221
|
});
|
|
7715
8222
|
const hostEvents = this.deps.events;
|
|
7716
8223
|
const offToolBridge = events.on("tool.executed", (e) => {
|
|
@@ -7732,7 +8239,7 @@ var MultiAgentHost = class {
|
|
|
7732
8239
|
};
|
|
7733
8240
|
return { agent, events, dispose };
|
|
7734
8241
|
};
|
|
7735
|
-
return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet });
|
|
8242
|
+
return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet ?? NULL_FLEET_BUS });
|
|
7736
8243
|
}
|
|
7737
8244
|
/**
|
|
7738
8245
|
* Build a Provider for a subagent. When `overrideId` is supplied (from
|
|
@@ -7778,7 +8285,7 @@ var MultiAgentHost = class {
|
|
|
7778
8285
|
* the full tool registry.
|
|
7779
8286
|
*/
|
|
7780
8287
|
async spawn(description, opts) {
|
|
7781
|
-
await this.
|
|
8288
|
+
await this.buildDirector();
|
|
7782
8289
|
const subagentConfig = {
|
|
7783
8290
|
name: opts?.name ?? "adhoc",
|
|
7784
8291
|
role: "general",
|
|
@@ -7786,35 +8293,11 @@ var MultiAgentHost = class {
|
|
|
7786
8293
|
model: opts?.model,
|
|
7787
8294
|
tools: opts?.tools
|
|
7788
8295
|
};
|
|
7789
|
-
const transcriptPath = this.sessionFactory ?
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
const taskId2 = randomUUID();
|
|
7793
|
-
this.pending.set(taskId2, { description, subagentId });
|
|
7794
|
-
this.deps.events.emit("subagent.spawned", {
|
|
7795
|
-
subagentId,
|
|
7796
|
-
taskId: taskId2,
|
|
7797
|
-
name: subagentConfig.name,
|
|
7798
|
-
provider: opts?.provider,
|
|
7799
|
-
model: opts?.model,
|
|
7800
|
-
description,
|
|
7801
|
-
transcriptPath
|
|
7802
|
-
});
|
|
7803
|
-
await this.director.assign({
|
|
7804
|
-
id: taskId2,
|
|
7805
|
-
description,
|
|
7806
|
-
subagentId
|
|
7807
|
-
// No maxToolCalls — same reasoning as the spawn config above.
|
|
7808
|
-
// The director / orchestrator owns the budget decision.
|
|
7809
|
-
});
|
|
7810
|
-
return { subagentId, taskId: taskId2 };
|
|
7811
|
-
}
|
|
7812
|
-
const coord = this.coordinator;
|
|
7813
|
-
const spawned = await coord.spawn(subagentConfig);
|
|
7814
|
-
const taskId = randomUUID();
|
|
7815
|
-
this.pending.set(taskId, { description, subagentId: spawned.subagentId });
|
|
8296
|
+
const transcriptPath = this.sessionFactory ? path23.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
8297
|
+
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
|
|
8298
|
+
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
7816
8299
|
this.deps.events.emit("subagent.spawned", {
|
|
7817
|
-
subagentId
|
|
8300
|
+
subagentId,
|
|
7818
8301
|
taskId,
|
|
7819
8302
|
name: subagentConfig.name,
|
|
7820
8303
|
provider: opts?.provider,
|
|
@@ -7822,13 +8305,22 @@ var MultiAgentHost = class {
|
|
|
7822
8305
|
description,
|
|
7823
8306
|
transcriptPath
|
|
7824
8307
|
});
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
8308
|
+
return { subagentId, taskId };
|
|
8309
|
+
}
|
|
8310
|
+
/**
|
|
8311
|
+
* Common spawn + assign logic shared by both director mode and raw
|
|
8312
|
+
* coordinator mode. Extracts the identical body from the two branches
|
|
8313
|
+
* in `spawn()` so future changes (e.g. adding a new field to both
|
|
8314
|
+
* paths) are made in one place.
|
|
8315
|
+
*
|
|
8316
|
+
* Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
|
|
8317
|
+
* and event emission — the helper only talks to the coordinator.
|
|
8318
|
+
*/
|
|
8319
|
+
async _spawnAndAssign(subagentConfig) {
|
|
8320
|
+
const taskId = randomUUID();
|
|
8321
|
+
const subagentId = await this.director.spawn(subagentConfig);
|
|
8322
|
+
await this.director.assign({ id: taskId, description: "", subagentId });
|
|
8323
|
+
return { subagentId, taskId };
|
|
7832
8324
|
}
|
|
7833
8325
|
/**
|
|
7834
8326
|
* Relay a `task.completed` notification (from either the Director or
|
|
@@ -7851,29 +8343,24 @@ var MultiAgentHost = class {
|
|
|
7851
8343
|
}
|
|
7852
8344
|
status() {
|
|
7853
8345
|
const activeSubagentIds = /* @__PURE__ */ new Set();
|
|
7854
|
-
|
|
7855
|
-
|
|
8346
|
+
const live = [];
|
|
8347
|
+
if (this.director) {
|
|
8348
|
+
const coord = this.getCoordinator();
|
|
8349
|
+
const s = coord.getStatus();
|
|
7856
8350
|
for (const a of s.subagents) {
|
|
7857
8351
|
if (a.status === "running" || a.status === "idle") {
|
|
7858
8352
|
activeSubagentIds.add(a.id);
|
|
7859
8353
|
}
|
|
7860
|
-
}
|
|
7861
|
-
}
|
|
7862
|
-
const pending = Array.from(this.pending.entries()).filter(([, v]) => activeSubagentIds.has(v.subagentId)).map(([taskId, v]) => ({
|
|
7863
|
-
taskId,
|
|
7864
|
-
description: v.description,
|
|
7865
|
-
subagentId: v.subagentId
|
|
7866
|
-
}));
|
|
7867
|
-
const live = [];
|
|
7868
|
-
if (this.coordinator) {
|
|
7869
|
-
const s = this.coordinator.getStatus();
|
|
7870
|
-
for (const a of s.subagents) {
|
|
7871
8354
|
live.push({ subagentId: a.id, status: a.status, task: a.currentTask });
|
|
7872
8355
|
}
|
|
7873
8356
|
}
|
|
8357
|
+
const fleetStatus = this.fleetManager?.getFleetStatus() ?? { pending: []};
|
|
8358
|
+
const pending = fleetStatus.pending.filter((p) => activeSubagentIds.has(p.subagentId));
|
|
8359
|
+
const completed = this.director ? this.director.completedResults() : [];
|
|
8360
|
+
const completedCount = completed.length;
|
|
7874
8361
|
const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
|
|
7875
|
-
const summary = !this.
|
|
7876
|
-
return { pending, completed
|
|
8362
|
+
const summary = !this.director ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${completedCount} completed.` : `${pending.length} pending, ${completedCount} completed.`;
|
|
8363
|
+
return { pending, completed, live, summary };
|
|
7877
8364
|
}
|
|
7878
8365
|
/**
|
|
7879
8366
|
* Roll up per-subagent runtime cost from completed TaskResults. We don't
|
|
@@ -7886,8 +8373,9 @@ var MultiAgentHost = class {
|
|
|
7886
8373
|
* the table renders the most interesting subagent at the top.
|
|
7887
8374
|
*/
|
|
7888
8375
|
usage() {
|
|
8376
|
+
const completed = this.director ? this.director.completedResults() : [];
|
|
7889
8377
|
const bySubagent = /* @__PURE__ */ new Map();
|
|
7890
|
-
for (const r of
|
|
8378
|
+
for (const r of completed) {
|
|
7891
8379
|
const cur = bySubagent.get(r.subagentId) ?? {
|
|
7892
8380
|
tasks: 0,
|
|
7893
8381
|
iterations: 0,
|
|
@@ -7933,7 +8421,7 @@ var MultiAgentHost = class {
|
|
|
7933
8421
|
*/
|
|
7934
8422
|
async manifest() {
|
|
7935
8423
|
if (!this.director) return null;
|
|
7936
|
-
return this.director.writeManifest();
|
|
8424
|
+
return await this.director.fleetManager?.writeManifest() ?? null;
|
|
7937
8425
|
}
|
|
7938
8426
|
/**
|
|
7939
8427
|
* Promote a non-director session to director mode at runtime. Only
|
|
@@ -7946,25 +8434,18 @@ var MultiAgentHost = class {
|
|
|
7946
8434
|
*/
|
|
7947
8435
|
async promoteToDirector() {
|
|
7948
8436
|
if (this.director) return this.director;
|
|
7949
|
-
if (this.coordinator) {
|
|
7950
|
-
const status = this.coordinator.getStatus();
|
|
7951
|
-
const running = status.subagents.filter((s) => s.status === "running").length;
|
|
7952
|
-
const idle = status.subagents.filter((s) => s.status === "idle").length;
|
|
7953
|
-
this.promotionBlockReason = `Cannot promote to director: a non-director coordinator is already in use (${running} running, ${idle} idle, ${status.pendingTasks} pending tasks). Stop the existing subagents with /fleet kill <id> or wait for them to finish, then retry \u2014 or restart wstack with --director to start in director mode.`;
|
|
7954
|
-
return null;
|
|
7955
|
-
}
|
|
7956
8437
|
this.opts.directorMode = true;
|
|
7957
8438
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
7958
|
-
this.opts.manifestPath =
|
|
8439
|
+
this.opts.manifestPath = path23.join(this.opts.fleetRoot, "fleet.json");
|
|
7959
8440
|
}
|
|
7960
8441
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
7961
|
-
this.opts.sharedScratchpadPath =
|
|
8442
|
+
this.opts.sharedScratchpadPath = path23.join(this.opts.fleetRoot, "shared");
|
|
7962
8443
|
}
|
|
7963
8444
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
7964
|
-
this.opts.sessionsRoot =
|
|
8445
|
+
this.opts.sessionsRoot = path23.join(this.opts.fleetRoot, "subagents");
|
|
7965
8446
|
}
|
|
7966
8447
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
7967
|
-
this.opts.stateCheckpointPath =
|
|
8448
|
+
this.opts.stateCheckpointPath = path23.join(this.opts.fleetRoot, "director-state.json");
|
|
7968
8449
|
}
|
|
7969
8450
|
await this.ensureDirector();
|
|
7970
8451
|
return this.director ?? null;
|
|
@@ -7994,13 +8475,13 @@ var MultiAgentHost = class {
|
|
|
7994
8475
|
* called /fleet kill before any /spawn, and there's nothing to do.
|
|
7995
8476
|
*/
|
|
7996
8477
|
async kill(subagentId) {
|
|
7997
|
-
if (!this.
|
|
7998
|
-
await this.
|
|
8478
|
+
if (!this.director) return false;
|
|
8479
|
+
await this.getCoordinator().stop(subagentId);
|
|
7999
8480
|
return true;
|
|
8000
8481
|
}
|
|
8001
8482
|
async stopAll() {
|
|
8002
|
-
if (this.
|
|
8003
|
-
await this.
|
|
8483
|
+
if (this.director) {
|
|
8484
|
+
await this.getCoordinator().stopAll();
|
|
8004
8485
|
}
|
|
8005
8486
|
}
|
|
8006
8487
|
};
|
|
@@ -8085,11 +8566,11 @@ var SessionStats = class {
|
|
|
8085
8566
|
if (e.name === "bash") this.bashCommands++;
|
|
8086
8567
|
else if (e.name === "fetch") this.fetches++;
|
|
8087
8568
|
if (!e.ok) return;
|
|
8088
|
-
const
|
|
8089
|
-
if (e.name === "read" &&
|
|
8090
|
-
else if (e.name === "edit" &&
|
|
8091
|
-
else if (e.name === "write" &&
|
|
8092
|
-
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);
|
|
8093
8574
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
8094
8575
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
8095
8576
|
}
|
|
@@ -8315,6 +8796,19 @@ async function setupCompaction(params) {
|
|
|
8315
8796
|
return { effectiveMaxContext, autoCompactor };
|
|
8316
8797
|
}
|
|
8317
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
|
+
});
|
|
8318
8812
|
return new Agent({
|
|
8319
8813
|
container: params.container,
|
|
8320
8814
|
tools: params.tools,
|
|
@@ -8326,9 +8820,144 @@ function createAgent(params) {
|
|
|
8326
8820
|
iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
|
|
8327
8821
|
executionStrategy: params.config.tools.defaultExecutionStrategy,
|
|
8328
8822
|
perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
|
|
8329
|
-
confirmAwaiter: params.confirmAwaiter
|
|
8823
|
+
confirmAwaiter: params.confirmAwaiter,
|
|
8824
|
+
toolExecutor,
|
|
8825
|
+
tracer: params.tracer
|
|
8330
8826
|
});
|
|
8331
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
|
+
}
|
|
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;
|
|
8960
|
+
}
|
|
8332
8961
|
async function setupProvider(params) {
|
|
8333
8962
|
const { config, modelsRegistry, logger } = params;
|
|
8334
8963
|
const savedProviderCfg = config.providers?.[config.provider];
|
|
@@ -8420,12 +9049,12 @@ async function setupSession(params) {
|
|
|
8420
9049
|
}
|
|
8421
9050
|
const sessionRef = { current: session };
|
|
8422
9051
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
8423
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
8424
|
-
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) });
|
|
8425
9054
|
const ctxSignal = new AbortController().signal;
|
|
8426
9055
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
8427
9056
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
8428
|
-
const todosCheckpointPath =
|
|
9057
|
+
const todosCheckpointPath = path23.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
8429
9058
|
if (resumeId) {
|
|
8430
9059
|
try {
|
|
8431
9060
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -8437,13 +9066,13 @@ async function setupSession(params) {
|
|
|
8437
9066
|
}
|
|
8438
9067
|
}
|
|
8439
9068
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
8440
|
-
const planPath =
|
|
9069
|
+
const planPath = path23.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
8441
9070
|
context.state.setMeta("plan.path", planPath);
|
|
8442
9071
|
let dirState;
|
|
8443
9072
|
if (resumeId) {
|
|
8444
9073
|
try {
|
|
8445
|
-
const fleetRoot =
|
|
8446
|
-
dirState = await loadDirectorState(
|
|
9074
|
+
const fleetRoot = path23.join(wpaths.projectSessions, session.id);
|
|
9075
|
+
dirState = await loadDirectorState(path23.join(fleetRoot, "director-state.json"));
|
|
8447
9076
|
if (dirState) {
|
|
8448
9077
|
const tCounts = {};
|
|
8449
9078
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -8470,22 +9099,11 @@ function resolveBundledSkillsDir2() {
|
|
|
8470
9099
|
try {
|
|
8471
9100
|
const req2 = createRequire(import.meta.url);
|
|
8472
9101
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
8473
|
-
return
|
|
9102
|
+
return path23.join(path23.dirname(corePkg), "skills");
|
|
8474
9103
|
} catch {
|
|
8475
9104
|
return void 0;
|
|
8476
9105
|
}
|
|
8477
9106
|
}
|
|
8478
|
-
function buildPluginOptions(config) {
|
|
8479
|
-
const options = {};
|
|
8480
|
-
for (const entry of config.plugins ?? []) {
|
|
8481
|
-
if (typeof entry !== "object") continue;
|
|
8482
|
-
if (entry.options) options[entry.name] = { ...entry.options };
|
|
8483
|
-
}
|
|
8484
|
-
for (const [name, value] of Object.entries(config.extensions ?? {})) {
|
|
8485
|
-
options[name] = { ...options[name] ?? {}, ...value };
|
|
8486
|
-
}
|
|
8487
|
-
return options;
|
|
8488
|
-
}
|
|
8489
9107
|
async function main(argv) {
|
|
8490
9108
|
const ctx = await boot(argv);
|
|
8491
9109
|
if (typeof ctx === "number") return ctx;
|
|
@@ -8577,7 +9195,7 @@ async function main(argv) {
|
|
|
8577
9195
|
modeId,
|
|
8578
9196
|
modePrompt,
|
|
8579
9197
|
modelCapabilities,
|
|
8580
|
-
planPath: () => sessionRef.current ?
|
|
9198
|
+
planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
8581
9199
|
})
|
|
8582
9200
|
);
|
|
8583
9201
|
const toolRegistry = new ToolRegistry();
|
|
@@ -8591,72 +9209,10 @@ async function main(argv) {
|
|
|
8591
9209
|
}
|
|
8592
9210
|
const events = new EventBus();
|
|
8593
9211
|
events.setLogger(logger);
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
const metricsPort = typeof metricsPortFlag === "string" && metricsPortFlag.length > 0 ? Number.parseInt(metricsPortFlag, 10) : void 0;
|
|
8599
|
-
if (metricsPort !== void 0 && !flags.metrics) flags.metrics = true;
|
|
8600
|
-
if (flags.metrics) {
|
|
8601
|
-
metricsSink = new InMemoryMetricsSink();
|
|
8602
|
-
wireMetricsToEvents(events, metricsSink);
|
|
8603
|
-
healthRegistry = new DefaultHealthRegistry();
|
|
8604
|
-
healthRegistry.register({
|
|
8605
|
-
name: "session-store",
|
|
8606
|
-
check: async () => {
|
|
8607
|
-
try {
|
|
8608
|
-
await fsp2.access(wpaths.projectSessions);
|
|
8609
|
-
return { status: "healthy" };
|
|
8610
|
-
} catch (e) {
|
|
8611
|
-
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
8612
|
-
}
|
|
8613
|
-
}
|
|
8614
|
-
});
|
|
8615
|
-
healthRegistry.register({
|
|
8616
|
-
name: "provider",
|
|
8617
|
-
check: async () => ({
|
|
8618
|
-
status: "healthy",
|
|
8619
|
-
data: { id: config.provider, model: config.model }
|
|
8620
|
-
})
|
|
8621
|
-
});
|
|
8622
|
-
const dumpMetrics = () => {
|
|
8623
|
-
if (!metricsSink) return;
|
|
8624
|
-
try {
|
|
8625
|
-
const out = path21.join(wpaths.projectSessions, "metrics.json");
|
|
8626
|
-
const snap = metricsSink.snapshot();
|
|
8627
|
-
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
8628
|
-
} catch {
|
|
8629
|
-
}
|
|
8630
|
-
};
|
|
8631
|
-
process.on("exit", dumpMetrics);
|
|
8632
|
-
process.on("SIGINT", () => {
|
|
8633
|
-
dumpMetrics();
|
|
8634
|
-
process.exit(130);
|
|
8635
|
-
});
|
|
8636
|
-
if (metricsPort !== void 0 && Number.isFinite(metricsPort)) {
|
|
8637
|
-
try {
|
|
8638
|
-
metricsServerHandle = await startMetricsServer({
|
|
8639
|
-
port: metricsPort,
|
|
8640
|
-
host: process.env.METRICS_HOST ?? "127.0.0.1",
|
|
8641
|
-
sink: metricsSink,
|
|
8642
|
-
// V2-C: mount /healthz on the same listener so k8s probes can
|
|
8643
|
-
// hit one endpoint per pod for both observability and liveness.
|
|
8644
|
-
healthRegistry
|
|
8645
|
-
});
|
|
8646
|
-
logger.info(
|
|
8647
|
-
`metrics endpoint listening on ${metricsServerHandle.url} (healthz on same port)`
|
|
8648
|
-
);
|
|
8649
|
-
process.on("exit", () => {
|
|
8650
|
-
void metricsServerHandle?.close().catch(() => {
|
|
8651
|
-
});
|
|
8652
|
-
});
|
|
8653
|
-
} catch (err) {
|
|
8654
|
-
logger.warn(
|
|
8655
|
-
`metrics endpoint failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
8656
|
-
);
|
|
8657
|
-
}
|
|
8658
|
-
}
|
|
8659
|
-
}
|
|
9212
|
+
const { metricsSink, healthRegistry, metricsServerHandle } = (() => {
|
|
9213
|
+
const ms = setupMetrics({ flags, wpaths, events, logger, config: { provider: config.provider, model: config.model } });
|
|
9214
|
+
return ms;
|
|
9215
|
+
})();
|
|
8660
9216
|
const spinner = new Spinner();
|
|
8661
9217
|
let lastInputTokens = 0;
|
|
8662
9218
|
events.on("provider.response", (e) => {
|
|
@@ -8775,49 +9331,21 @@ async function main(argv) {
|
|
|
8775
9331
|
}
|
|
8776
9332
|
}
|
|
8777
9333
|
const slashRegistry = new SlashCommandRegistry();
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
const pluginConfig = Object.keys(pluginOptions).length > 0 ? patchConfig(config, { extensions: pluginOptions }) : config;
|
|
8794
|
-
await loadPlugins(resolvedPlugins, {
|
|
8795
|
-
log: logger,
|
|
8796
|
-
// Each plugin's `configSchema` is validated against merged
|
|
8797
|
-
// options from `plugins[].options` and `extensions[name]`.
|
|
8798
|
-
// The merged view is also exposed as `api.config.extensions`.
|
|
8799
|
-
pluginOptions,
|
|
8800
|
-
apiFactory: (plugin) => createApi2(plugin.name, {
|
|
8801
|
-
container,
|
|
8802
|
-
events,
|
|
8803
|
-
pipelines,
|
|
8804
|
-
toolRegistry,
|
|
8805
|
-
providerRegistry,
|
|
8806
|
-
slashCommandRegistry: slashRegistry,
|
|
8807
|
-
mcpRegistry,
|
|
8808
|
-
config: pluginConfig,
|
|
8809
|
-
log: logger,
|
|
8810
|
-
extensions: agent.extensions,
|
|
8811
|
-
sessionWriter: {
|
|
8812
|
-
transcriptPath: context.session.transcriptPath,
|
|
8813
|
-
append: (e) => context.session.append(e)
|
|
8814
|
-
},
|
|
8815
|
-
metricsSink,
|
|
8816
|
-
configStore
|
|
8817
|
-
})
|
|
8818
|
-
});
|
|
8819
|
-
}
|
|
8820
|
-
}
|
|
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
|
+
});
|
|
8821
9349
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
8822
9350
|
try {
|
|
8823
9351
|
const savedCfg = config.providers?.[providerId];
|
|
@@ -8842,12 +9370,22 @@ async function main(argv) {
|
|
|
8842
9370
|
const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
|
|
8843
9371
|
let director = null;
|
|
8844
9372
|
let autonomyMode = "off";
|
|
8845
|
-
|
|
8846
|
-
const
|
|
8847
|
-
const
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
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);
|
|
8851
9389
|
const multiAgentHost = new MultiAgentHost(
|
|
8852
9390
|
{
|
|
8853
9391
|
container,
|
|
@@ -8859,7 +9397,8 @@ async function main(argv) {
|
|
|
8859
9397
|
session,
|
|
8860
9398
|
tokenCounter,
|
|
8861
9399
|
projectRoot,
|
|
8862
|
-
cwd
|
|
9400
|
+
cwd,
|
|
9401
|
+
secretScrubber: container.resolve(TOKENS.SecretScrubber)
|
|
8863
9402
|
},
|
|
8864
9403
|
{
|
|
8865
9404
|
directorMode,
|
|
@@ -8906,6 +9445,20 @@ async function main(argv) {
|
|
|
8906
9445
|
this.enabled = enabled;
|
|
8907
9446
|
}
|
|
8908
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
|
+
};
|
|
8909
9462
|
const slashCmds = buildBuiltinSlashCommands({
|
|
8910
9463
|
registry: slashRegistry,
|
|
8911
9464
|
toolRegistry,
|
|
@@ -8925,6 +9478,7 @@ async function main(argv) {
|
|
|
8925
9478
|
fleetStreamController,
|
|
8926
9479
|
llmProvider: provider,
|
|
8927
9480
|
llmModel: config.model,
|
|
9481
|
+
statuslineConfig: statuslineConfigDeps,
|
|
8928
9482
|
onSpawn: async (description, spawnOpts) => {
|
|
8929
9483
|
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
8930
9484
|
const tags = [];
|
|
@@ -9031,7 +9585,7 @@ async function main(argv) {
|
|
|
9031
9585
|
return `Unknown fleet action: ${action}`;
|
|
9032
9586
|
},
|
|
9033
9587
|
onFleetLog: async (subagentId, mode) => {
|
|
9034
|
-
const subagentsRoot =
|
|
9588
|
+
const subagentsRoot = path23.join(fleetRootForPromotion, "subagents");
|
|
9035
9589
|
let runDirs;
|
|
9036
9590
|
try {
|
|
9037
9591
|
runDirs = await fsp2.readdir(subagentsRoot);
|
|
@@ -9040,7 +9594,7 @@ async function main(argv) {
|
|
|
9040
9594
|
}
|
|
9041
9595
|
const found = [];
|
|
9042
9596
|
for (const runId of runDirs) {
|
|
9043
|
-
const runDir =
|
|
9597
|
+
const runDir = path23.join(subagentsRoot, runId);
|
|
9044
9598
|
let files;
|
|
9045
9599
|
try {
|
|
9046
9600
|
files = await fsp2.readdir(runDir);
|
|
@@ -9049,7 +9603,7 @@ async function main(argv) {
|
|
|
9049
9603
|
}
|
|
9050
9604
|
for (const f of files) {
|
|
9051
9605
|
if (!f.endsWith(".jsonl")) continue;
|
|
9052
|
-
const full =
|
|
9606
|
+
const full = path23.join(runDir, f);
|
|
9053
9607
|
try {
|
|
9054
9608
|
const stat3 = await fsp2.stat(full);
|
|
9055
9609
|
found.push({
|
|
@@ -9146,7 +9700,7 @@ async function main(argv) {
|
|
|
9146
9700
|
}
|
|
9147
9701
|
const dir = await multiAgentHost.ensureDirector();
|
|
9148
9702
|
if (!dir) return "Director is not available.";
|
|
9149
|
-
const dirStatePath =
|
|
9703
|
+
const dirStatePath = path23.join(fleetRootForPromotion, "director-state.json");
|
|
9150
9704
|
const prior = await loadDirectorState(dirStatePath);
|
|
9151
9705
|
if (!prior) {
|
|
9152
9706
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -9217,9 +9771,9 @@ async function main(argv) {
|
|
|
9217
9771
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
9218
9772
|
toolRegistry.register(tool);
|
|
9219
9773
|
}
|
|
9220
|
-
const mp =
|
|
9221
|
-
const sp =
|
|
9222
|
-
const ss =
|
|
9774
|
+
const mp = path23.join(fleetRootForPromotion, "fleet.json");
|
|
9775
|
+
const sp = path23.join(fleetRootForPromotion, "shared");
|
|
9776
|
+
const ss = path23.join(fleetRootForPromotion, "subagents");
|
|
9223
9777
|
const lines = [
|
|
9224
9778
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
9225
9779
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -9262,6 +9816,26 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9262
9816
|
}
|
|
9263
9817
|
return autonomyMode;
|
|
9264
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
|
+
},
|
|
9265
9839
|
onExit: () => {
|
|
9266
9840
|
void mcpRegistry.stopAll();
|
|
9267
9841
|
},
|
|
@@ -9319,6 +9893,29 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9319
9893
|
}
|
|
9320
9894
|
});
|
|
9321
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
|
+
}
|
|
9322
9919
|
const savedProviderCfg = config.providers?.[config.provider];
|
|
9323
9920
|
return execute({
|
|
9324
9921
|
agent,
|
|
@@ -9349,11 +9946,18 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9349
9946
|
director: director ?? null,
|
|
9350
9947
|
fleetRoster: FLEET_ROSTER,
|
|
9351
9948
|
fleetStreamController,
|
|
9949
|
+
statuslineHiddenItems,
|
|
9950
|
+
setStatuslineHiddenItems,
|
|
9352
9951
|
getYolo: () => {
|
|
9353
9952
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
9354
9953
|
return policy.getYolo();
|
|
9355
9954
|
},
|
|
9356
9955
|
getAutonomy: () => autonomyMode,
|
|
9956
|
+
getEternalEngine: () => eternalEngine,
|
|
9957
|
+
subscribeEternalIteration: (fn) => {
|
|
9958
|
+
eternalListeners.add(fn);
|
|
9959
|
+
return () => eternalListeners.delete(fn);
|
|
9960
|
+
},
|
|
9357
9961
|
skillLoader: config.features.skills ? skillLoader : void 0
|
|
9358
9962
|
});
|
|
9359
9963
|
}
|