@wrongstack/cli 0.5.5 → 0.5.7
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 +528 -294
- 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 path21 from 'path';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import * as
|
|
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, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director,
|
|
6
|
+
import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, 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, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os6 from 'os';
|
|
9
9
|
import os6__default from 'os';
|
|
@@ -261,8 +261,8 @@ function buildSddCommand(opts) {
|
|
|
261
261
|
async run(args) {
|
|
262
262
|
const ctx = opts.context;
|
|
263
263
|
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
264
|
-
const specsDir =
|
|
265
|
-
const graphsDir =
|
|
264
|
+
const specsDir = path21.join(projectRoot, ".wrongstack", "specs");
|
|
265
|
+
const graphsDir = path21.join(projectRoot, ".wrongstack", "task-graphs");
|
|
266
266
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
267
267
|
new TaskGraphStore({ baseDir: graphsDir });
|
|
268
268
|
const versioning = new SpecVersioning();
|
|
@@ -278,9 +278,9 @@ function buildSddCommand(opts) {
|
|
|
278
278
|
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
279
279
|
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
280
280
|
if (!sddState.getBuilder() && !forceFlag) {
|
|
281
|
-
const sessionPath =
|
|
281
|
+
const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
282
282
|
try {
|
|
283
|
-
await
|
|
283
|
+
await fsp2.access(sessionPath);
|
|
284
284
|
const projectContext2 = await gatherProjectContext(projectRoot);
|
|
285
285
|
const tempBuilder = new AISpecBuilder({
|
|
286
286
|
store: specStore,
|
|
@@ -313,7 +313,7 @@ function buildSddCommand(opts) {
|
|
|
313
313
|
projectContext,
|
|
314
314
|
minQuestions: 2,
|
|
315
315
|
maxQuestions: 10,
|
|
316
|
-
sessionPath:
|
|
316
|
+
sessionPath: path21.join(projectRoot, ".wrongstack", "sdd-session.json")
|
|
317
317
|
}));
|
|
318
318
|
const builder = sddState.getBuilder();
|
|
319
319
|
builder.startSession(title);
|
|
@@ -581,10 +581,10 @@ Start executing the tasks one by one.`
|
|
|
581
581
|
};
|
|
582
582
|
}
|
|
583
583
|
case "cancel": {
|
|
584
|
-
const sessionPath =
|
|
584
|
+
const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
585
585
|
let deletedFromDisk = false;
|
|
586
586
|
try {
|
|
587
|
-
await
|
|
587
|
+
await fsp2.unlink(sessionPath);
|
|
588
588
|
deletedFromDisk = true;
|
|
589
589
|
} catch {
|
|
590
590
|
}
|
|
@@ -605,7 +605,7 @@ Start executing the tasks one by one.`
|
|
|
605
605
|
if (sddState.getBuilder()) {
|
|
606
606
|
return { message: "An SDD session is already active. Use /sdd cancel first." };
|
|
607
607
|
}
|
|
608
|
-
const sessionPath =
|
|
608
|
+
const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
609
609
|
const projectContext = await gatherProjectContext(projectRoot);
|
|
610
610
|
sddState.setBuilder(new AISpecBuilder({
|
|
611
611
|
store: specStore,
|
|
@@ -831,8 +831,8 @@ function sddHelp() {
|
|
|
831
831
|
async function gatherProjectContext(projectRoot) {
|
|
832
832
|
const parts = [];
|
|
833
833
|
try {
|
|
834
|
-
const pkgPath =
|
|
835
|
-
const pkgRaw = await
|
|
834
|
+
const pkgPath = path21.join(projectRoot, "package.json");
|
|
835
|
+
const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
|
|
836
836
|
const pkg = JSON.parse(pkgRaw);
|
|
837
837
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
838
838
|
parts.push(`Description: ${String(pkg.description ?? "none")}`);
|
|
@@ -847,14 +847,14 @@ async function gatherProjectContext(projectRoot) {
|
|
|
847
847
|
} catch {
|
|
848
848
|
}
|
|
849
849
|
try {
|
|
850
|
-
const tsconfigPath =
|
|
851
|
-
await
|
|
850
|
+
const tsconfigPath = path21.join(projectRoot, "tsconfig.json");
|
|
851
|
+
await fsp2.access(tsconfigPath);
|
|
852
852
|
parts.push("Language: TypeScript");
|
|
853
853
|
} catch {
|
|
854
854
|
}
|
|
855
855
|
try {
|
|
856
|
-
const srcDir =
|
|
857
|
-
const entries = await
|
|
856
|
+
const srcDir = path21.join(projectRoot, "src");
|
|
857
|
+
const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
|
|
858
858
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
859
859
|
if (dirs.length > 0) {
|
|
860
860
|
parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -976,7 +976,7 @@ __export(update_check_exports, {
|
|
|
976
976
|
getUpdateNotification: () => getUpdateNotification
|
|
977
977
|
});
|
|
978
978
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
979
|
-
return
|
|
979
|
+
return path21.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
980
980
|
}
|
|
981
981
|
function currentVersion() {
|
|
982
982
|
const req2 = createRequire(import.meta.url);
|
|
@@ -1003,7 +1003,7 @@ function isNewer(a, b) {
|
|
|
1003
1003
|
}
|
|
1004
1004
|
async function readCache(homeFn = defaultHomeDir2) {
|
|
1005
1005
|
try {
|
|
1006
|
-
const raw = await
|
|
1006
|
+
const raw = await fsp2.readFile(cachePath(homeFn), "utf8");
|
|
1007
1007
|
const entry = JSON.parse(raw);
|
|
1008
1008
|
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
|
|
1009
1009
|
return entry;
|
|
@@ -1013,9 +1013,9 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1013
1013
|
}
|
|
1014
1014
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1015
1015
|
try {
|
|
1016
|
-
const dir =
|
|
1017
|
-
await
|
|
1018
|
-
await
|
|
1016
|
+
const dir = path21.dirname(cachePath(homeFn));
|
|
1017
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
1018
|
+
await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1019
1019
|
} catch {
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
@@ -1573,7 +1573,7 @@ async function runWebUI(opts) {
|
|
|
1573
1573
|
if (!opts.globalConfigPath) return {};
|
|
1574
1574
|
let raw;
|
|
1575
1575
|
try {
|
|
1576
|
-
raw = await
|
|
1576
|
+
raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
|
|
1577
1577
|
} catch {
|
|
1578
1578
|
return {};
|
|
1579
1579
|
}
|
|
@@ -1584,7 +1584,7 @@ async function runWebUI(opts) {
|
|
|
1584
1584
|
return {};
|
|
1585
1585
|
}
|
|
1586
1586
|
if (!parsed.providers) return {};
|
|
1587
|
-
const keyFile =
|
|
1587
|
+
const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
|
|
1588
1588
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1589
1589
|
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
1590
1590
|
}
|
|
@@ -1592,7 +1592,7 @@ async function runWebUI(opts) {
|
|
|
1592
1592
|
if (!opts.globalConfigPath) return;
|
|
1593
1593
|
let raw;
|
|
1594
1594
|
try {
|
|
1595
|
-
raw = await
|
|
1595
|
+
raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
|
|
1596
1596
|
} catch {
|
|
1597
1597
|
raw = "{}";
|
|
1598
1598
|
}
|
|
@@ -1603,7 +1603,7 @@ async function runWebUI(opts) {
|
|
|
1603
1603
|
parsed = {};
|
|
1604
1604
|
}
|
|
1605
1605
|
parsed.providers = providers;
|
|
1606
|
-
const keyFile =
|
|
1606
|
+
const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
|
|
1607
1607
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1608
1608
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
1609
1609
|
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
@@ -1681,7 +1681,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
1681
1681
|
"prompt",
|
|
1682
1682
|
"metrics",
|
|
1683
1683
|
"webui",
|
|
1684
|
-
"no-check"
|
|
1684
|
+
"no-check",
|
|
1685
|
+
"director"
|
|
1685
1686
|
]);
|
|
1686
1687
|
function parseArgs(argv) {
|
|
1687
1688
|
const flags = {};
|
|
@@ -1787,7 +1788,7 @@ function parseSpawnFlags(input) {
|
|
|
1787
1788
|
return { description: rest.trim(), opts };
|
|
1788
1789
|
}
|
|
1789
1790
|
async function bootConfig(flags) {
|
|
1790
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
1791
|
+
const cwd = typeof flags["cwd"] === "string" ? path21.resolve(flags["cwd"]) : process.cwd();
|
|
1791
1792
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
1792
1793
|
const projectRoot = pathResolver.projectRoot;
|
|
1793
1794
|
const userHome = os6.homedir();
|
|
@@ -1838,13 +1839,13 @@ function flagsToConfigPatch(flags) {
|
|
|
1838
1839
|
}
|
|
1839
1840
|
async function ensureProjectMeta(paths, projectRoot) {
|
|
1840
1841
|
try {
|
|
1841
|
-
await
|
|
1842
|
+
await fsp2.mkdir(paths.projectDir, { recursive: true });
|
|
1842
1843
|
const meta = {
|
|
1843
1844
|
hash: paths.projectHash,
|
|
1844
1845
|
root: projectRoot,
|
|
1845
1846
|
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
|
|
1846
1847
|
};
|
|
1847
|
-
await
|
|
1848
|
+
await fsp2.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
|
|
1848
1849
|
} catch {
|
|
1849
1850
|
}
|
|
1850
1851
|
}
|
|
@@ -1854,11 +1855,11 @@ var ReadlineInputReader = class {
|
|
|
1854
1855
|
history = [];
|
|
1855
1856
|
pending = false;
|
|
1856
1857
|
constructor(opts = {}) {
|
|
1857
|
-
this.historyFile = opts.historyFile ??
|
|
1858
|
+
this.historyFile = opts.historyFile ?? path21.join(os6.homedir(), ".wrongstack", "history");
|
|
1858
1859
|
}
|
|
1859
1860
|
async loadHistory() {
|
|
1860
1861
|
try {
|
|
1861
|
-
const raw = await
|
|
1862
|
+
const raw = await fsp2.readFile(this.historyFile, "utf8");
|
|
1862
1863
|
this.history = raw.split("\n").filter(Boolean).slice(-1e3);
|
|
1863
1864
|
} catch {
|
|
1864
1865
|
this.history = [];
|
|
@@ -1866,8 +1867,8 @@ var ReadlineInputReader = class {
|
|
|
1866
1867
|
}
|
|
1867
1868
|
async saveHistory() {
|
|
1868
1869
|
try {
|
|
1869
|
-
await
|
|
1870
|
-
await
|
|
1870
|
+
await fsp2.mkdir(path21.dirname(this.historyFile), { recursive: true });
|
|
1871
|
+
await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
1871
1872
|
} catch {
|
|
1872
1873
|
}
|
|
1873
1874
|
}
|
|
@@ -2093,23 +2094,23 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
2093
2094
|
if (PROTECTED_BASENAMES.has(filename)) {
|
|
2094
2095
|
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
2095
2096
|
}
|
|
2096
|
-
if (filename !==
|
|
2097
|
+
if (filename !== path21.basename(filename)) {
|
|
2097
2098
|
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
2098
2099
|
}
|
|
2099
2100
|
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
2100
2101
|
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
2101
2102
|
}
|
|
2102
|
-
const resolvedParent =
|
|
2103
|
+
const resolvedParent = path21.resolve(parentDir);
|
|
2103
2104
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
2104
2105
|
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
2105
2106
|
}
|
|
2106
2107
|
}
|
|
2107
2108
|
async function safeDelete(filePath) {
|
|
2108
|
-
const dir =
|
|
2109
|
-
const filename =
|
|
2109
|
+
const dir = path21.dirname(filePath);
|
|
2110
|
+
const filename = path21.basename(filePath);
|
|
2110
2111
|
try {
|
|
2111
2112
|
assertSafeToDelete(filename, dir);
|
|
2112
|
-
await
|
|
2113
|
+
await fsp2.unlink(filePath);
|
|
2113
2114
|
} catch (err) {
|
|
2114
2115
|
if (err instanceof Error && err.message.startsWith("Refusing")) {
|
|
2115
2116
|
process.stderr.write(`[config-history] SAFETY: ${err.message}
|
|
@@ -2151,26 +2152,26 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
2151
2152
|
}
|
|
2152
2153
|
var defaultHomeDir = () => os6__default.homedir();
|
|
2153
2154
|
function historyDir(homeFn = defaultHomeDir) {
|
|
2154
|
-
return
|
|
2155
|
+
return path21.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
2155
2156
|
}
|
|
2156
2157
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
2157
|
-
return
|
|
2158
|
+
return path21.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
2158
2159
|
}
|
|
2159
2160
|
function configPath(homeFn = defaultHomeDir) {
|
|
2160
|
-
return
|
|
2161
|
+
return path21.join(homeFn(), ".wrongstack", "config.json");
|
|
2161
2162
|
}
|
|
2162
2163
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
2163
|
-
return
|
|
2164
|
+
return path21.join(homeFn(), ".wrongstack", "config.json.last");
|
|
2164
2165
|
}
|
|
2165
2166
|
function entryId(ts) {
|
|
2166
2167
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
2167
2168
|
}
|
|
2168
2169
|
async function ensureHistoryDir(homeFn = defaultHomeDir) {
|
|
2169
|
-
await
|
|
2170
|
+
await fsp2.mkdir(historyDir(homeFn), { recursive: true });
|
|
2170
2171
|
}
|
|
2171
2172
|
async function readIndex(homeFn = defaultHomeDir) {
|
|
2172
2173
|
try {
|
|
2173
|
-
const raw = await
|
|
2174
|
+
const raw = await fsp2.readFile(historyIndexPath(homeFn), "utf8");
|
|
2174
2175
|
return JSON.parse(raw);
|
|
2175
2176
|
} catch {
|
|
2176
2177
|
return { version: 1, entries: [] };
|
|
@@ -2178,7 +2179,7 @@ async function readIndex(homeFn = defaultHomeDir) {
|
|
|
2178
2179
|
}
|
|
2179
2180
|
async function writeIndex(idx, homeFn = defaultHomeDir) {
|
|
2180
2181
|
await ensureHistoryDir(homeFn);
|
|
2181
|
-
await
|
|
2182
|
+
await fsp2.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
|
|
2182
2183
|
}
|
|
2183
2184
|
async function backupCurrent(homeFn = defaultHomeDir) {
|
|
2184
2185
|
const cfg = configPath(homeFn);
|
|
@@ -2186,7 +2187,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
2186
2187
|
const ts = Date.now();
|
|
2187
2188
|
let content;
|
|
2188
2189
|
try {
|
|
2189
|
-
content = await
|
|
2190
|
+
content = await fsp2.readFile(cfg, "utf8");
|
|
2190
2191
|
} catch {
|
|
2191
2192
|
}
|
|
2192
2193
|
if (content !== void 0) {
|
|
@@ -2197,17 +2198,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
2197
2198
|
}
|
|
2198
2199
|
if (content !== void 0) {
|
|
2199
2200
|
try {
|
|
2200
|
-
const bakPath =
|
|
2201
|
+
const bakPath = path21.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
2201
2202
|
await atomicWrite(bakPath, content);
|
|
2202
2203
|
} catch {
|
|
2203
2204
|
}
|
|
2204
2205
|
}
|
|
2205
2206
|
try {
|
|
2206
|
-
const dir =
|
|
2207
|
-
const files = await
|
|
2207
|
+
const dir = path21.join(homeFn(), ".wrongstack");
|
|
2208
|
+
const files = await fsp2.readdir(dir);
|
|
2208
2209
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
2209
2210
|
for (const f of baks.slice(10)) {
|
|
2210
|
-
await safeDelete(
|
|
2211
|
+
await safeDelete(path21.join(dir, f));
|
|
2211
2212
|
}
|
|
2212
2213
|
} catch {
|
|
2213
2214
|
}
|
|
@@ -2223,8 +2224,8 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
2223
2224
|
snapshotMasked: maskConfigSecrets(newCfg),
|
|
2224
2225
|
diffSummary: diffSummary(oldCfg, newCfg)
|
|
2225
2226
|
};
|
|
2226
|
-
await
|
|
2227
|
-
|
|
2227
|
+
await fsp2.writeFile(
|
|
2228
|
+
path21.join(historyDir(homeFn), `${id}.json`),
|
|
2228
2229
|
JSON.stringify(entry, null, 2),
|
|
2229
2230
|
"utf8"
|
|
2230
2231
|
);
|
|
@@ -2239,7 +2240,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
2239
2240
|
}
|
|
2240
2241
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
2241
2242
|
try {
|
|
2242
|
-
const raw = await
|
|
2243
|
+
const raw = await fsp2.readFile(path21.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
2243
2244
|
return JSON.parse(raw);
|
|
2244
2245
|
} catch {
|
|
2245
2246
|
return null;
|
|
@@ -2251,7 +2252,7 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir) {
|
|
|
2251
2252
|
await backupCurrent(homeFn);
|
|
2252
2253
|
let oldCfg = {};
|
|
2253
2254
|
try {
|
|
2254
|
-
const raw = await
|
|
2255
|
+
const raw = await fsp2.readFile(configPath(homeFn), "utf8");
|
|
2255
2256
|
oldCfg = JSON.parse(raw);
|
|
2256
2257
|
} catch {
|
|
2257
2258
|
}
|
|
@@ -2273,13 +2274,13 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
2273
2274
|
const cfg = configPath(homeFn);
|
|
2274
2275
|
let oldCfg = {};
|
|
2275
2276
|
try {
|
|
2276
|
-
const raw = await
|
|
2277
|
+
const raw = await fsp2.readFile(cfg, "utf8");
|
|
2277
2278
|
oldCfg = JSON.parse(raw);
|
|
2278
2279
|
} catch {
|
|
2279
2280
|
}
|
|
2280
2281
|
let lastCfg = {};
|
|
2281
2282
|
try {
|
|
2282
|
-
const raw = await
|
|
2283
|
+
const raw = await fsp2.readFile(last, "utf8");
|
|
2283
2284
|
lastCfg = JSON.parse(raw);
|
|
2284
2285
|
} catch {
|
|
2285
2286
|
return { ok: false, error: "No prior backup found" };
|
|
@@ -2557,7 +2558,7 @@ async function resolveModelSelection(answer, models, provider, _registry, render
|
|
|
2557
2558
|
}
|
|
2558
2559
|
async function pathExists(file) {
|
|
2559
2560
|
try {
|
|
2560
|
-
await
|
|
2561
|
+
await fsp2.access(file);
|
|
2561
2562
|
return true;
|
|
2562
2563
|
} catch {
|
|
2563
2564
|
return false;
|
|
@@ -2568,10 +2569,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2568
2569
|
const name = declared.split("@")[0];
|
|
2569
2570
|
if (name) return name;
|
|
2570
2571
|
}
|
|
2571
|
-
if (await pathExists(
|
|
2572
|
-
if (await pathExists(
|
|
2573
|
-
if (await pathExists(
|
|
2574
|
-
if (await pathExists(
|
|
2572
|
+
if (await pathExists(path21.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2573
|
+
if (await pathExists(path21.join(root, "bun.lockb"))) return "bun";
|
|
2574
|
+
if (await pathExists(path21.join(root, "bun.lock"))) return "bun";
|
|
2575
|
+
if (await pathExists(path21.join(root, "yarn.lock"))) return "yarn";
|
|
2575
2576
|
return "npm";
|
|
2576
2577
|
}
|
|
2577
2578
|
function hasUsableScript(scripts, name) {
|
|
@@ -2592,7 +2593,7 @@ function parseMakeTargets(makefile) {
|
|
|
2592
2593
|
async function detectProjectFacts(root) {
|
|
2593
2594
|
const facts = { hints: [] };
|
|
2594
2595
|
try {
|
|
2595
|
-
const pkg = JSON.parse(await
|
|
2596
|
+
const pkg = JSON.parse(await fsp2.readFile(path21.join(root, "package.json"), "utf8"));
|
|
2596
2597
|
const scripts = pkg.scripts ?? {};
|
|
2597
2598
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2598
2599
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2606,14 +2607,14 @@ async function detectProjectFacts(root) {
|
|
|
2606
2607
|
} catch {
|
|
2607
2608
|
}
|
|
2608
2609
|
try {
|
|
2609
|
-
if (!await pathExists(
|
|
2610
|
+
if (!await pathExists(path21.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2610
2611
|
facts.test ??= "pytest";
|
|
2611
2612
|
facts.lint ??= "ruff check .";
|
|
2612
2613
|
facts.hints.push("pyproject.toml");
|
|
2613
2614
|
} catch {
|
|
2614
2615
|
}
|
|
2615
2616
|
try {
|
|
2616
|
-
if (!await pathExists(
|
|
2617
|
+
if (!await pathExists(path21.join(root, "go.mod"))) throw new Error("not go");
|
|
2617
2618
|
facts.build ??= "go build ./...";
|
|
2618
2619
|
facts.test ??= "go test ./...";
|
|
2619
2620
|
facts.run ??= "go run .";
|
|
@@ -2621,7 +2622,7 @@ async function detectProjectFacts(root) {
|
|
|
2621
2622
|
} catch {
|
|
2622
2623
|
}
|
|
2623
2624
|
try {
|
|
2624
|
-
if (!await pathExists(
|
|
2625
|
+
if (!await pathExists(path21.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2625
2626
|
facts.build ??= "cargo build";
|
|
2626
2627
|
facts.test ??= "cargo test";
|
|
2627
2628
|
facts.lint ??= "cargo clippy";
|
|
@@ -2630,7 +2631,7 @@ async function detectProjectFacts(root) {
|
|
|
2630
2631
|
} catch {
|
|
2631
2632
|
}
|
|
2632
2633
|
try {
|
|
2633
|
-
const makefile = await
|
|
2634
|
+
const makefile = await fsp2.readFile(path21.join(root, "Makefile"), "utf8");
|
|
2634
2635
|
const targets = parseMakeTargets(makefile);
|
|
2635
2636
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2636
2637
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -3266,10 +3267,10 @@ function buildInitCommand(opts) {
|
|
|
3266
3267
|
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3267
3268
|
async run(args, ctx) {
|
|
3268
3269
|
const force = args.trim() === "--force";
|
|
3269
|
-
const dir =
|
|
3270
|
-
const file =
|
|
3270
|
+
const dir = path21.join(ctx.projectRoot, ".wrongstack");
|
|
3271
|
+
const file = path21.join(dir, "AGENTS.md");
|
|
3271
3272
|
try {
|
|
3272
|
-
await
|
|
3273
|
+
await fsp2.access(file);
|
|
3273
3274
|
if (!force) {
|
|
3274
3275
|
const msg2 = `AGENTS.md already exists at ${file}. Use "/init --force" to overwrite.`;
|
|
3275
3276
|
opts.renderer.writeWarning(msg2);
|
|
@@ -3279,8 +3280,8 @@ function buildInitCommand(opts) {
|
|
|
3279
3280
|
}
|
|
3280
3281
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
3281
3282
|
const body = renderAgentsTemplate(detected);
|
|
3282
|
-
await
|
|
3283
|
-
await
|
|
3283
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
3284
|
+
await fsp2.writeFile(file, body, "utf8");
|
|
3284
3285
|
if (detected.hints.length > 0) {
|
|
3285
3286
|
const msg2 = `Wrote ${file}
|
|
3286
3287
|
Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
@@ -3378,7 +3379,7 @@ function buildMetricsCommand(opts) {
|
|
|
3378
3379
|
function buildPlanCommand(opts) {
|
|
3379
3380
|
return {
|
|
3380
3381
|
name: "plan",
|
|
3381
|
-
description: "Strategic plan board: /plan [show|add <title>|done <id|#>|remove <id|#>|clear]",
|
|
3382
|
+
description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
|
|
3382
3383
|
async run(args) {
|
|
3383
3384
|
const planPath = opts.planPath;
|
|
3384
3385
|
if (!planPath) return { message: "Plan storage is not configured for this session." };
|
|
@@ -3422,6 +3423,58 @@ ${formatPlan(updated)}` };
|
|
|
3422
3423
|
await savePlan(planPath, updated);
|
|
3423
3424
|
return { message: formatPlan(updated) };
|
|
3424
3425
|
}
|
|
3426
|
+
case "promote": {
|
|
3427
|
+
if (!restJoined) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3428
|
+
const [target, ...subtasks] = restJoined.split(/\s+/);
|
|
3429
|
+
if (!target) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3430
|
+
const derived = deriveTodosFromPlanItem(plan, target, subtasks.length > 0 ? subtasks : void 0);
|
|
3431
|
+
if (!derived) return { message: `No plan item matched "${target}".` };
|
|
3432
|
+
await savePlan(planPath, derived.plan);
|
|
3433
|
+
if (ctx) {
|
|
3434
|
+
ctx.state.replaceTodos(derived.todos);
|
|
3435
|
+
}
|
|
3436
|
+
return {
|
|
3437
|
+
message: `Promoted to ${derived.todos.length} todo(s):
|
|
3438
|
+
${formatTodosList(derived.todos)}
|
|
3439
|
+
|
|
3440
|
+
${formatPlan(derived.plan)}`
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
case "derive": {
|
|
3444
|
+
if (!restJoined) return { message: "Usage: /plan derive <id|index>" };
|
|
3445
|
+
const derived = deriveTodosFromPlanItem(plan, restJoined);
|
|
3446
|
+
if (!derived) return { message: `No plan item matched "${restJoined}".` };
|
|
3447
|
+
await savePlan(planPath, derived.plan);
|
|
3448
|
+
if (ctx) {
|
|
3449
|
+
ctx.state.replaceTodos(derived.todos);
|
|
3450
|
+
}
|
|
3451
|
+
return {
|
|
3452
|
+
message: `Derived ${derived.todos.length} todo(s):
|
|
3453
|
+
${formatTodosList(derived.todos)}
|
|
3454
|
+
|
|
3455
|
+
${formatPlan(derived.plan)}`
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
case "template": {
|
|
3459
|
+
const subVerb = rest[0] ?? "";
|
|
3460
|
+
const subRest = rest.slice(1).join(" ").trim();
|
|
3461
|
+
if (subVerb === "" || subVerb === "list") {
|
|
3462
|
+
return { message: formatPlanTemplates() };
|
|
3463
|
+
}
|
|
3464
|
+
if (subVerb === "use") {
|
|
3465
|
+
if (!subRest) return { message: "Usage: /plan template use <template-name>" };
|
|
3466
|
+
const template = getPlanTemplate(subRest);
|
|
3467
|
+
if (!template) return { message: `Unknown template "${subRest}". Use /plan template list to see available templates.` };
|
|
3468
|
+
let updated = plan;
|
|
3469
|
+
for (const item of template.items) {
|
|
3470
|
+
({ plan: updated } = addPlanItem(updated, item.title, item.details));
|
|
3471
|
+
}
|
|
3472
|
+
await savePlan(planPath, updated);
|
|
3473
|
+
return { message: `Applied template "${template.name}" (${template.items.length} items):
|
|
3474
|
+
${formatPlan(updated)}` };
|
|
3475
|
+
}
|
|
3476
|
+
return { message: `Unknown template subcommand "${subVerb}". Try: list | use <name>` };
|
|
3477
|
+
}
|
|
3425
3478
|
case "clear": {
|
|
3426
3479
|
const updated = clearPlan(plan);
|
|
3427
3480
|
await savePlan(planPath, updated);
|
|
@@ -3429,7 +3482,7 @@ ${formatPlan(updated)}` };
|
|
|
3429
3482
|
}
|
|
3430
3483
|
default:
|
|
3431
3484
|
return {
|
|
3432
|
-
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | clear`
|
|
3485
|
+
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | promote <id|#> | derive <id|#> | template [list|use <name>] | clear`
|
|
3433
3486
|
};
|
|
3434
3487
|
}
|
|
3435
3488
|
}
|
|
@@ -4107,11 +4160,11 @@ function getHelpMessage() {
|
|
|
4107
4160
|
Run \`/security scan\` to start.`;
|
|
4108
4161
|
}
|
|
4109
4162
|
function makeInstaller(opts, projectRoot, global) {
|
|
4110
|
-
const globalRoot =
|
|
4163
|
+
const globalRoot = path21.join(os6.homedir(), ".wrongstack");
|
|
4111
4164
|
return new SkillInstaller({
|
|
4112
|
-
manifestPath:
|
|
4113
|
-
projectSkillsDir:
|
|
4114
|
-
globalSkillsDir:
|
|
4165
|
+
manifestPath: path21.join(globalRoot, "installed-skills.json"),
|
|
4166
|
+
projectSkillsDir: path21.join(projectRoot, ".wrongstack", "skills"),
|
|
4167
|
+
globalSkillsDir: path21.join(globalRoot, "skills"),
|
|
4115
4168
|
projectHash: projectHash(projectRoot),
|
|
4116
4169
|
skillLoader: opts.skillLoader
|
|
4117
4170
|
});
|
|
@@ -4323,13 +4376,13 @@ var MANIFESTS = [
|
|
|
4323
4376
|
];
|
|
4324
4377
|
async function detectProjectKind(projectRoot) {
|
|
4325
4378
|
try {
|
|
4326
|
-
await
|
|
4379
|
+
await fsp2.access(path21.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
4327
4380
|
return "initialized";
|
|
4328
4381
|
} catch {
|
|
4329
4382
|
}
|
|
4330
4383
|
for (const m of MANIFESTS) {
|
|
4331
4384
|
try {
|
|
4332
|
-
await
|
|
4385
|
+
await fsp2.access(path21.join(projectRoot, m));
|
|
4333
4386
|
return "project";
|
|
4334
4387
|
} catch {
|
|
4335
4388
|
}
|
|
@@ -4337,12 +4390,12 @@ async function detectProjectKind(projectRoot) {
|
|
|
4337
4390
|
return "empty";
|
|
4338
4391
|
}
|
|
4339
4392
|
async function scaffoldAgentsMd(projectRoot) {
|
|
4340
|
-
const dir =
|
|
4341
|
-
const file =
|
|
4393
|
+
const dir = path21.join(projectRoot, ".wrongstack");
|
|
4394
|
+
const file = path21.join(dir, "AGENTS.md");
|
|
4342
4395
|
const facts = await detectProjectFacts(projectRoot);
|
|
4343
4396
|
const body = renderAgentsTemplate(facts);
|
|
4344
|
-
await
|
|
4345
|
-
await
|
|
4397
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
4398
|
+
await fsp2.writeFile(file, body, "utf8");
|
|
4346
4399
|
return file;
|
|
4347
4400
|
}
|
|
4348
4401
|
async function runProjectCheck(opts) {
|
|
@@ -4351,7 +4404,7 @@ async function runProjectCheck(opts) {
|
|
|
4351
4404
|
if (kind === "initialized") {
|
|
4352
4405
|
renderer.write(
|
|
4353
4406
|
`
|
|
4354
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
4407
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path21.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
4355
4408
|
`
|
|
4356
4409
|
);
|
|
4357
4410
|
return true;
|
|
@@ -4378,10 +4431,10 @@ async function runProjectCheck(opts) {
|
|
|
4378
4431
|
}
|
|
4379
4432
|
return true;
|
|
4380
4433
|
}
|
|
4381
|
-
const gitDir =
|
|
4434
|
+
const gitDir = path21.join(projectRoot, ".git");
|
|
4382
4435
|
let hasGit = false;
|
|
4383
4436
|
try {
|
|
4384
|
-
await
|
|
4437
|
+
await fsp2.access(gitDir);
|
|
4385
4438
|
hasGit = true;
|
|
4386
4439
|
} catch {
|
|
4387
4440
|
}
|
|
@@ -4679,14 +4732,14 @@ function summarize(value, name) {
|
|
|
4679
4732
|
if (typeof v === "object" && v !== null) {
|
|
4680
4733
|
const o = v;
|
|
4681
4734
|
if (name === "edit") {
|
|
4682
|
-
const
|
|
4735
|
+
const path22 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4683
4736
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
4684
|
-
return `${
|
|
4737
|
+
return `${path22} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
4685
4738
|
}
|
|
4686
4739
|
if (name === "write") {
|
|
4687
|
-
const
|
|
4740
|
+
const path22 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4688
4741
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
4689
|
-
return bytes !== void 0 ? `${
|
|
4742
|
+
return bytes !== void 0 ? `${path22} ${bytes}B` : path22;
|
|
4690
4743
|
}
|
|
4691
4744
|
if (typeof o["count"] === "number") {
|
|
4692
4745
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -5282,7 +5335,7 @@ async function readKeyInput(deps, intent) {
|
|
|
5282
5335
|
async function loadProviders(deps) {
|
|
5283
5336
|
let raw;
|
|
5284
5337
|
try {
|
|
5285
|
-
raw = await
|
|
5338
|
+
raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
|
|
5286
5339
|
} catch {
|
|
5287
5340
|
return {};
|
|
5288
5341
|
}
|
|
@@ -5298,7 +5351,7 @@ async function loadProviders(deps) {
|
|
|
5298
5351
|
async function mutateProviders(deps, mutator) {
|
|
5299
5352
|
let raw;
|
|
5300
5353
|
try {
|
|
5301
|
-
raw = await
|
|
5354
|
+
raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
|
|
5302
5355
|
} catch {
|
|
5303
5356
|
raw = "{}";
|
|
5304
5357
|
}
|
|
@@ -5498,7 +5551,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
5498
5551
|
});
|
|
5499
5552
|
}
|
|
5500
5553
|
try {
|
|
5501
|
-
await
|
|
5554
|
+
await fsp2.access(deps.paths.secretsKey);
|
|
5502
5555
|
checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
|
|
5503
5556
|
} catch {
|
|
5504
5557
|
checks.push({
|
|
@@ -5508,10 +5561,10 @@ var doctorCmd = async (_args, deps) => {
|
|
|
5508
5561
|
});
|
|
5509
5562
|
}
|
|
5510
5563
|
try {
|
|
5511
|
-
await
|
|
5512
|
-
const probe =
|
|
5513
|
-
await
|
|
5514
|
-
await
|
|
5564
|
+
await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
5565
|
+
const probe = path21.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
5566
|
+
await fsp2.writeFile(probe, "");
|
|
5567
|
+
await fsp2.unlink(probe);
|
|
5515
5568
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
5516
5569
|
} catch (err) {
|
|
5517
5570
|
checks.push({
|
|
@@ -5612,8 +5665,8 @@ var exportCmd = async (args, deps) => {
|
|
|
5612
5665
|
return 1;
|
|
5613
5666
|
}
|
|
5614
5667
|
if (output) {
|
|
5615
|
-
await
|
|
5616
|
-
await
|
|
5668
|
+
await fsp2.mkdir(path21.dirname(path21.resolve(deps.cwd, output)), { recursive: true });
|
|
5669
|
+
await fsp2.writeFile(path21.resolve(deps.cwd, output), rendered, "utf8");
|
|
5617
5670
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
5618
5671
|
`);
|
|
5619
5672
|
} else {
|
|
@@ -5670,17 +5723,17 @@ var initCmd = async (_args, deps) => {
|
|
|
5670
5723
|
} else {
|
|
5671
5724
|
deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
|
|
5672
5725
|
}
|
|
5673
|
-
await
|
|
5726
|
+
await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
5674
5727
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
5675
5728
|
if (apiKey) config.apiKey = apiKey;
|
|
5676
|
-
const keyFile =
|
|
5729
|
+
const keyFile = path21.join(path21.dirname(deps.paths.globalConfig), ".key");
|
|
5677
5730
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
5678
5731
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
5679
5732
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
5680
|
-
await
|
|
5681
|
-
const agentsFile =
|
|
5733
|
+
await fsp2.mkdir(path21.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
5734
|
+
const agentsFile = path21.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
5682
5735
|
try {
|
|
5683
|
-
await
|
|
5736
|
+
await fsp2.access(agentsFile);
|
|
5684
5737
|
} catch {
|
|
5685
5738
|
const detected2 = await detectProjectFacts(deps.projectRoot);
|
|
5686
5739
|
await atomicWrite(agentsFile, renderAgentsTemplate(detected2));
|
|
@@ -5756,7 +5809,7 @@ async function addMcpServer(args, deps) {
|
|
|
5756
5809
|
serverCfg.enabled = enable;
|
|
5757
5810
|
let existing = {};
|
|
5758
5811
|
try {
|
|
5759
|
-
existing = JSON.parse(await
|
|
5812
|
+
existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
|
|
5760
5813
|
} catch {
|
|
5761
5814
|
}
|
|
5762
5815
|
const mcpServers = existing.mcpServers ?? {};
|
|
@@ -5776,7 +5829,7 @@ async function addMcpServer(args, deps) {
|
|
|
5776
5829
|
async function removeMcpServer(name, deps) {
|
|
5777
5830
|
let existing = {};
|
|
5778
5831
|
try {
|
|
5779
|
-
existing = JSON.parse(await
|
|
5832
|
+
existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
|
|
5780
5833
|
} catch {
|
|
5781
5834
|
deps.renderer.writeError("No config file found.\n");
|
|
5782
5835
|
return 1;
|
|
@@ -5897,7 +5950,7 @@ function renderConfiguredPlugins(config) {
|
|
|
5897
5950
|
}
|
|
5898
5951
|
async function readConfig(file) {
|
|
5899
5952
|
try {
|
|
5900
|
-
return JSON.parse(await
|
|
5953
|
+
return JSON.parse(await fsp2.readFile(file, "utf8"));
|
|
5901
5954
|
} catch {
|
|
5902
5955
|
return {};
|
|
5903
5956
|
}
|
|
@@ -5988,9 +6041,9 @@ var usageCmd = async (_args, deps) => {
|
|
|
5988
6041
|
return 0;
|
|
5989
6042
|
};
|
|
5990
6043
|
var projectsCmd = async (_args, deps) => {
|
|
5991
|
-
const projectsRoot =
|
|
6044
|
+
const projectsRoot = path21.join(deps.paths.globalRoot, "projects");
|
|
5992
6045
|
try {
|
|
5993
|
-
const entries = await
|
|
6046
|
+
const entries = await fsp2.readdir(projectsRoot);
|
|
5994
6047
|
if (entries.length === 0) {
|
|
5995
6048
|
deps.renderer.write("No projects tracked.\n");
|
|
5996
6049
|
return 0;
|
|
@@ -5998,7 +6051,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
5998
6051
|
for (const hash of entries) {
|
|
5999
6052
|
try {
|
|
6000
6053
|
const meta = JSON.parse(
|
|
6001
|
-
await
|
|
6054
|
+
await fsp2.readFile(path21.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
6002
6055
|
);
|
|
6003
6056
|
deps.renderer.write(
|
|
6004
6057
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -6141,9 +6194,210 @@ function redactKeys(obj) {
|
|
|
6141
6194
|
}
|
|
6142
6195
|
return out;
|
|
6143
6196
|
}
|
|
6197
|
+
var sessionsFleetCmd = async (args, deps) => {
|
|
6198
|
+
const runId = args.find((a) => !a.startsWith("-"));
|
|
6199
|
+
if (runId) {
|
|
6200
|
+
return showFleetRun(runId, deps);
|
|
6201
|
+
}
|
|
6202
|
+
return listFleetRuns(deps);
|
|
6203
|
+
};
|
|
6204
|
+
async function listFleetRuns(deps) {
|
|
6205
|
+
let entries = [];
|
|
6206
|
+
try {
|
|
6207
|
+
entries = await fsp2.readdir(deps.paths.projectSessions);
|
|
6208
|
+
} catch {
|
|
6209
|
+
deps.renderer.writeError(`Cannot read projectSessions: ${deps.paths.projectSessions}
|
|
6210
|
+
`);
|
|
6211
|
+
return 1;
|
|
6212
|
+
}
|
|
6213
|
+
const runs = [];
|
|
6214
|
+
for (const id of entries) {
|
|
6215
|
+
const runDir = path21.join(deps.paths.projectSessions, id);
|
|
6216
|
+
let stat3;
|
|
6217
|
+
try {
|
|
6218
|
+
stat3 = await fsp2.stat(runDir);
|
|
6219
|
+
} catch {
|
|
6220
|
+
continue;
|
|
6221
|
+
}
|
|
6222
|
+
if (!stat3.isDirectory()) continue;
|
|
6223
|
+
let manifest = false;
|
|
6224
|
+
let checkpoint = false;
|
|
6225
|
+
let subagentCount = 0;
|
|
6226
|
+
let subagentsDir;
|
|
6227
|
+
try {
|
|
6228
|
+
await fsp2.access(path21.join(runDir, "fleet.json"));
|
|
6229
|
+
manifest = true;
|
|
6230
|
+
} catch {
|
|
6231
|
+
}
|
|
6232
|
+
try {
|
|
6233
|
+
await fsp2.access(path21.join(runDir, "checkpoint.json"));
|
|
6234
|
+
checkpoint = true;
|
|
6235
|
+
} catch {
|
|
6236
|
+
}
|
|
6237
|
+
try {
|
|
6238
|
+
subagentsDir = path21.join(runDir, "subagents");
|
|
6239
|
+
const files = await fsp2.readdir(subagentsDir);
|
|
6240
|
+
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
6241
|
+
} catch {
|
|
6242
|
+
}
|
|
6243
|
+
runs.push({ id, manifest, checkpoint, subagents: subagentCount });
|
|
6244
|
+
}
|
|
6245
|
+
if (runs.length === 0) {
|
|
6246
|
+
deps.renderer.write("No fleet runs found.\n");
|
|
6247
|
+
return 0;
|
|
6248
|
+
}
|
|
6249
|
+
deps.renderer.write(color.bold("\nFleet Runs\n") + "\n");
|
|
6250
|
+
for (const r of runs.sort((a, b) => b.id.localeCompare(a.id))) {
|
|
6251
|
+
const checkpointFlag = r.checkpoint ? color.green("\u2713") : color.dim("\u25CB");
|
|
6252
|
+
const manifestFlag = r.manifest ? color.green("\u2713") : color.dim("\u25CB");
|
|
6253
|
+
const subagentInfo = r.subagents > 0 ? color.dim(` ${r.subagents} subagent jsonl`) : "";
|
|
6254
|
+
deps.renderer.write(
|
|
6255
|
+
` ${color.bold(r.id)} ${checkpointFlag} checkpoint ${manifestFlag} manifest${subagentInfo}
|
|
6256
|
+
`
|
|
6257
|
+
);
|
|
6258
|
+
}
|
|
6259
|
+
deps.renderer.write(
|
|
6260
|
+
`
|
|
6261
|
+
${color.dim("Run `wrongstack sessions fleet <runId>` for details.")}
|
|
6262
|
+
`
|
|
6263
|
+
);
|
|
6264
|
+
return 0;
|
|
6265
|
+
}
|
|
6266
|
+
async function showFleetRun(runId, deps) {
|
|
6267
|
+
const runDir = path21.join(deps.paths.projectSessions, runId);
|
|
6268
|
+
let stat3;
|
|
6269
|
+
try {
|
|
6270
|
+
stat3 = await fsp2.stat(runDir);
|
|
6271
|
+
} catch {
|
|
6272
|
+
deps.renderer.writeError(`Fleet run not found: ${runId}
|
|
6273
|
+
`);
|
|
6274
|
+
return 1;
|
|
6275
|
+
}
|
|
6276
|
+
if (!stat3.isDirectory()) {
|
|
6277
|
+
deps.renderer.writeError(`Not a directory: ${runId}
|
|
6278
|
+
`);
|
|
6279
|
+
return 1;
|
|
6280
|
+
}
|
|
6281
|
+
deps.renderer.write(color.bold(`
|
|
6282
|
+
Fleet Run: ${runId}
|
|
6283
|
+
`) + "\n");
|
|
6284
|
+
const manifestPath = path21.join(runDir, "fleet.json");
|
|
6285
|
+
let manifestData = null;
|
|
6286
|
+
try {
|
|
6287
|
+
manifestData = await fsp2.readFile(manifestPath, "utf8");
|
|
6288
|
+
const manifest = JSON.parse(manifestData);
|
|
6289
|
+
const subagents = manifest.subagents ?? [];
|
|
6290
|
+
const tasks = manifest.tasks ?? [];
|
|
6291
|
+
const completed = tasks.filter((t) => t.status === "completed" || t.status === "failed" || t.status === "timeout" || t.status === "stopped");
|
|
6292
|
+
deps.renderer.write(
|
|
6293
|
+
` ${color.green("\u2713")} fleet.json \u2014 ${subagents.length} subagent(s), ${completed.length}/${tasks.length} tasks done
|
|
6294
|
+
`
|
|
6295
|
+
);
|
|
6296
|
+
} catch {
|
|
6297
|
+
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
6298
|
+
`);
|
|
6299
|
+
}
|
|
6300
|
+
const checkpointPath = path21.join(runDir, "checkpoint.json");
|
|
6301
|
+
let checkpointData = null;
|
|
6302
|
+
try {
|
|
6303
|
+
checkpointData = await fsp2.readFile(checkpointPath, "utf8");
|
|
6304
|
+
const snap = JSON.parse(checkpointData);
|
|
6305
|
+
const lockPath = `${checkpointPath}.lock`;
|
|
6306
|
+
let lockStatus = color.dim("\u25CB no lock");
|
|
6307
|
+
try {
|
|
6308
|
+
const lockRaw = await fsp2.readFile(lockPath, "utf8");
|
|
6309
|
+
const lock = JSON.parse(lockRaw);
|
|
6310
|
+
lockStatus = `${color.yellow("\u25B8")} lock held by pid ${lock.pid} on ${lock.hostname} (started ${lock.startedAt})`;
|
|
6311
|
+
} catch {
|
|
6312
|
+
lockStatus = color.green("\u2713 no lock (safe to resume)");
|
|
6313
|
+
}
|
|
6314
|
+
deps.renderer.write(
|
|
6315
|
+
` ${color.green("\u2713")} checkpoint.json \u2014 updated ${snap.updatedAt}, ${snap.spawnCount} spawns, ${snap.tasks?.length ?? 0} tasks tracked
|
|
6316
|
+
${lockStatus}
|
|
6317
|
+
`
|
|
6318
|
+
);
|
|
6319
|
+
} catch {
|
|
6320
|
+
deps.renderer.write(` ${color.dim("\u25CB")} checkpoint.json \u2014 not found
|
|
6321
|
+
`);
|
|
6322
|
+
}
|
|
6323
|
+
if (checkpointData) {
|
|
6324
|
+
try {
|
|
6325
|
+
const snap = JSON.parse(checkpointData);
|
|
6326
|
+
if (snap.subagents?.length) {
|
|
6327
|
+
deps.renderer.write("\n Subagents:\n");
|
|
6328
|
+
for (const s of snap.subagents) {
|
|
6329
|
+
deps.renderer.write(
|
|
6330
|
+
` ${color.cyan(s.id)} ${s.name ? `${s.name} ` : ""}${s.provider ? `(${s.provider}/${s.model})` : ""} spawned ${s.spawnedAt}
|
|
6331
|
+
`
|
|
6332
|
+
);
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
if (snap.tasks?.length) {
|
|
6336
|
+
deps.renderer.write("\n Tasks:\n");
|
|
6337
|
+
for (const t of snap.tasks) {
|
|
6338
|
+
deps.renderer.write(
|
|
6339
|
+
` ${color.dim(t.taskId)} ${t.status} ${t.description ? t.description.slice(0, 50) : "(no description)"}
|
|
6340
|
+
`
|
|
6341
|
+
);
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
} catch {
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
const subagentsDir = path21.join(runDir, "subagents");
|
|
6348
|
+
let subagentFiles = [];
|
|
6349
|
+
try {
|
|
6350
|
+
subagentFiles = await fsp2.readdir(subagentsDir);
|
|
6351
|
+
subagentFiles = subagentFiles.filter((f) => f.endsWith(".jsonl"));
|
|
6352
|
+
} catch {
|
|
6353
|
+
}
|
|
6354
|
+
if (subagentFiles.length > 0) {
|
|
6355
|
+
deps.renderer.write(`
|
|
6356
|
+
Subagent transcripts (${subagentFiles.length}):
|
|
6357
|
+
`);
|
|
6358
|
+
for (const f of subagentFiles.sort()) {
|
|
6359
|
+
const filePath = path21.join(subagentsDir, f);
|
|
6360
|
+
let size;
|
|
6361
|
+
try {
|
|
6362
|
+
const s = await fsp2.stat(filePath);
|
|
6363
|
+
size = s.size;
|
|
6364
|
+
} catch {
|
|
6365
|
+
size = 0;
|
|
6366
|
+
}
|
|
6367
|
+
const sizeStr = size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(1)}MB` : `${(size / 1024).toFixed(0)}KB`;
|
|
6368
|
+
deps.renderer.write(` ${color.dim(f)} ${color.dim(sizeStr)}
|
|
6369
|
+
`);
|
|
6370
|
+
}
|
|
6371
|
+
} else {
|
|
6372
|
+
deps.renderer.write(`
|
|
6373
|
+
${color.dim("\u25CB")} No subagent transcripts
|
|
6374
|
+
`);
|
|
6375
|
+
}
|
|
6376
|
+
const sharedDir = path21.join(runDir, "shared");
|
|
6377
|
+
try {
|
|
6378
|
+
const files = await fsp2.readdir(sharedDir);
|
|
6379
|
+
deps.renderer.write(`
|
|
6380
|
+
Shared scratchpad: ${files.length} file(s)
|
|
6381
|
+
`);
|
|
6382
|
+
} catch {
|
|
6383
|
+
deps.renderer.write(`
|
|
6384
|
+
${color.dim("\u25CB")} No shared scratchpad
|
|
6385
|
+
`);
|
|
6386
|
+
}
|
|
6387
|
+
deps.renderer.write(
|
|
6388
|
+
`
|
|
6389
|
+
${color.dim("Resume: wrongstack --resume " + runId)}
|
|
6390
|
+
`
|
|
6391
|
+
);
|
|
6392
|
+
return 0;
|
|
6393
|
+
}
|
|
6144
6394
|
|
|
6145
6395
|
// src/subcommands/handlers/sessions-config.ts
|
|
6146
|
-
var sessionsCmd = async (
|
|
6396
|
+
var sessionsCmd = async (args, deps) => {
|
|
6397
|
+
const sub = args[0];
|
|
6398
|
+
if (sub === "fleet") {
|
|
6399
|
+
return sessionsFleetCmd(args.slice(1), deps);
|
|
6400
|
+
}
|
|
6147
6401
|
if (!deps.sessionStore) {
|
|
6148
6402
|
deps.renderer.writeError("No session store available.");
|
|
6149
6403
|
return 1;
|
|
@@ -6275,7 +6529,7 @@ function parseRewindFlags(args) {
|
|
|
6275
6529
|
var rewindCmd = async (args, deps) => {
|
|
6276
6530
|
const flags = parseRewindFlags(args);
|
|
6277
6531
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
6278
|
-
const sessionsDir =
|
|
6532
|
+
const sessionsDir = path21.join(wpaths.globalRoot, "sessions");
|
|
6279
6533
|
const rewind = new DefaultSessionRewinder(sessionsDir);
|
|
6280
6534
|
let sessionId = args.find((a) => !a.startsWith("--"));
|
|
6281
6535
|
if (!sessionId) {
|
|
@@ -6485,22 +6739,22 @@ function fmtDuration(ms) {
|
|
|
6485
6739
|
const remMin = m - h * 60;
|
|
6486
6740
|
return `${h}h${remMin}m`;
|
|
6487
6741
|
}
|
|
6488
|
-
function fmtTaskResultLine(r,
|
|
6742
|
+
function fmtTaskResultLine(r, color33) {
|
|
6489
6743
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
6490
6744
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
6491
6745
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
6492
6746
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
6493
|
-
const errKindChip = errKind ?
|
|
6494
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
6747
|
+
const errKindChip = errKind ? color33.dim(` [${errKind}]`) : "";
|
|
6748
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color33.dim(errTail)}` : "";
|
|
6495
6749
|
switch (r.status) {
|
|
6496
6750
|
case "success":
|
|
6497
|
-
return { mark:
|
|
6751
|
+
return { mark: color33.green("\u2713"), stats, tail: "" };
|
|
6498
6752
|
case "timeout":
|
|
6499
|
-
return { mark:
|
|
6753
|
+
return { mark: color33.yellow("\u23F1"), stats: `${color33.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
6500
6754
|
case "stopped":
|
|
6501
|
-
return { mark:
|
|
6755
|
+
return { mark: color33.dim("\u2298"), stats: `${color33.dim("stopped")} ${stats}`, tail: errSnip };
|
|
6502
6756
|
case "failed":
|
|
6503
|
-
return { mark:
|
|
6757
|
+
return { mark: color33.red("\u2717"), stats: `${color33.red("failed")} ${stats}`, tail: errSnip };
|
|
6504
6758
|
}
|
|
6505
6759
|
}
|
|
6506
6760
|
|
|
@@ -6510,7 +6764,7 @@ function resolveBundledSkillsDir() {
|
|
|
6510
6764
|
try {
|
|
6511
6765
|
const req2 = createRequire(import.meta.url);
|
|
6512
6766
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
6513
|
-
return
|
|
6767
|
+
return path21.join(path21.dirname(corePkg), "skills");
|
|
6514
6768
|
} catch {
|
|
6515
6769
|
return void 0;
|
|
6516
6770
|
}
|
|
@@ -7085,6 +7339,8 @@ async function execute(deps) {
|
|
|
7085
7339
|
result = await agent.run(query, { signal: ctrl.signal });
|
|
7086
7340
|
} finally {
|
|
7087
7341
|
process.off("SIGINT", onSigint);
|
|
7342
|
+
const { getProcessRegistry } = await import('@wrongstack/tools');
|
|
7343
|
+
getProcessRegistry().killAll();
|
|
7088
7344
|
}
|
|
7089
7345
|
const after = tokenCounter.total();
|
|
7090
7346
|
const costAfter = tokenCounter.estimateCost().total;
|
|
@@ -7236,7 +7492,7 @@ async function execute(deps) {
|
|
|
7236
7492
|
supportsVision,
|
|
7237
7493
|
attachments,
|
|
7238
7494
|
effectiveMaxContext,
|
|
7239
|
-
projectName:
|
|
7495
|
+
projectName: path21.basename(projectRoot) || void 0,
|
|
7240
7496
|
getAutonomy,
|
|
7241
7497
|
skillLoader
|
|
7242
7498
|
});
|
|
@@ -7254,7 +7510,7 @@ async function execute(deps) {
|
|
|
7254
7510
|
supportsVision,
|
|
7255
7511
|
attachments,
|
|
7256
7512
|
effectiveMaxContext,
|
|
7257
|
-
projectName:
|
|
7513
|
+
projectName: path21.basename(projectRoot) || void 0,
|
|
7258
7514
|
getAutonomy,
|
|
7259
7515
|
skillLoader
|
|
7260
7516
|
});
|
|
@@ -7283,22 +7539,18 @@ var MultiAgentHost = class {
|
|
|
7283
7539
|
this.opts = opts;
|
|
7284
7540
|
}
|
|
7285
7541
|
deps;
|
|
7286
|
-
coordinator;
|
|
7287
|
-
/** Lazily built when `opts.directorMode` is set. Owns its own internal
|
|
7288
|
-
* coordinator; the host's `coordinator` field still points at it so
|
|
7289
|
-
* the rest of the methods don't need to branch. */
|
|
7290
7542
|
director;
|
|
7543
|
+
/** Own FleetManager — created in buildDirector(), used for pending task
|
|
7544
|
+
* tracking so status() can show descriptions without host-side state. */
|
|
7545
|
+
fleetManager;
|
|
7291
7546
|
/** Lazily built alongside the director — produces per-subagent JSONL
|
|
7292
|
-
* writers under `<sessionsRoot>/<runId>/`. Null
|
|
7547
|
+
* writers under `<sessionsRoot>/<runId>/`. Null without sessionsRoot. */
|
|
7293
7548
|
sessionFactory;
|
|
7294
|
-
pending = /* @__PURE__ */ new Map();
|
|
7295
|
-
results = [];
|
|
7296
7549
|
opts;
|
|
7297
7550
|
/**
|
|
7298
|
-
* Populated by `promoteToDirector` when it refuses to promote
|
|
7299
|
-
* because a non-director coordinator is already running). The delegate
|
|
7551
|
+
* Populated by `promoteToDirector` when it refuses to promote. The delegate
|
|
7300
7552
|
* tool reads this through `getPromotionBlockReason` to render an
|
|
7301
|
-
* actionable error instead of a generic "could not
|
|
7553
|
+
* actionable error instead of a generic "Director could not be activated".
|
|
7302
7554
|
*/
|
|
7303
7555
|
promotionBlockReason = null;
|
|
7304
7556
|
/**
|
|
@@ -7311,14 +7563,34 @@ var MultiAgentHost = class {
|
|
|
7311
7563
|
* orchestration tools and `--director` becomes a no-op.
|
|
7312
7564
|
*/
|
|
7313
7565
|
async ensureDirector() {
|
|
7566
|
+
if (this.director) return this.director;
|
|
7314
7567
|
if (!this.opts.directorMode) return null;
|
|
7315
|
-
await this.
|
|
7568
|
+
await this.buildDirector();
|
|
7316
7569
|
return this.director ?? null;
|
|
7317
7570
|
}
|
|
7318
|
-
|
|
7319
|
-
|
|
7571
|
+
/** Access the Director's internal coordinator. Returns the concrete
|
|
7572
|
+
* `DefaultMultiAgentCoordinator` so callers can use class-only surface
|
|
7573
|
+
* (`on`, `setRunner`) that isn't part of the `MultiAgentCoordinator`
|
|
7574
|
+
* interface. */
|
|
7575
|
+
getCoordinator() {
|
|
7576
|
+
return this.director.coordinator;
|
|
7577
|
+
}
|
|
7578
|
+
async buildDirector() {
|
|
7579
|
+
if (this.director) return;
|
|
7320
7580
|
const config = this.deps.configStore.get();
|
|
7321
|
-
|
|
7581
|
+
const fleetManager = new FleetManager({
|
|
7582
|
+
manifestPath: this.opts.manifestPath,
|
|
7583
|
+
sessionsRoot: this.opts.sessionsRoot,
|
|
7584
|
+
directorRunId: this.opts.directorRunId,
|
|
7585
|
+
stateCheckpointPath: this.opts.stateCheckpointPath,
|
|
7586
|
+
sessionWriter: this.opts.sessionWriter,
|
|
7587
|
+
directorBudget: this.opts.directorBudget,
|
|
7588
|
+
manifestDebounceMs: 2e3,
|
|
7589
|
+
checkpointDebounceMs: this.opts.checkpointDebounceMs ?? 250,
|
|
7590
|
+
maxSpawnDepth: 5
|
|
7591
|
+
});
|
|
7592
|
+
this.fleetManager = fleetManager;
|
|
7593
|
+
if (this.opts.sessionsRoot && !this.sessionFactory) {
|
|
7322
7594
|
this.sessionFactory = makeDirectorSessionFactory({
|
|
7323
7595
|
sessionsRoot: this.opts.sessionsRoot,
|
|
7324
7596
|
directorRunId: this.opts.directorRunId
|
|
@@ -7328,69 +7600,45 @@ var MultiAgentHost = class {
|
|
|
7328
7600
|
coordinatorId: randomUUID(),
|
|
7329
7601
|
doneCondition: { type: "all_tasks_done" },
|
|
7330
7602
|
maxConcurrent: 8
|
|
7331
|
-
// No defaultBudget. Caps land on a subagent ONLY when the
|
|
7332
|
-
// orchestrator (delegate-tool / spawn_subagent) or the user
|
|
7333
|
-
// (CLI flag) sets them explicitly. The prior defaults
|
|
7334
|
-
// (1000 tools / 200 iter / 4h) silently killed long autonomous
|
|
7335
|
-
// runs; for a "work until done" director we want no implicit
|
|
7336
|
-
// ceilings. The orchestrator can still cap a single subagent
|
|
7337
|
-
// by passing maxToolCalls/maxIterations through the spawn tool.
|
|
7338
7603
|
};
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7604
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path21.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
7605
|
+
this.director = new Director({
|
|
7606
|
+
config: coordinatorConfig,
|
|
7607
|
+
manifestPath: this.opts.manifestPath,
|
|
7608
|
+
sharedScratchpadPath: defaultScratchpad,
|
|
7609
|
+
stateCheckpointPath: this.opts.stateCheckpointPath,
|
|
7610
|
+
sessionWriter: this.opts.sessionWriter,
|
|
7611
|
+
directorBudget: this.opts.directorBudget,
|
|
7612
|
+
maxBudgetExtensions: this.opts.maxBudgetExtensions,
|
|
7613
|
+
checkpointDebounceMs: this.opts.checkpointDebounceMs,
|
|
7614
|
+
sessionsRoot: this.opts.sessionsRoot,
|
|
7615
|
+
directorRunId: this.opts.directorRunId,
|
|
7616
|
+
maxSpawnDepth: 5,
|
|
7617
|
+
fleetManager
|
|
7618
|
+
// pass so director.fleetManager is never undefined
|
|
7619
|
+
});
|
|
7620
|
+
this.director.on("task.completed", ({ task, result }) => {
|
|
7621
|
+
this.fleetManager?.removePendingTask(task.id);
|
|
7622
|
+
this.emitLifecycleCompleted(task.id, result);
|
|
7623
|
+
});
|
|
7624
|
+
this.director.fleet.filter("budget.threshold_reached", (e) => {
|
|
7625
|
+
const payload = e.payload;
|
|
7626
|
+
this.deps.events.emit("subagent.budget_warning", {
|
|
7627
|
+
subagentId: e.subagentId,
|
|
7628
|
+
kind: payload.kind,
|
|
7629
|
+
used: payload.used,
|
|
7630
|
+
limit: payload.limit
|
|
7358
7631
|
});
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
limit: payload.limit
|
|
7366
|
-
});
|
|
7632
|
+
});
|
|
7633
|
+
this.getCoordinator().on("task.assigned", ({ task, subagentId }) => {
|
|
7634
|
+
this.deps.events.emit("subagent.task_started", {
|
|
7635
|
+
subagentId,
|
|
7636
|
+
taskId: task.id,
|
|
7637
|
+
description: task.description
|
|
7367
7638
|
});
|
|
7368
|
-
|
|
7369
|
-
} else {
|
|
7370
|
-
this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
|
|
7371
|
-
this.coordinator.on(
|
|
7372
|
-
"task.completed",
|
|
7373
|
-
({ task, result }) => {
|
|
7374
|
-
this.results.push(result);
|
|
7375
|
-
this.pending.delete(task.id);
|
|
7376
|
-
this.emitLifecycleCompleted(task.id, result);
|
|
7377
|
-
}
|
|
7378
|
-
);
|
|
7379
|
-
}
|
|
7380
|
-
this.coordinator.on(
|
|
7381
|
-
"task.assigned",
|
|
7382
|
-
({ task, subagentId }) => {
|
|
7383
|
-
this.deps.events.emit("subagent.task_started", {
|
|
7384
|
-
subagentId,
|
|
7385
|
-
taskId: task.id,
|
|
7386
|
-
description: task.description
|
|
7387
|
-
});
|
|
7388
|
-
}
|
|
7389
|
-
);
|
|
7639
|
+
});
|
|
7390
7640
|
const runner = this.buildSubagentRunner(config);
|
|
7391
|
-
|
|
7392
|
-
innerCoord.setRunner(runner);
|
|
7393
|
-
return this.coordinator;
|
|
7641
|
+
this.getCoordinator().setRunner(runner);
|
|
7394
7642
|
}
|
|
7395
7643
|
/**
|
|
7396
7644
|
* Build the per-subagent runner: agent factory → runner. Extracted so
|
|
@@ -7472,7 +7720,7 @@ var MultiAgentHost = class {
|
|
|
7472
7720
|
};
|
|
7473
7721
|
return { agent, events, dispose };
|
|
7474
7722
|
};
|
|
7475
|
-
return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet });
|
|
7723
|
+
return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet ?? NULL_FLEET_BUS });
|
|
7476
7724
|
}
|
|
7477
7725
|
/**
|
|
7478
7726
|
* Build a Provider for a subagent. When `overrideId` is supplied (from
|
|
@@ -7518,7 +7766,7 @@ var MultiAgentHost = class {
|
|
|
7518
7766
|
* the full tool registry.
|
|
7519
7767
|
*/
|
|
7520
7768
|
async spawn(description, opts) {
|
|
7521
|
-
await this.
|
|
7769
|
+
await this.buildDirector();
|
|
7522
7770
|
const subagentConfig = {
|
|
7523
7771
|
name: opts?.name ?? "adhoc",
|
|
7524
7772
|
role: "general",
|
|
@@ -7526,35 +7774,11 @@ var MultiAgentHost = class {
|
|
|
7526
7774
|
model: opts?.model,
|
|
7527
7775
|
tools: opts?.tools
|
|
7528
7776
|
};
|
|
7529
|
-
const transcriptPath = this.sessionFactory ?
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
const taskId2 = randomUUID();
|
|
7533
|
-
this.pending.set(taskId2, { description, subagentId });
|
|
7534
|
-
this.deps.events.emit("subagent.spawned", {
|
|
7535
|
-
subagentId,
|
|
7536
|
-
taskId: taskId2,
|
|
7537
|
-
name: subagentConfig.name,
|
|
7538
|
-
provider: opts?.provider,
|
|
7539
|
-
model: opts?.model,
|
|
7540
|
-
description,
|
|
7541
|
-
transcriptPath
|
|
7542
|
-
});
|
|
7543
|
-
await this.director.assign({
|
|
7544
|
-
id: taskId2,
|
|
7545
|
-
description,
|
|
7546
|
-
subagentId
|
|
7547
|
-
// No maxToolCalls — same reasoning as the spawn config above.
|
|
7548
|
-
// The director / orchestrator owns the budget decision.
|
|
7549
|
-
});
|
|
7550
|
-
return { subagentId, taskId: taskId2 };
|
|
7551
|
-
}
|
|
7552
|
-
const coord = this.coordinator;
|
|
7553
|
-
const spawned = await coord.spawn(subagentConfig);
|
|
7554
|
-
const taskId = randomUUID();
|
|
7555
|
-
this.pending.set(taskId, { description, subagentId: spawned.subagentId });
|
|
7777
|
+
const transcriptPath = this.sessionFactory ? path21.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
7778
|
+
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
|
|
7779
|
+
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
7556
7780
|
this.deps.events.emit("subagent.spawned", {
|
|
7557
|
-
subagentId
|
|
7781
|
+
subagentId,
|
|
7558
7782
|
taskId,
|
|
7559
7783
|
name: subagentConfig.name,
|
|
7560
7784
|
provider: opts?.provider,
|
|
@@ -7562,13 +7786,22 @@ var MultiAgentHost = class {
|
|
|
7562
7786
|
description,
|
|
7563
7787
|
transcriptPath
|
|
7564
7788
|
});
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7789
|
+
return { subagentId, taskId };
|
|
7790
|
+
}
|
|
7791
|
+
/**
|
|
7792
|
+
* Common spawn + assign logic shared by both director mode and raw
|
|
7793
|
+
* coordinator mode. Extracts the identical body from the two branches
|
|
7794
|
+
* in `spawn()` so future changes (e.g. adding a new field to both
|
|
7795
|
+
* paths) are made in one place.
|
|
7796
|
+
*
|
|
7797
|
+
* Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
|
|
7798
|
+
* and event emission — the helper only talks to the coordinator.
|
|
7799
|
+
*/
|
|
7800
|
+
async _spawnAndAssign(subagentConfig) {
|
|
7801
|
+
const taskId = randomUUID();
|
|
7802
|
+
const subagentId = await this.director.spawn(subagentConfig);
|
|
7803
|
+
await this.director.assign({ id: taskId, description: "", subagentId });
|
|
7804
|
+
return { subagentId, taskId };
|
|
7572
7805
|
}
|
|
7573
7806
|
/**
|
|
7574
7807
|
* Relay a `task.completed` notification (from either the Director or
|
|
@@ -7590,21 +7823,25 @@ var MultiAgentHost = class {
|
|
|
7590
7823
|
});
|
|
7591
7824
|
}
|
|
7592
7825
|
status() {
|
|
7593
|
-
const
|
|
7594
|
-
taskId,
|
|
7595
|
-
description: v.description,
|
|
7596
|
-
subagentId: v.subagentId
|
|
7597
|
-
}));
|
|
7826
|
+
const activeSubagentIds = /* @__PURE__ */ new Set();
|
|
7598
7827
|
const live = [];
|
|
7599
|
-
if (this.
|
|
7600
|
-
const
|
|
7828
|
+
if (this.director) {
|
|
7829
|
+
const coord = this.getCoordinator();
|
|
7830
|
+
const s = coord.getStatus();
|
|
7601
7831
|
for (const a of s.subagents) {
|
|
7832
|
+
if (a.status === "running" || a.status === "idle") {
|
|
7833
|
+
activeSubagentIds.add(a.id);
|
|
7834
|
+
}
|
|
7602
7835
|
live.push({ subagentId: a.id, status: a.status, task: a.currentTask });
|
|
7603
7836
|
}
|
|
7604
7837
|
}
|
|
7838
|
+
const fleetStatus = this.fleetManager?.getFleetStatus() ?? { pending: []};
|
|
7839
|
+
const pending = fleetStatus.pending.filter((p) => activeSubagentIds.has(p.subagentId));
|
|
7840
|
+
const completed = this.director ? this.director.completedResults() : [];
|
|
7841
|
+
const completedCount = completed.length;
|
|
7605
7842
|
const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
|
|
7606
|
-
const summary = !this.
|
|
7607
|
-
return { pending, completed
|
|
7843
|
+
const summary = !this.director ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${completedCount} completed.` : `${pending.length} pending, ${completedCount} completed.`;
|
|
7844
|
+
return { pending, completed, live, summary };
|
|
7608
7845
|
}
|
|
7609
7846
|
/**
|
|
7610
7847
|
* Roll up per-subagent runtime cost from completed TaskResults. We don't
|
|
@@ -7617,8 +7854,9 @@ var MultiAgentHost = class {
|
|
|
7617
7854
|
* the table renders the most interesting subagent at the top.
|
|
7618
7855
|
*/
|
|
7619
7856
|
usage() {
|
|
7857
|
+
const completed = this.director ? this.director.completedResults() : [];
|
|
7620
7858
|
const bySubagent = /* @__PURE__ */ new Map();
|
|
7621
|
-
for (const r of
|
|
7859
|
+
for (const r of completed) {
|
|
7622
7860
|
const cur = bySubagent.get(r.subagentId) ?? {
|
|
7623
7861
|
tasks: 0,
|
|
7624
7862
|
iterations: 0,
|
|
@@ -7664,7 +7902,7 @@ var MultiAgentHost = class {
|
|
|
7664
7902
|
*/
|
|
7665
7903
|
async manifest() {
|
|
7666
7904
|
if (!this.director) return null;
|
|
7667
|
-
return this.director.writeManifest();
|
|
7905
|
+
return await this.director.fleetManager?.writeManifest() ?? null;
|
|
7668
7906
|
}
|
|
7669
7907
|
/**
|
|
7670
7908
|
* Promote a non-director session to director mode at runtime. Only
|
|
@@ -7677,25 +7915,18 @@ var MultiAgentHost = class {
|
|
|
7677
7915
|
*/
|
|
7678
7916
|
async promoteToDirector() {
|
|
7679
7917
|
if (this.director) return this.director;
|
|
7680
|
-
if (this.coordinator) {
|
|
7681
|
-
const status = this.coordinator.getStatus();
|
|
7682
|
-
const running = status.subagents.filter((s) => s.status === "running").length;
|
|
7683
|
-
const idle = status.subagents.filter((s) => s.status === "idle").length;
|
|
7684
|
-
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.`;
|
|
7685
|
-
return null;
|
|
7686
|
-
}
|
|
7687
7918
|
this.opts.directorMode = true;
|
|
7688
7919
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
7689
|
-
this.opts.manifestPath =
|
|
7920
|
+
this.opts.manifestPath = path21.join(this.opts.fleetRoot, "fleet.json");
|
|
7690
7921
|
}
|
|
7691
7922
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
7692
|
-
this.opts.sharedScratchpadPath =
|
|
7923
|
+
this.opts.sharedScratchpadPath = path21.join(this.opts.fleetRoot, "shared");
|
|
7693
7924
|
}
|
|
7694
7925
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
7695
|
-
this.opts.sessionsRoot =
|
|
7926
|
+
this.opts.sessionsRoot = path21.join(this.opts.fleetRoot, "subagents");
|
|
7696
7927
|
}
|
|
7697
7928
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
7698
|
-
this.opts.stateCheckpointPath =
|
|
7929
|
+
this.opts.stateCheckpointPath = path21.join(this.opts.fleetRoot, "director-state.json");
|
|
7699
7930
|
}
|
|
7700
7931
|
await this.ensureDirector();
|
|
7701
7932
|
return this.director ?? null;
|
|
@@ -7725,13 +7956,13 @@ var MultiAgentHost = class {
|
|
|
7725
7956
|
* called /fleet kill before any /spawn, and there's nothing to do.
|
|
7726
7957
|
*/
|
|
7727
7958
|
async kill(subagentId) {
|
|
7728
|
-
if (!this.
|
|
7729
|
-
await this.
|
|
7959
|
+
if (!this.director) return false;
|
|
7960
|
+
await this.getCoordinator().stop(subagentId);
|
|
7730
7961
|
return true;
|
|
7731
7962
|
}
|
|
7732
7963
|
async stopAll() {
|
|
7733
|
-
if (this.
|
|
7734
|
-
await this.
|
|
7964
|
+
if (this.director) {
|
|
7965
|
+
await this.getCoordinator().stopAll();
|
|
7735
7966
|
}
|
|
7736
7967
|
}
|
|
7737
7968
|
};
|
|
@@ -7816,11 +8047,11 @@ var SessionStats = class {
|
|
|
7816
8047
|
if (e.name === "bash") this.bashCommands++;
|
|
7817
8048
|
else if (e.name === "fetch") this.fetches++;
|
|
7818
8049
|
if (!e.ok) return;
|
|
7819
|
-
const
|
|
7820
|
-
if (e.name === "read" &&
|
|
7821
|
-
else if (e.name === "edit" &&
|
|
7822
|
-
else if (e.name === "write" &&
|
|
7823
|
-
this.writtenPaths.add(
|
|
8050
|
+
const path22 = typeof input?.path === "string" ? input.path : void 0;
|
|
8051
|
+
if (e.name === "read" && path22) this.readPaths.add(path22);
|
|
8052
|
+
else if (e.name === "edit" && path22) this.editedPaths.add(path22);
|
|
8053
|
+
else if (e.name === "write" && path22) {
|
|
8054
|
+
this.writtenPaths.add(path22);
|
|
7824
8055
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
7825
8056
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
7826
8057
|
}
|
|
@@ -8151,12 +8382,12 @@ async function setupSession(params) {
|
|
|
8151
8382
|
}
|
|
8152
8383
|
const sessionRef = { current: session };
|
|
8153
8384
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
8154
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
8155
|
-
const queueStore = new QueueStore({ dir:
|
|
8385
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path21.join(wpaths.projectSessions, session.id, "attachments") });
|
|
8386
|
+
const queueStore = new QueueStore({ dir: path21.join(wpaths.projectSessions, session.id) });
|
|
8156
8387
|
const ctxSignal = new AbortController().signal;
|
|
8157
8388
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
8158
8389
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
8159
|
-
const todosCheckpointPath =
|
|
8390
|
+
const todosCheckpointPath = path21.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
8160
8391
|
if (resumeId) {
|
|
8161
8392
|
try {
|
|
8162
8393
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -8168,12 +8399,13 @@ async function setupSession(params) {
|
|
|
8168
8399
|
}
|
|
8169
8400
|
}
|
|
8170
8401
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
8171
|
-
const planPath =
|
|
8402
|
+
const planPath = path21.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
8172
8403
|
context.state.setMeta("plan.path", planPath);
|
|
8404
|
+
let dirState;
|
|
8173
8405
|
if (resumeId) {
|
|
8174
8406
|
try {
|
|
8175
|
-
const fleetRoot =
|
|
8176
|
-
|
|
8407
|
+
const fleetRoot = path21.join(wpaths.projectSessions, session.id);
|
|
8408
|
+
dirState = await loadDirectorState(path21.join(fleetRoot, "director-state.json"));
|
|
8177
8409
|
if (dirState) {
|
|
8178
8410
|
const tCounts = {};
|
|
8179
8411
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -8192,7 +8424,7 @@ async function setupSession(params) {
|
|
|
8192
8424
|
} catch {
|
|
8193
8425
|
}
|
|
8194
8426
|
}
|
|
8195
|
-
return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint };
|
|
8427
|
+
return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint, priorFleetState: dirState ?? void 0 };
|
|
8196
8428
|
}
|
|
8197
8429
|
|
|
8198
8430
|
// src/index.ts
|
|
@@ -8200,7 +8432,7 @@ function resolveBundledSkillsDir2() {
|
|
|
8200
8432
|
try {
|
|
8201
8433
|
const req2 = createRequire(import.meta.url);
|
|
8202
8434
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
8203
|
-
return
|
|
8435
|
+
return path21.join(path21.dirname(corePkg), "skills");
|
|
8204
8436
|
} catch {
|
|
8205
8437
|
return void 0;
|
|
8206
8438
|
}
|
|
@@ -8307,7 +8539,7 @@ async function main(argv) {
|
|
|
8307
8539
|
modeId,
|
|
8308
8540
|
modePrompt,
|
|
8309
8541
|
modelCapabilities,
|
|
8310
|
-
planPath: () => sessionRef.current ?
|
|
8542
|
+
planPath: () => sessionRef.current ? path21.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
8311
8543
|
})
|
|
8312
8544
|
);
|
|
8313
8545
|
const toolRegistry = new ToolRegistry();
|
|
@@ -8335,7 +8567,7 @@ async function main(argv) {
|
|
|
8335
8567
|
name: "session-store",
|
|
8336
8568
|
check: async () => {
|
|
8337
8569
|
try {
|
|
8338
|
-
await
|
|
8570
|
+
await fsp2.access(wpaths.projectSessions);
|
|
8339
8571
|
return { status: "healthy" };
|
|
8340
8572
|
} catch (e) {
|
|
8341
8573
|
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
@@ -8352,7 +8584,7 @@ async function main(argv) {
|
|
|
8352
8584
|
const dumpMetrics = () => {
|
|
8353
8585
|
if (!metricsSink) return;
|
|
8354
8586
|
try {
|
|
8355
|
-
const out =
|
|
8587
|
+
const out = path21.join(wpaths.projectSessions, "metrics.json");
|
|
8356
8588
|
const snap = metricsSink.snapshot();
|
|
8357
8589
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
8358
8590
|
} catch {
|
|
@@ -8469,6 +8701,7 @@ async function main(argv) {
|
|
|
8469
8701
|
const queueStore = sessResult.queueStore;
|
|
8470
8702
|
const planPath = sessResult.planPath;
|
|
8471
8703
|
const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
|
|
8704
|
+
const priorFleetState = sessResult.priorFleetState;
|
|
8472
8705
|
const stats = new SessionStats(events, tokenCounter);
|
|
8473
8706
|
const errorRing = [];
|
|
8474
8707
|
events.on("error", (e) => {
|
|
@@ -8568,15 +8801,15 @@ async function main(argv) {
|
|
|
8568
8801
|
return err instanceof Error ? err.message : String(err);
|
|
8569
8802
|
}
|
|
8570
8803
|
};
|
|
8571
|
-
const directorMode = flags["director"] === true;
|
|
8804
|
+
const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
|
|
8572
8805
|
let director = null;
|
|
8573
8806
|
let autonomyMode = "off";
|
|
8574
|
-
const fleetRoot = directorMode ?
|
|
8575
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
8576
|
-
const sharedScratchpadPath = directorMode ?
|
|
8577
|
-
const subagentSessionsRoot = directorMode ?
|
|
8578
|
-
const stateCheckpointPath = directorMode ?
|
|
8579
|
-
const fleetRootForPromotion =
|
|
8807
|
+
const fleetRoot = directorMode ? path21.join(wpaths.projectSessions, session.id) : void 0;
|
|
8808
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path21.join(fleetRoot, "fleet.json") : void 0;
|
|
8809
|
+
const sharedScratchpadPath = directorMode ? path21.join(fleetRoot, "shared") : void 0;
|
|
8810
|
+
const subagentSessionsRoot = directorMode ? path21.join(fleetRoot, "subagents") : void 0;
|
|
8811
|
+
const stateCheckpointPath = directorMode ? path21.join(fleetRoot, "director-state.json") : void 0;
|
|
8812
|
+
const fleetRootForPromotion = path21.join(wpaths.projectSessions, session.id);
|
|
8580
8813
|
const multiAgentHost = new MultiAgentHost(
|
|
8581
8814
|
{
|
|
8582
8815
|
container,
|
|
@@ -8616,6 +8849,7 @@ async function main(argv) {
|
|
|
8616
8849
|
if (directorMode) {
|
|
8617
8850
|
director = await multiAgentHost.ensureDirector();
|
|
8618
8851
|
if (director) {
|
|
8852
|
+
if (priorFleetState) director.setCheckpointState(priorFleetState);
|
|
8619
8853
|
for (const tool of director.tools(FLEET_ROSTER)) {
|
|
8620
8854
|
toolRegistry.register(tool);
|
|
8621
8855
|
}
|
|
@@ -8759,32 +8993,32 @@ async function main(argv) {
|
|
|
8759
8993
|
return `Unknown fleet action: ${action}`;
|
|
8760
8994
|
},
|
|
8761
8995
|
onFleetLog: async (subagentId, mode) => {
|
|
8762
|
-
const subagentsRoot =
|
|
8996
|
+
const subagentsRoot = path21.join(fleetRootForPromotion, "subagents");
|
|
8763
8997
|
let runDirs;
|
|
8764
8998
|
try {
|
|
8765
|
-
runDirs = await
|
|
8999
|
+
runDirs = await fsp2.readdir(subagentsRoot);
|
|
8766
9000
|
} catch {
|
|
8767
9001
|
return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
|
|
8768
9002
|
}
|
|
8769
9003
|
const found = [];
|
|
8770
9004
|
for (const runId of runDirs) {
|
|
8771
|
-
const runDir =
|
|
9005
|
+
const runDir = path21.join(subagentsRoot, runId);
|
|
8772
9006
|
let files;
|
|
8773
9007
|
try {
|
|
8774
|
-
files = await
|
|
9008
|
+
files = await fsp2.readdir(runDir);
|
|
8775
9009
|
} catch {
|
|
8776
9010
|
continue;
|
|
8777
9011
|
}
|
|
8778
9012
|
for (const f of files) {
|
|
8779
9013
|
if (!f.endsWith(".jsonl")) continue;
|
|
8780
|
-
const full =
|
|
9014
|
+
const full = path21.join(runDir, f);
|
|
8781
9015
|
try {
|
|
8782
|
-
const
|
|
9016
|
+
const stat3 = await fsp2.stat(full);
|
|
8783
9017
|
found.push({
|
|
8784
9018
|
runId,
|
|
8785
9019
|
subagentId: f.replace(/\.jsonl$/, ""),
|
|
8786
9020
|
file: full,
|
|
8787
|
-
size:
|
|
9021
|
+
size: stat3.size
|
|
8788
9022
|
});
|
|
8789
9023
|
} catch {
|
|
8790
9024
|
}
|
|
@@ -8818,7 +9052,7 @@ async function main(argv) {
|
|
|
8818
9052
|
].join("\n");
|
|
8819
9053
|
}
|
|
8820
9054
|
const t = matches[0];
|
|
8821
|
-
const raw = await
|
|
9055
|
+
const raw = await fsp2.readFile(t.file, "utf8");
|
|
8822
9056
|
if (mode === "raw") return raw;
|
|
8823
9057
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
8824
9058
|
const counts = {};
|
|
@@ -8874,7 +9108,7 @@ async function main(argv) {
|
|
|
8874
9108
|
}
|
|
8875
9109
|
const dir = await multiAgentHost.ensureDirector();
|
|
8876
9110
|
if (!dir) return "Director is not available.";
|
|
8877
|
-
const dirStatePath =
|
|
9111
|
+
const dirStatePath = path21.join(fleetRootForPromotion, "director-state.json");
|
|
8878
9112
|
const prior = await loadDirectorState(dirStatePath);
|
|
8879
9113
|
if (!prior) {
|
|
8880
9114
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -8945,9 +9179,9 @@ async function main(argv) {
|
|
|
8945
9179
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
8946
9180
|
toolRegistry.register(tool);
|
|
8947
9181
|
}
|
|
8948
|
-
const mp =
|
|
8949
|
-
const sp =
|
|
8950
|
-
const ss =
|
|
9182
|
+
const mp = path21.join(fleetRootForPromotion, "fleet.json");
|
|
9183
|
+
const sp = path21.join(fleetRootForPromotion, "shared");
|
|
9184
|
+
const ss = path21.join(fleetRootForPromotion, "subagents");
|
|
8951
9185
|
const lines = [
|
|
8952
9186
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
8953
9187
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|