@wrongstack/cli 0.5.5 → 0.5.6
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 +437 -165
- 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, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan,
|
|
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, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, 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
|
}
|
|
@@ -7236,7 +7490,7 @@ async function execute(deps) {
|
|
|
7236
7490
|
supportsVision,
|
|
7237
7491
|
attachments,
|
|
7238
7492
|
effectiveMaxContext,
|
|
7239
|
-
projectName:
|
|
7493
|
+
projectName: path21.basename(projectRoot) || void 0,
|
|
7240
7494
|
getAutonomy,
|
|
7241
7495
|
skillLoader
|
|
7242
7496
|
});
|
|
@@ -7254,7 +7508,7 @@ async function execute(deps) {
|
|
|
7254
7508
|
supportsVision,
|
|
7255
7509
|
attachments,
|
|
7256
7510
|
effectiveMaxContext,
|
|
7257
|
-
projectName:
|
|
7511
|
+
projectName: path21.basename(projectRoot) || void 0,
|
|
7258
7512
|
getAutonomy,
|
|
7259
7513
|
skillLoader
|
|
7260
7514
|
});
|
|
@@ -7337,12 +7591,18 @@ var MultiAgentHost = class {
|
|
|
7337
7591
|
// by passing maxToolCalls/maxIterations through the spawn tool.
|
|
7338
7592
|
};
|
|
7339
7593
|
if (this.opts.directorMode) {
|
|
7594
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path21.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
7340
7595
|
this.director = new Director({
|
|
7341
7596
|
config: coordinatorConfig,
|
|
7342
7597
|
manifestPath: this.opts.manifestPath,
|
|
7343
|
-
sharedScratchpadPath:
|
|
7598
|
+
sharedScratchpadPath: defaultScratchpad,
|
|
7344
7599
|
stateCheckpointPath: this.opts.stateCheckpointPath,
|
|
7345
7600
|
sessionWriter: this.opts.sessionWriter,
|
|
7601
|
+
directorBudget: this.opts.directorBudget,
|
|
7602
|
+
maxBudgetExtensions: this.opts.maxBudgetExtensions,
|
|
7603
|
+
checkpointDebounceMs: this.opts.checkpointDebounceMs,
|
|
7604
|
+
sessionsRoot: this.opts.sessionsRoot,
|
|
7605
|
+
directorRunId: this.opts.directorRunId,
|
|
7346
7606
|
// Autonomy: allow nested directors a few levels deep. Default
|
|
7347
7607
|
// is 2 (root + one tier of workers), which trips the moment a
|
|
7348
7608
|
// worker tries to recurse into "let me delegate the parser
|
|
@@ -7526,7 +7786,7 @@ var MultiAgentHost = class {
|
|
|
7526
7786
|
model: opts?.model,
|
|
7527
7787
|
tools: opts?.tools
|
|
7528
7788
|
};
|
|
7529
|
-
const transcriptPath = this.sessionFactory ?
|
|
7789
|
+
const transcriptPath = this.sessionFactory ? path21.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
7530
7790
|
if (this.director) {
|
|
7531
7791
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
7532
7792
|
const taskId2 = randomUUID();
|
|
@@ -7590,7 +7850,16 @@ var MultiAgentHost = class {
|
|
|
7590
7850
|
});
|
|
7591
7851
|
}
|
|
7592
7852
|
status() {
|
|
7593
|
-
const
|
|
7853
|
+
const activeSubagentIds = /* @__PURE__ */ new Set();
|
|
7854
|
+
if (this.coordinator) {
|
|
7855
|
+
const s = this.coordinator.getStatus();
|
|
7856
|
+
for (const a of s.subagents) {
|
|
7857
|
+
if (a.status === "running" || a.status === "idle") {
|
|
7858
|
+
activeSubagentIds.add(a.id);
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
const pending = Array.from(this.pending.entries()).filter(([, v]) => activeSubagentIds.has(v.subagentId)).map(([taskId, v]) => ({
|
|
7594
7863
|
taskId,
|
|
7595
7864
|
description: v.description,
|
|
7596
7865
|
subagentId: v.subagentId
|
|
@@ -7686,16 +7955,16 @@ var MultiAgentHost = class {
|
|
|
7686
7955
|
}
|
|
7687
7956
|
this.opts.directorMode = true;
|
|
7688
7957
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
7689
|
-
this.opts.manifestPath =
|
|
7958
|
+
this.opts.manifestPath = path21.join(this.opts.fleetRoot, "fleet.json");
|
|
7690
7959
|
}
|
|
7691
7960
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
7692
|
-
this.opts.sharedScratchpadPath =
|
|
7961
|
+
this.opts.sharedScratchpadPath = path21.join(this.opts.fleetRoot, "shared");
|
|
7693
7962
|
}
|
|
7694
7963
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
7695
|
-
this.opts.sessionsRoot =
|
|
7964
|
+
this.opts.sessionsRoot = path21.join(this.opts.fleetRoot, "subagents");
|
|
7696
7965
|
}
|
|
7697
7966
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
7698
|
-
this.opts.stateCheckpointPath =
|
|
7967
|
+
this.opts.stateCheckpointPath = path21.join(this.opts.fleetRoot, "director-state.json");
|
|
7699
7968
|
}
|
|
7700
7969
|
await this.ensureDirector();
|
|
7701
7970
|
return this.director ?? null;
|
|
@@ -7816,11 +8085,11 @@ var SessionStats = class {
|
|
|
7816
8085
|
if (e.name === "bash") this.bashCommands++;
|
|
7817
8086
|
else if (e.name === "fetch") this.fetches++;
|
|
7818
8087
|
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(
|
|
8088
|
+
const path22 = typeof input?.path === "string" ? input.path : void 0;
|
|
8089
|
+
if (e.name === "read" && path22) this.readPaths.add(path22);
|
|
8090
|
+
else if (e.name === "edit" && path22) this.editedPaths.add(path22);
|
|
8091
|
+
else if (e.name === "write" && path22) {
|
|
8092
|
+
this.writtenPaths.add(path22);
|
|
7824
8093
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
7825
8094
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
7826
8095
|
}
|
|
@@ -8151,12 +8420,12 @@ async function setupSession(params) {
|
|
|
8151
8420
|
}
|
|
8152
8421
|
const sessionRef = { current: session };
|
|
8153
8422
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
8154
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
8155
|
-
const queueStore = new QueueStore({ dir:
|
|
8423
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path21.join(wpaths.projectSessions, session.id, "attachments") });
|
|
8424
|
+
const queueStore = new QueueStore({ dir: path21.join(wpaths.projectSessions, session.id) });
|
|
8156
8425
|
const ctxSignal = new AbortController().signal;
|
|
8157
8426
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
8158
8427
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
8159
|
-
const todosCheckpointPath =
|
|
8428
|
+
const todosCheckpointPath = path21.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
8160
8429
|
if (resumeId) {
|
|
8161
8430
|
try {
|
|
8162
8431
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -8168,12 +8437,13 @@ async function setupSession(params) {
|
|
|
8168
8437
|
}
|
|
8169
8438
|
}
|
|
8170
8439
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
8171
|
-
const planPath =
|
|
8440
|
+
const planPath = path21.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
8172
8441
|
context.state.setMeta("plan.path", planPath);
|
|
8442
|
+
let dirState;
|
|
8173
8443
|
if (resumeId) {
|
|
8174
8444
|
try {
|
|
8175
|
-
const fleetRoot =
|
|
8176
|
-
|
|
8445
|
+
const fleetRoot = path21.join(wpaths.projectSessions, session.id);
|
|
8446
|
+
dirState = await loadDirectorState(path21.join(fleetRoot, "director-state.json"));
|
|
8177
8447
|
if (dirState) {
|
|
8178
8448
|
const tCounts = {};
|
|
8179
8449
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -8192,7 +8462,7 @@ async function setupSession(params) {
|
|
|
8192
8462
|
} catch {
|
|
8193
8463
|
}
|
|
8194
8464
|
}
|
|
8195
|
-
return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint };
|
|
8465
|
+
return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint, priorFleetState: dirState ?? void 0 };
|
|
8196
8466
|
}
|
|
8197
8467
|
|
|
8198
8468
|
// src/index.ts
|
|
@@ -8200,7 +8470,7 @@ function resolveBundledSkillsDir2() {
|
|
|
8200
8470
|
try {
|
|
8201
8471
|
const req2 = createRequire(import.meta.url);
|
|
8202
8472
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
8203
|
-
return
|
|
8473
|
+
return path21.join(path21.dirname(corePkg), "skills");
|
|
8204
8474
|
} catch {
|
|
8205
8475
|
return void 0;
|
|
8206
8476
|
}
|
|
@@ -8307,7 +8577,7 @@ async function main(argv) {
|
|
|
8307
8577
|
modeId,
|
|
8308
8578
|
modePrompt,
|
|
8309
8579
|
modelCapabilities,
|
|
8310
|
-
planPath: () => sessionRef.current ?
|
|
8580
|
+
planPath: () => sessionRef.current ? path21.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
8311
8581
|
})
|
|
8312
8582
|
);
|
|
8313
8583
|
const toolRegistry = new ToolRegistry();
|
|
@@ -8335,7 +8605,7 @@ async function main(argv) {
|
|
|
8335
8605
|
name: "session-store",
|
|
8336
8606
|
check: async () => {
|
|
8337
8607
|
try {
|
|
8338
|
-
await
|
|
8608
|
+
await fsp2.access(wpaths.projectSessions);
|
|
8339
8609
|
return { status: "healthy" };
|
|
8340
8610
|
} catch (e) {
|
|
8341
8611
|
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
@@ -8352,7 +8622,7 @@ async function main(argv) {
|
|
|
8352
8622
|
const dumpMetrics = () => {
|
|
8353
8623
|
if (!metricsSink) return;
|
|
8354
8624
|
try {
|
|
8355
|
-
const out =
|
|
8625
|
+
const out = path21.join(wpaths.projectSessions, "metrics.json");
|
|
8356
8626
|
const snap = metricsSink.snapshot();
|
|
8357
8627
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
8358
8628
|
} catch {
|
|
@@ -8469,6 +8739,7 @@ async function main(argv) {
|
|
|
8469
8739
|
const queueStore = sessResult.queueStore;
|
|
8470
8740
|
const planPath = sessResult.planPath;
|
|
8471
8741
|
const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
|
|
8742
|
+
const priorFleetState = sessResult.priorFleetState;
|
|
8472
8743
|
const stats = new SessionStats(events, tokenCounter);
|
|
8473
8744
|
const errorRing = [];
|
|
8474
8745
|
events.on("error", (e) => {
|
|
@@ -8568,15 +8839,15 @@ async function main(argv) {
|
|
|
8568
8839
|
return err instanceof Error ? err.message : String(err);
|
|
8569
8840
|
}
|
|
8570
8841
|
};
|
|
8571
|
-
const directorMode = flags["director"] === true;
|
|
8842
|
+
const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
|
|
8572
8843
|
let director = null;
|
|
8573
8844
|
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 =
|
|
8845
|
+
const fleetRoot = directorMode ? path21.join(wpaths.projectSessions, session.id) : void 0;
|
|
8846
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path21.join(fleetRoot, "fleet.json") : void 0;
|
|
8847
|
+
const sharedScratchpadPath = directorMode ? path21.join(fleetRoot, "shared") : void 0;
|
|
8848
|
+
const subagentSessionsRoot = directorMode ? path21.join(fleetRoot, "subagents") : void 0;
|
|
8849
|
+
const stateCheckpointPath = directorMode ? path21.join(fleetRoot, "director-state.json") : void 0;
|
|
8850
|
+
const fleetRootForPromotion = path21.join(wpaths.projectSessions, session.id);
|
|
8580
8851
|
const multiAgentHost = new MultiAgentHost(
|
|
8581
8852
|
{
|
|
8582
8853
|
container,
|
|
@@ -8616,6 +8887,7 @@ async function main(argv) {
|
|
|
8616
8887
|
if (directorMode) {
|
|
8617
8888
|
director = await multiAgentHost.ensureDirector();
|
|
8618
8889
|
if (director) {
|
|
8890
|
+
if (priorFleetState) director.setCheckpointState(priorFleetState);
|
|
8619
8891
|
for (const tool of director.tools(FLEET_ROSTER)) {
|
|
8620
8892
|
toolRegistry.register(tool);
|
|
8621
8893
|
}
|
|
@@ -8759,32 +9031,32 @@ async function main(argv) {
|
|
|
8759
9031
|
return `Unknown fleet action: ${action}`;
|
|
8760
9032
|
},
|
|
8761
9033
|
onFleetLog: async (subagentId, mode) => {
|
|
8762
|
-
const subagentsRoot =
|
|
9034
|
+
const subagentsRoot = path21.join(fleetRootForPromotion, "subagents");
|
|
8763
9035
|
let runDirs;
|
|
8764
9036
|
try {
|
|
8765
|
-
runDirs = await
|
|
9037
|
+
runDirs = await fsp2.readdir(subagentsRoot);
|
|
8766
9038
|
} catch {
|
|
8767
9039
|
return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
|
|
8768
9040
|
}
|
|
8769
9041
|
const found = [];
|
|
8770
9042
|
for (const runId of runDirs) {
|
|
8771
|
-
const runDir =
|
|
9043
|
+
const runDir = path21.join(subagentsRoot, runId);
|
|
8772
9044
|
let files;
|
|
8773
9045
|
try {
|
|
8774
|
-
files = await
|
|
9046
|
+
files = await fsp2.readdir(runDir);
|
|
8775
9047
|
} catch {
|
|
8776
9048
|
continue;
|
|
8777
9049
|
}
|
|
8778
9050
|
for (const f of files) {
|
|
8779
9051
|
if (!f.endsWith(".jsonl")) continue;
|
|
8780
|
-
const full =
|
|
9052
|
+
const full = path21.join(runDir, f);
|
|
8781
9053
|
try {
|
|
8782
|
-
const
|
|
9054
|
+
const stat3 = await fsp2.stat(full);
|
|
8783
9055
|
found.push({
|
|
8784
9056
|
runId,
|
|
8785
9057
|
subagentId: f.replace(/\.jsonl$/, ""),
|
|
8786
9058
|
file: full,
|
|
8787
|
-
size:
|
|
9059
|
+
size: stat3.size
|
|
8788
9060
|
});
|
|
8789
9061
|
} catch {
|
|
8790
9062
|
}
|
|
@@ -8818,7 +9090,7 @@ async function main(argv) {
|
|
|
8818
9090
|
].join("\n");
|
|
8819
9091
|
}
|
|
8820
9092
|
const t = matches[0];
|
|
8821
|
-
const raw = await
|
|
9093
|
+
const raw = await fsp2.readFile(t.file, "utf8");
|
|
8822
9094
|
if (mode === "raw") return raw;
|
|
8823
9095
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
8824
9096
|
const counts = {};
|
|
@@ -8874,7 +9146,7 @@ async function main(argv) {
|
|
|
8874
9146
|
}
|
|
8875
9147
|
const dir = await multiAgentHost.ensureDirector();
|
|
8876
9148
|
if (!dir) return "Director is not available.";
|
|
8877
|
-
const dirStatePath =
|
|
9149
|
+
const dirStatePath = path21.join(fleetRootForPromotion, "director-state.json");
|
|
8878
9150
|
const prior = await loadDirectorState(dirStatePath);
|
|
8879
9151
|
if (!prior) {
|
|
8880
9152
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -8945,9 +9217,9 @@ async function main(argv) {
|
|
|
8945
9217
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
8946
9218
|
toolRegistry.register(tool);
|
|
8947
9219
|
}
|
|
8948
|
-
const mp =
|
|
8949
|
-
const sp =
|
|
8950
|
-
const ss =
|
|
9220
|
+
const mp = path21.join(fleetRootForPromotion, "fleet.json");
|
|
9221
|
+
const sp = path21.join(fleetRootForPromotion, "shared");
|
|
9222
|
+
const ss = path21.join(fleetRootForPromotion, "subagents");
|
|
8951
9223
|
const lines = [
|
|
8952
9224
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
8953
9225
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|