@wrongstack/cli 0.119.1 → 0.148.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1233 -217
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError } from '@wrongstack/core';
|
|
2
|
+
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens } from '@wrongstack/core';
|
|
3
3
|
import * as fsp4 from 'fs/promises';
|
|
4
4
|
import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
|
|
5
|
-
import * as
|
|
5
|
+
import * as path10 from 'path';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os2 from 'os';
|
|
@@ -11,14 +11,14 @@ import * as crypto2 from 'crypto';
|
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
import { findFreePort, createHttpServer, openBrowser, registerInstance, unregisterInstance } from '@wrongstack/webui/server';
|
|
13
13
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
14
|
-
import { spawn } from 'child_process';
|
|
14
|
+
import { spawn, exec, execFileSync } from 'child_process';
|
|
15
15
|
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
16
16
|
import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
|
|
17
17
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
18
18
|
import { builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
20
|
import * as readline from 'readline';
|
|
21
|
-
import * as
|
|
21
|
+
import * as fs15 from 'fs';
|
|
22
22
|
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
23
23
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
24
24
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
@@ -96,7 +96,7 @@ async function loadConfigProviders(configPath2, vault, opts) {
|
|
|
96
96
|
}
|
|
97
97
|
async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
98
98
|
let raw;
|
|
99
|
-
let
|
|
99
|
+
let fileExists2 = true;
|
|
100
100
|
try {
|
|
101
101
|
raw = await fsp4.readFile(configPath2, "utf8");
|
|
102
102
|
} catch (err) {
|
|
@@ -106,14 +106,14 @@ async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
|
106
106
|
{ cause: err }
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
fileExists2 = false;
|
|
110
110
|
raw = "{}";
|
|
111
111
|
}
|
|
112
112
|
let parsed;
|
|
113
113
|
try {
|
|
114
114
|
parsed = JSON.parse(raw);
|
|
115
115
|
} catch (err) {
|
|
116
|
-
if (
|
|
116
|
+
if (fileExists2) {
|
|
117
117
|
throw new Error(
|
|
118
118
|
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
|
|
119
119
|
{ cause: err }
|
|
@@ -386,7 +386,7 @@ async function findSpec(store, idOrTitle) {
|
|
|
386
386
|
async function gatherProjectContext2(projectRoot) {
|
|
387
387
|
const parts = [];
|
|
388
388
|
try {
|
|
389
|
-
const pkgPath =
|
|
389
|
+
const pkgPath = path10.join(projectRoot, "package.json");
|
|
390
390
|
const pkgRaw = await fsp4.readFile(pkgPath, "utf8");
|
|
391
391
|
const pkg = JSON.parse(pkgRaw);
|
|
392
392
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -402,13 +402,13 @@ async function gatherProjectContext2(projectRoot) {
|
|
|
402
402
|
} catch {
|
|
403
403
|
}
|
|
404
404
|
try {
|
|
405
|
-
const tsconfigPath =
|
|
405
|
+
const tsconfigPath = path10.join(projectRoot, "tsconfig.json");
|
|
406
406
|
await fsp4.access(tsconfigPath);
|
|
407
407
|
parts.push("Language: TypeScript");
|
|
408
408
|
} catch {
|
|
409
409
|
}
|
|
410
410
|
try {
|
|
411
|
-
const srcDir =
|
|
411
|
+
const srcDir = path10.join(projectRoot, "src");
|
|
412
412
|
const entries = await fsp4.readdir(srcDir, { withFileTypes: true });
|
|
413
413
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
414
414
|
if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -1602,7 +1602,7 @@ __export(update_check_exports, {
|
|
|
1602
1602
|
getUpdateNotification: () => getUpdateNotification
|
|
1603
1603
|
});
|
|
1604
1604
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
1605
|
-
return
|
|
1605
|
+
return path10.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
1606
1606
|
}
|
|
1607
1607
|
function currentVersion() {
|
|
1608
1608
|
const req2 = createRequire(import.meta.url);
|
|
@@ -1639,7 +1639,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1639
1639
|
}
|
|
1640
1640
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1641
1641
|
try {
|
|
1642
|
-
const dir =
|
|
1642
|
+
const dir = path10.dirname(cachePath(homeFn));
|
|
1643
1643
|
await fsp4.mkdir(dir, { recursive: true });
|
|
1644
1644
|
await fsp4.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1645
1645
|
} catch {
|
|
@@ -1746,7 +1746,7 @@ async function runWebUI(opts) {
|
|
|
1746
1746
|
try {
|
|
1747
1747
|
const requireFromHere = createRequire(import.meta.url);
|
|
1748
1748
|
const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
|
|
1749
|
-
const distDir =
|
|
1749
|
+
const distDir = path10.resolve(path10.dirname(serverEntry), "..");
|
|
1750
1750
|
httpServer = createHttpServer({ host, distDir, wsPort });
|
|
1751
1751
|
const openUrl = `http://${host}:${httpPort}`;
|
|
1752
1752
|
httpServer?.listen(httpPort, host, () => {
|
|
@@ -1763,7 +1763,7 @@ async function runWebUI(opts) {
|
|
|
1763
1763
|
`[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`): ${err instanceof Error ? err.message : String(err)}. WS bridge still active on ws://${host}:${wsPort}.`
|
|
1764
1764
|
);
|
|
1765
1765
|
}
|
|
1766
|
-
const registryBaseDir = opts.globalConfigPath ?
|
|
1766
|
+
const registryBaseDir = opts.globalConfigPath ? path10.dirname(opts.globalConfigPath) : void 0;
|
|
1767
1767
|
if (opts.projectRoot) {
|
|
1768
1768
|
void registerInstance(
|
|
1769
1769
|
{
|
|
@@ -1772,7 +1772,7 @@ async function runWebUI(opts) {
|
|
|
1772
1772
|
wsPort,
|
|
1773
1773
|
host,
|
|
1774
1774
|
projectRoot: opts.projectRoot,
|
|
1775
|
-
projectName:
|
|
1775
|
+
projectName: path10.basename(opts.projectRoot) || opts.projectRoot,
|
|
1776
1776
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1777
1777
|
url: `http://${host}:${httpPort}`
|
|
1778
1778
|
},
|
|
@@ -2083,8 +2083,7 @@ async function runWebUI(opts) {
|
|
|
2083
2083
|
ws.close();
|
|
2084
2084
|
}
|
|
2085
2085
|
clients.clear();
|
|
2086
|
-
void unregisterInstance(process.pid, registryBaseDir).catch(() => {
|
|
2087
|
-
});
|
|
2086
|
+
void unregisterInstance(process.pid, registryBaseDir).catch((err) => console.debug(`[webui-server] unregister failed: ${err}`));
|
|
2088
2087
|
httpServer?.close();
|
|
2089
2088
|
wss.close(() => {
|
|
2090
2089
|
console.log("[WebUI] Server stopped");
|
|
@@ -2160,6 +2159,12 @@ async function runWebUI(opts) {
|
|
|
2160
2159
|
await handleProviderRemove(ws, m.payload.providerId);
|
|
2161
2160
|
break;
|
|
2162
2161
|
}
|
|
2162
|
+
default: {
|
|
2163
|
+
console.debug(
|
|
2164
|
+
`[WebUI] Unhandled message type: ${String(msg.type)}`
|
|
2165
|
+
);
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2163
2168
|
}
|
|
2164
2169
|
}
|
|
2165
2170
|
async function handleUserMessage(ws, _client, content) {
|
|
@@ -2400,7 +2405,7 @@ async function runWebUI(opts) {
|
|
|
2400
2405
|
}
|
|
2401
2406
|
}
|
|
2402
2407
|
function getVault() {
|
|
2403
|
-
const keyFile =
|
|
2408
|
+
const keyFile = path10.join(path10.dirname(opts.globalConfigPath ?? ""), ".key");
|
|
2404
2409
|
return new DefaultSecretVault({ keyFile });
|
|
2405
2410
|
}
|
|
2406
2411
|
async function loadSavedProviders() {
|
|
@@ -2701,10 +2706,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2701
2706
|
const name = declared.split("@")[0];
|
|
2702
2707
|
if (name) return name;
|
|
2703
2708
|
}
|
|
2704
|
-
if (await pathExists(
|
|
2705
|
-
if (await pathExists(
|
|
2706
|
-
if (await pathExists(
|
|
2707
|
-
if (await pathExists(
|
|
2709
|
+
if (await pathExists(path10.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2710
|
+
if (await pathExists(path10.join(root, "bun.lockb"))) return "bun";
|
|
2711
|
+
if (await pathExists(path10.join(root, "bun.lock"))) return "bun";
|
|
2712
|
+
if (await pathExists(path10.join(root, "yarn.lock"))) return "yarn";
|
|
2708
2713
|
return "npm";
|
|
2709
2714
|
}
|
|
2710
2715
|
function hasUsableScript(scripts, name) {
|
|
@@ -2725,7 +2730,7 @@ function parseMakeTargets(makefile) {
|
|
|
2725
2730
|
async function detectProjectFacts(root) {
|
|
2726
2731
|
const facts = { hints: [] };
|
|
2727
2732
|
try {
|
|
2728
|
-
const pkg = JSON.parse(await fsp4.readFile(
|
|
2733
|
+
const pkg = JSON.parse(await fsp4.readFile(path10.join(root, "package.json"), "utf8"));
|
|
2729
2734
|
const scripts = pkg.scripts ?? {};
|
|
2730
2735
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2731
2736
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2739,14 +2744,14 @@ async function detectProjectFacts(root) {
|
|
|
2739
2744
|
} catch {
|
|
2740
2745
|
}
|
|
2741
2746
|
try {
|
|
2742
|
-
if (!await pathExists(
|
|
2747
|
+
if (!await pathExists(path10.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2743
2748
|
facts.test ??= "pytest";
|
|
2744
2749
|
facts.lint ??= "ruff check .";
|
|
2745
2750
|
facts.hints.push("pyproject.toml");
|
|
2746
2751
|
} catch {
|
|
2747
2752
|
}
|
|
2748
2753
|
try {
|
|
2749
|
-
if (!await pathExists(
|
|
2754
|
+
if (!await pathExists(path10.join(root, "go.mod"))) throw new Error("not go");
|
|
2750
2755
|
facts.build ??= "go build ./...";
|
|
2751
2756
|
facts.test ??= "go test ./...";
|
|
2752
2757
|
facts.run ??= "go run .";
|
|
@@ -2754,7 +2759,7 @@ async function detectProjectFacts(root) {
|
|
|
2754
2759
|
} catch {
|
|
2755
2760
|
}
|
|
2756
2761
|
try {
|
|
2757
|
-
if (!await pathExists(
|
|
2762
|
+
if (!await pathExists(path10.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2758
2763
|
facts.build ??= "cargo build";
|
|
2759
2764
|
facts.test ??= "cargo test";
|
|
2760
2765
|
facts.lint ??= "cargo clippy";
|
|
@@ -2763,7 +2768,7 @@ async function detectProjectFacts(root) {
|
|
|
2763
2768
|
} catch {
|
|
2764
2769
|
}
|
|
2765
2770
|
try {
|
|
2766
|
-
const makefile = await fsp4.readFile(
|
|
2771
|
+
const makefile = await fsp4.readFile(path10.join(root, "Makefile"), "utf8");
|
|
2767
2772
|
const targets = parseMakeTargets(makefile);
|
|
2768
2773
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2769
2774
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -2873,20 +2878,7 @@ function countToolResults(messages) {
|
|
|
2873
2878
|
return count;
|
|
2874
2879
|
}
|
|
2875
2880
|
function estimateTokens(messages) {
|
|
2876
|
-
|
|
2877
|
-
for (const m of messages) {
|
|
2878
|
-
const content = m.content;
|
|
2879
|
-
if (typeof content === "string") {
|
|
2880
|
-
total += Math.ceil(content.length / 4);
|
|
2881
|
-
} else if (Array.isArray(content)) {
|
|
2882
|
-
for (const b of content) {
|
|
2883
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
2884
|
-
else if (b.type === "tool_use" || b.type === "tool_result")
|
|
2885
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
return total;
|
|
2881
|
+
return estimateMessageTokens(messages);
|
|
2890
2882
|
}
|
|
2891
2883
|
|
|
2892
2884
|
// src/slash-commands/auth.ts
|
|
@@ -3071,7 +3063,7 @@ function formatPhaseList(graph) {
|
|
|
3071
3063
|
}
|
|
3072
3064
|
async function gatherProjectContext(projectRoot) {
|
|
3073
3065
|
try {
|
|
3074
|
-
const raw = await fsp4.readFile(
|
|
3066
|
+
const raw = await fsp4.readFile(path10.join(projectRoot, "package.json"), "utf8");
|
|
3075
3067
|
const pkg = JSON.parse(raw);
|
|
3076
3068
|
const parts = [
|
|
3077
3069
|
`Project: ${String(pkg.name ?? "unknown")}`,
|
|
@@ -3187,6 +3179,7 @@ function buildAutoPhaseCommand(opts) {
|
|
|
3187
3179
|
};
|
|
3188
3180
|
}
|
|
3189
3181
|
}
|
|
3182
|
+
return { message: `Unknown subcommand "${sub}". Run \`/autophase\` for usage.` };
|
|
3190
3183
|
}
|
|
3191
3184
|
};
|
|
3192
3185
|
}
|
|
@@ -3874,7 +3867,7 @@ ${formatContextWindowModeList(active)}`;
|
|
|
3874
3867
|
const lines = [
|
|
3875
3868
|
`${color.bold("Context Window")}`,
|
|
3876
3869
|
` messages: ${messages.length} total (${countTurnPairs(messages)} user+assistant pairs)`,
|
|
3877
|
-
` tokens (est): ${estimateTokens(messages).toLocaleString()} (chars
|
|
3870
|
+
` tokens (est): ${estimateTokens(messages).toLocaleString()} (\u2248 chars/3.5)`,
|
|
3878
3871
|
` mode: ${policy ? `${policy.id} (${policy.name})` : "balanced"}`,
|
|
3879
3872
|
` limit: ${formatLimit(readEffectiveLimit(ctx, opts))}`,
|
|
3880
3873
|
` system prompt: ${ctx.systemPrompt.length} block${ctx.systemPrompt.length !== 1 ? "s" : ""}`,
|
|
@@ -3968,6 +3961,94 @@ function formatLimit(limit) {
|
|
|
3968
3961
|
function pct(n) {
|
|
3969
3962
|
return `${Math.round(n * 100)}%`;
|
|
3970
3963
|
}
|
|
3964
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
3965
|
+
var MAX_OUTPUT_LINES = 500;
|
|
3966
|
+
function runCommand(cmd, cwd, timeout) {
|
|
3967
|
+
return new Promise((resolve5) => {
|
|
3968
|
+
exec(
|
|
3969
|
+
cmd,
|
|
3970
|
+
{
|
|
3971
|
+
cwd,
|
|
3972
|
+
timeout,
|
|
3973
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
3974
|
+
// 2 MB
|
|
3975
|
+
windowsHide: true
|
|
3976
|
+
},
|
|
3977
|
+
(error, stdout, stderr) => {
|
|
3978
|
+
resolve5({
|
|
3979
|
+
stdout,
|
|
3980
|
+
stderr,
|
|
3981
|
+
exitCode: error?.code ?? 0,
|
|
3982
|
+
killed: error?.killed ?? false
|
|
3983
|
+
});
|
|
3984
|
+
}
|
|
3985
|
+
);
|
|
3986
|
+
});
|
|
3987
|
+
}
|
|
3988
|
+
function formatOutput(cmd, result, elapsed) {
|
|
3989
|
+
const lines = [];
|
|
3990
|
+
const exitLabel = result.killed ? color.red("TIMEOUT") : result.exitCode === 0 ? color.green("OK") : color.red(`EXIT ${result.exitCode}`);
|
|
3991
|
+
lines.push(`${color.cyan("$")} ${color.bold(cmd)} ${exitLabel} ${color.dim(`${elapsed}ms`)}`);
|
|
3992
|
+
const combined = (result.stdout + result.stderr).trimEnd();
|
|
3993
|
+
if (combined) {
|
|
3994
|
+
const outputLines = combined.split("\n");
|
|
3995
|
+
const truncated = outputLines.length > MAX_OUTPUT_LINES;
|
|
3996
|
+
const shown = truncated ? outputLines.slice(0, MAX_OUTPUT_LINES) : outputLines;
|
|
3997
|
+
lines.push("");
|
|
3998
|
+
lines.push(color.dim("\u2500\u2500"));
|
|
3999
|
+
for (const line of shown) {
|
|
4000
|
+
lines.push(line);
|
|
4001
|
+
}
|
|
4002
|
+
if (truncated) {
|
|
4003
|
+
lines.push(color.dim(`\u2026 (truncated, showing first ${MAX_OUTPUT_LINES} of ${outputLines.length} lines)`));
|
|
4004
|
+
}
|
|
4005
|
+
lines.push(color.dim("\u2500\u2500"));
|
|
4006
|
+
} else {
|
|
4007
|
+
lines.push(color.dim("(no output)"));
|
|
4008
|
+
}
|
|
4009
|
+
return lines.join("\n");
|
|
4010
|
+
}
|
|
4011
|
+
function buildDevCommand(opts) {
|
|
4012
|
+
return {
|
|
4013
|
+
name: "dev",
|
|
4014
|
+
category: "Run",
|
|
4015
|
+
description: "Run a shell command and see the output (LLM does not see it).",
|
|
4016
|
+
argsHint: "<shell command>",
|
|
4017
|
+
help: [
|
|
4018
|
+
"Usage:",
|
|
4019
|
+
" /dev <shell command> Run a command from the chat input.",
|
|
4020
|
+
"",
|
|
4021
|
+
"Examples:",
|
|
4022
|
+
" /dev pnpm release:check",
|
|
4023
|
+
" /dev git diff --stat",
|
|
4024
|
+
" /dev ls -la src/",
|
|
4025
|
+
"",
|
|
4026
|
+
"The command runs in the current working directory. Output is displayed",
|
|
4027
|
+
"in the chat history but is NOT fed to the LLM \u2014 use this for your own",
|
|
4028
|
+
"eyes only. Timeout: 60s. Max output: 500 lines.",
|
|
4029
|
+
"",
|
|
4030
|
+
"This is a convenience shortcut \u2014 equivalent to switching to a terminal",
|
|
4031
|
+
"tab. For commands the LLM should see, use the `exec` tool instead."
|
|
4032
|
+
].join("\n"),
|
|
4033
|
+
async run(args, _ctx) {
|
|
4034
|
+
const cmd = args.trim();
|
|
4035
|
+
if (!cmd) {
|
|
4036
|
+
return { message: `${color.yellow("Usage:")} /dev <shell command>
|
|
4037
|
+
|
|
4038
|
+
Examples:
|
|
4039
|
+
/dev pnpm release:check
|
|
4040
|
+
/dev git diff --stat` };
|
|
4041
|
+
}
|
|
4042
|
+
const cwd = opts.cwd;
|
|
4043
|
+
const startedAt = Date.now();
|
|
4044
|
+
opts.renderer.write(color.dim(`$ ${cmd}`));
|
|
4045
|
+
const result = await runCommand(cmd, cwd, DEFAULT_TIMEOUT_MS);
|
|
4046
|
+
const elapsed = Date.now() - startedAt;
|
|
4047
|
+
const display = formatOutput(cmd, result, elapsed);
|
|
4048
|
+
return { message: display };
|
|
4049
|
+
}
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
3971
4052
|
|
|
3972
4053
|
// src/slash-commands/diag-stats.ts
|
|
3973
4054
|
function buildDiagCommand(opts) {
|
|
@@ -4001,7 +4082,7 @@ function resolvePersistPath(deps) {
|
|
|
4001
4082
|
return deps.globalConfigPath;
|
|
4002
4083
|
}
|
|
4003
4084
|
async function ensureProjectDir(filePath) {
|
|
4004
|
-
const dir =
|
|
4085
|
+
const dir = path10.dirname(filePath);
|
|
4005
4086
|
try {
|
|
4006
4087
|
await fsp4.mkdir(dir, { recursive: true });
|
|
4007
4088
|
} catch {
|
|
@@ -4040,21 +4121,21 @@ async function persistAutonomySetting(deps, mutator) {
|
|
|
4040
4121
|
const targetPath = resolvePersistPath(deps);
|
|
4041
4122
|
await ensureProjectDir(targetPath);
|
|
4042
4123
|
let raw;
|
|
4043
|
-
let
|
|
4124
|
+
let fileExists2 = true;
|
|
4044
4125
|
try {
|
|
4045
4126
|
raw = await fsp4.readFile(targetPath, "utf8");
|
|
4046
4127
|
} catch (err) {
|
|
4047
4128
|
if (err.code !== "ENOENT") {
|
|
4048
4129
|
throw new Error(`Could not read ${targetPath}: ${err.message}`);
|
|
4049
4130
|
}
|
|
4050
|
-
|
|
4131
|
+
fileExists2 = false;
|
|
4051
4132
|
raw = "{}";
|
|
4052
4133
|
}
|
|
4053
4134
|
let parsed;
|
|
4054
4135
|
try {
|
|
4055
4136
|
parsed = JSON.parse(raw);
|
|
4056
4137
|
} catch (err) {
|
|
4057
|
-
if (
|
|
4138
|
+
if (fileExists2) {
|
|
4058
4139
|
throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
|
|
4059
4140
|
}
|
|
4060
4141
|
parsed = {};
|
|
@@ -4077,21 +4158,21 @@ async function persistConfigSetting(deps, mutator) {
|
|
|
4077
4158
|
const targetPath = resolvePersistPath(deps);
|
|
4078
4159
|
await ensureProjectDir(targetPath);
|
|
4079
4160
|
let raw;
|
|
4080
|
-
let
|
|
4161
|
+
let fileExists2 = true;
|
|
4081
4162
|
try {
|
|
4082
4163
|
raw = await fsp4.readFile(targetPath, "utf8");
|
|
4083
4164
|
} catch (err) {
|
|
4084
4165
|
if (err.code !== "ENOENT") {
|
|
4085
4166
|
throw new Error(`Could not read ${targetPath}: ${err.message}`);
|
|
4086
4167
|
}
|
|
4087
|
-
|
|
4168
|
+
fileExists2 = false;
|
|
4088
4169
|
raw = "{}";
|
|
4089
4170
|
}
|
|
4090
4171
|
let parsed;
|
|
4091
4172
|
try {
|
|
4092
4173
|
parsed = JSON.parse(raw);
|
|
4093
4174
|
} catch (err) {
|
|
4094
|
-
if (
|
|
4175
|
+
if (fileExists2) {
|
|
4095
4176
|
throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
|
|
4096
4177
|
}
|
|
4097
4178
|
parsed = {};
|
|
@@ -4110,21 +4191,21 @@ async function persistConfigSetting(deps, mutator) {
|
|
|
4110
4191
|
}
|
|
4111
4192
|
async function persistTelegramConfig(deps, mutator) {
|
|
4112
4193
|
let raw;
|
|
4113
|
-
let
|
|
4194
|
+
let fileExists2 = true;
|
|
4114
4195
|
try {
|
|
4115
4196
|
raw = await fsp4.readFile(deps.globalConfigPath, "utf8");
|
|
4116
4197
|
} catch (err) {
|
|
4117
4198
|
if (err.code !== "ENOENT") {
|
|
4118
4199
|
throw new Error(`Could not read ${deps.globalConfigPath}: ${err.message}`);
|
|
4119
4200
|
}
|
|
4120
|
-
|
|
4201
|
+
fileExists2 = false;
|
|
4121
4202
|
raw = "{}";
|
|
4122
4203
|
}
|
|
4123
4204
|
let parsed;
|
|
4124
4205
|
try {
|
|
4125
4206
|
parsed = JSON.parse(raw);
|
|
4126
4207
|
} catch (err) {
|
|
4127
|
-
if (
|
|
4208
|
+
if (fileExists2) {
|
|
4128
4209
|
throw new Error(`Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`);
|
|
4129
4210
|
}
|
|
4130
4211
|
parsed = {};
|
|
@@ -4137,7 +4218,7 @@ async function persistTelegramConfig(deps, mutator) {
|
|
|
4137
4218
|
decrypted.extensions = extensions;
|
|
4138
4219
|
const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
|
|
4139
4220
|
await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4140
|
-
deps.configStore.update({ extensions
|
|
4221
|
+
deps.configStore.update({ extensions });
|
|
4141
4222
|
}
|
|
4142
4223
|
|
|
4143
4224
|
// src/slash-commands/enhance.ts
|
|
@@ -4218,6 +4299,7 @@ function buildEnhanceCommand(opts) {
|
|
|
4218
4299
|
|
|
4219
4300
|
// src/slash-commands/fix-classifier.ts
|
|
4220
4301
|
var TS = ["typescript-strict"];
|
|
4302
|
+
var TC = ["tech-stack"];
|
|
4221
4303
|
var BH = ["bug-hunter"];
|
|
4222
4304
|
var SS = ["security-scanner"];
|
|
4223
4305
|
var NM = ["node-modern"];
|
|
@@ -4592,6 +4674,90 @@ var P = [
|
|
|
4592
4674
|
detail: "JavaScript runtime error",
|
|
4593
4675
|
conf: 0.95
|
|
4594
4676
|
},
|
|
4677
|
+
// ── Tech stack validation — BEFORE generic dep patterns ─────────────
|
|
4678
|
+
// Technology choice questions: "should I use X?", "is Y deprecated?", etc.
|
|
4679
|
+
{
|
|
4680
|
+
pat: /\b(should I (use|install|add|pick|choose)|is .+ (still (good|maintained|supported|relevant)|deprecated|dead|obsolete|outdated)|what replaces|alternative to|instead of)\b/i,
|
|
4681
|
+
cat: "tech",
|
|
4682
|
+
sub: "tech-choice",
|
|
4683
|
+
lang: void 0,
|
|
4684
|
+
hints: TC,
|
|
4685
|
+
detail: "Technology choice validation",
|
|
4686
|
+
conf: 0.85
|
|
4687
|
+
},
|
|
4688
|
+
{
|
|
4689
|
+
pat: /\bwhat (is the latest|are the latest|version of|versions of)|what version|upgrade to latest|downgrade to|which version of\b/i,
|
|
4690
|
+
cat: "tech",
|
|
4691
|
+
sub: "version-check",
|
|
4692
|
+
lang: void 0,
|
|
4693
|
+
hints: TC,
|
|
4694
|
+
detail: "Version verification needed",
|
|
4695
|
+
conf: 0.9
|
|
4696
|
+
},
|
|
4697
|
+
{
|
|
4698
|
+
pat: /\b(adding|installing|using|switching to|migrating to) (a |an |the )?(package|dependency|library|module|framework|gem|crate)\b/i,
|
|
4699
|
+
cat: "tech",
|
|
4700
|
+
sub: "tech-choice",
|
|
4701
|
+
lang: void 0,
|
|
4702
|
+
hints: TC,
|
|
4703
|
+
detail: "Technology choice validation",
|
|
4704
|
+
conf: 0.8
|
|
4705
|
+
},
|
|
4706
|
+
// Commands that imply tech choices: "pip install X", "cargo add X", etc.
|
|
4707
|
+
{
|
|
4708
|
+
pat: /\b(pip install|pip3 install|pipenv install|poetry add|uv add)\s+[a-zA-Z0-9_-]+/i,
|
|
4709
|
+
cat: "tech",
|
|
4710
|
+
sub: "python-pkg",
|
|
4711
|
+
lang: "python",
|
|
4712
|
+
hints: TC,
|
|
4713
|
+
detail: "Python package choice \u2014 validate before installing",
|
|
4714
|
+
conf: 0.85
|
|
4715
|
+
},
|
|
4716
|
+
{
|
|
4717
|
+
pat: /\b(cargo add|cargo install)\s+[a-zA-Z0-9_-]+/i,
|
|
4718
|
+
cat: "tech",
|
|
4719
|
+
sub: "rust-crate",
|
|
4720
|
+
lang: "rust",
|
|
4721
|
+
hints: TC,
|
|
4722
|
+
detail: "Rust crate choice \u2014 validate before installing",
|
|
4723
|
+
conf: 0.85
|
|
4724
|
+
},
|
|
4725
|
+
{
|
|
4726
|
+
pat: /\b(go get|go install)\s+[a-zA-Z0-9_./-]+/i,
|
|
4727
|
+
cat: "tech",
|
|
4728
|
+
sub: "go-module",
|
|
4729
|
+
lang: "go",
|
|
4730
|
+
hints: TC,
|
|
4731
|
+
detail: "Go module choice \u2014 validate before installing",
|
|
4732
|
+
conf: 0.85
|
|
4733
|
+
},
|
|
4734
|
+
{
|
|
4735
|
+
pat: /\b(gem install|bundle add)\s+[a-zA-Z0-9_-]+/i,
|
|
4736
|
+
cat: "tech",
|
|
4737
|
+
sub: "ruby-gem",
|
|
4738
|
+
lang: "ruby",
|
|
4739
|
+
hints: TC,
|
|
4740
|
+
detail: "Ruby gem choice \u2014 validate before installing",
|
|
4741
|
+
conf: 0.85
|
|
4742
|
+
},
|
|
4743
|
+
{
|
|
4744
|
+
pat: /\b(npm install|pnpm add|yarn add)\s+[a-zA-Z0-9@/_-]+/i,
|
|
4745
|
+
cat: "tech",
|
|
4746
|
+
sub: "js-pkg",
|
|
4747
|
+
lang: "javascript",
|
|
4748
|
+
hints: TC,
|
|
4749
|
+
detail: "JS package choice \u2014 validate before installing",
|
|
4750
|
+
conf: 0.85
|
|
4751
|
+
},
|
|
4752
|
+
{
|
|
4753
|
+
pat: /\b(composer require|nuget install|dotnet add package)\s+[a-zA-Z0-9./_-]+/i,
|
|
4754
|
+
cat: "tech",
|
|
4755
|
+
sub: "pkg-choice",
|
|
4756
|
+
lang: void 0,
|
|
4757
|
+
hints: TC,
|
|
4758
|
+
detail: "Package choice \u2014 validate before installing",
|
|
4759
|
+
conf: 0.85
|
|
4760
|
+
},
|
|
4595
4761
|
// ── Dependency / Import ───────────────────────────────────────────────
|
|
4596
4762
|
{
|
|
4597
4763
|
pat: /\bcannot find module|modulenotfounderror|no such module|missing module/i,
|
|
@@ -4728,7 +4894,7 @@ function needsSubagent(c) {
|
|
|
4728
4894
|
return c.confidence < 0.85;
|
|
4729
4895
|
}
|
|
4730
4896
|
function isSimpleFix(c) {
|
|
4731
|
-
return c.category === "ts" && c.confidence >= 0.9 || c.category === "runtime" && c.subcategory === "null-undefined-access" && c.confidence >= 0.85;
|
|
4897
|
+
return c.category === "ts" && c.confidence >= 0.9 || c.category === "runtime" && c.subcategory === "null-undefined-access" && c.confidence >= 0.85 || c.category === "tech" && c.confidence >= 0.85;
|
|
4732
4898
|
}
|
|
4733
4899
|
|
|
4734
4900
|
// src/slash-commands/fix.ts
|
|
@@ -4824,6 +4990,22 @@ function buildDirective(cli, errorText) {
|
|
|
4824
4990
|
"3. Fix the config",
|
|
4825
4991
|
"4. Verify"
|
|
4826
4992
|
].join("\n");
|
|
4993
|
+
case "tech":
|
|
4994
|
+
return [
|
|
4995
|
+
`## Tech Stack Validation${lang}`,
|
|
4996
|
+
"",
|
|
4997
|
+
"```",
|
|
4998
|
+
`${errorText}`,
|
|
4999
|
+
"```",
|
|
5000
|
+
"",
|
|
5001
|
+
"Your task:",
|
|
5002
|
+
"1. Detect the ecosystem from project files or context",
|
|
5003
|
+
"2. Verify the package/version against the correct registry",
|
|
5004
|
+
"3. Check if the package is dead, deprecated, or superseded (prehistoric)",
|
|
5005
|
+
"4. Check if the language standard library already covers this need",
|
|
5006
|
+
"5. Report APPROVED (with install command) or REJECTED (with replacement + migration)",
|
|
5007
|
+
`6. Use "This isn't code, this is X-year-old technology" when rejecting on age grounds`
|
|
5008
|
+
].join("\n");
|
|
4827
5009
|
case "perf":
|
|
4828
5010
|
return [
|
|
4829
5011
|
`## Fix: Performance / Memory Issue${lang}`,
|
|
@@ -4863,6 +5045,8 @@ function delegateRoleFor(cli) {
|
|
|
4863
5045
|
return "security-scanner";
|
|
4864
5046
|
case "perf":
|
|
4865
5047
|
return "refactor-planner";
|
|
5048
|
+
case "tech":
|
|
5049
|
+
return "tech-stack";
|
|
4866
5050
|
default:
|
|
4867
5051
|
return "bug-hunter";
|
|
4868
5052
|
}
|
|
@@ -4909,7 +5093,8 @@ Docker, Git, CI/CD, and more.
|
|
|
4909
5093
|
| Security / secrets | Any | \`security-scanner\` |
|
|
4910
5094
|
| Compiler error | Rust, Go, C/C++, Python | \`bug-hunter\` |
|
|
4911
5095
|
| Dependency / import | Any | \`bug-hunter\` |
|
|
4912
|
-
|
|
|
5096
|
+
| Tech choice / pkg | Any | \`tech-stack\` |
|
|
5097
|
+
| Performance / leak | Any | \`bug-hunter\` + \`refactor-planner\` |
|
|
4913
5098
|
| Infrastructure | Config, Docker, Git, CI | \`bug-hunter\` |
|
|
4914
5099
|
| React / Next.js | JavaScript | \`react-modern\` |
|
|
4915
5100
|
| Node.js | JavaScript | \`node-modern\` |
|
|
@@ -4931,6 +5116,10 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
|
4931
5116
|
/fix react-dom.development.js:172 Error: Invalid hook call
|
|
4932
5117
|
/fix Security: hardcoded API key in config.ts
|
|
4933
5118
|
/fix ERRO1014: SQL injection vulnerability in query builder
|
|
5119
|
+
/fix Should I use axios for API calls?
|
|
5120
|
+
/fix is moment.js still maintained?
|
|
5121
|
+
/fix pip install urllib2
|
|
5122
|
+
/fix what replaces lodash?
|
|
4934
5123
|
\`\`\`
|
|
4935
5124
|
`,
|
|
4936
5125
|
async run(args, _ctx) {
|
|
@@ -4952,6 +5141,7 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
|
4952
5141
|
` /fix ${color.dim("AttributeError: 'NoneType' object has no attribute 'encode'")}`,
|
|
4953
5142
|
` /fix ${color.dim("react-dom.development.js:172 Error: Invalid hook call")}`,
|
|
4954
5143
|
` /fix ${color.dim("Security: hardcoded API key in config.ts")}`,
|
|
5144
|
+
` /fix ${color.dim("Should I use axios for API calls?")}`,
|
|
4955
5145
|
"",
|
|
4956
5146
|
"Run `/help fix` for full documentation."
|
|
4957
5147
|
].join("\n")
|
|
@@ -5605,6 +5795,7 @@ ${formatGoal(updated)}` };
|
|
|
5605
5795
|
} catch {
|
|
5606
5796
|
}
|
|
5607
5797
|
if (opts.onEternalStop) opts.onEternalStop();
|
|
5798
|
+
if (opts.onAutonomy) opts.onAutonomy("off");
|
|
5608
5799
|
const msg = `${color.amber("Goal cleared.")} Previous goal marked abandoned; eternal mode will stop.`;
|
|
5609
5800
|
opts.renderer.write(msg);
|
|
5610
5801
|
return { message: msg };
|
|
@@ -5749,28 +5940,52 @@ function buildInitCommand(opts) {
|
|
|
5749
5940
|
category: "Config",
|
|
5750
5941
|
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
5751
5942
|
async run(_args, ctx) {
|
|
5752
|
-
const dir =
|
|
5753
|
-
const file =
|
|
5943
|
+
const dir = path10.join(ctx.projectRoot, ".wrongstack");
|
|
5944
|
+
const file = path10.join(dir, "AGENTS.md");
|
|
5945
|
+
const isFirstInit = !await fileExists(file);
|
|
5754
5946
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
5755
5947
|
const body = renderAgentsTemplate(detected);
|
|
5756
5948
|
await fsp4.mkdir(dir, { recursive: true });
|
|
5757
5949
|
await fsp4.writeFile(file, body, "utf8");
|
|
5950
|
+
let nodePkg = false;
|
|
5951
|
+
try {
|
|
5952
|
+
await fsp4.access(path10.join(ctx.projectRoot, "package.json"));
|
|
5953
|
+
nodePkg = true;
|
|
5954
|
+
} catch {
|
|
5955
|
+
}
|
|
5956
|
+
const lines = [];
|
|
5957
|
+
lines.push(`Wrote ${file}`);
|
|
5758
5958
|
if (detected.hints.length > 0) {
|
|
5759
|
-
const msg2 = `Wrote ${file}
|
|
5760
|
-
Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
5761
5959
|
opts.renderer.writeInfo(`Wrote ${file}`);
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
);
|
|
5765
|
-
|
|
5960
|
+
const hintLine = `Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
5961
|
+
opts.renderer.writeInfo(hintLine);
|
|
5962
|
+
lines.push(hintLine);
|
|
5963
|
+
} else {
|
|
5964
|
+
opts.renderer.writeInfo(`Wrote ${file}`);
|
|
5965
|
+
lines.push("No project type auto-detected. Edit the file with project context and instructions the system prompt should carry.");
|
|
5766
5966
|
}
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5967
|
+
if (nodePkg && isFirstInit) {
|
|
5968
|
+
const techHint = [
|
|
5969
|
+
"",
|
|
5970
|
+
`${color.cyan("\u{1F4A1}")} ${color.bold("Tech Stack Audit")} \u2014 This is a Node.js project with a fresh init.`,
|
|
5971
|
+
color.dim(" The LLM may have suggested stale version numbers. Run"),
|
|
5972
|
+
` ${color.cyan("/techstack --init")} to scan dependencies and verify versions.`
|
|
5973
|
+
].join("\n");
|
|
5974
|
+
opts.renderer.write(techHint);
|
|
5975
|
+
lines.push(techHint);
|
|
5976
|
+
}
|
|
5977
|
+
return { message: lines.join("\n") };
|
|
5771
5978
|
}
|
|
5772
5979
|
};
|
|
5773
5980
|
}
|
|
5981
|
+
async function fileExists(filePath) {
|
|
5982
|
+
try {
|
|
5983
|
+
await fsp4.access(filePath);
|
|
5984
|
+
return true;
|
|
5985
|
+
} catch {
|
|
5986
|
+
return false;
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5774
5989
|
function parseMcpArgs(args) {
|
|
5775
5990
|
const trimmed = args.trim();
|
|
5776
5991
|
if (!trimmed || trimmed === "list") return { action: "list", name: "" };
|
|
@@ -5965,9 +6180,9 @@ function stateBadge(state) {
|
|
|
5965
6180
|
return color.dim(state);
|
|
5966
6181
|
}
|
|
5967
6182
|
}
|
|
5968
|
-
async function readConfig(
|
|
6183
|
+
async function readConfig(path28) {
|
|
5969
6184
|
try {
|
|
5970
|
-
return JSON.parse(await fsp4.readFile(
|
|
6185
|
+
return JSON.parse(await fsp4.readFile(path28, "utf8"));
|
|
5971
6186
|
} catch {
|
|
5972
6187
|
return {};
|
|
5973
6188
|
}
|
|
@@ -5975,11 +6190,11 @@ async function readConfig(path27) {
|
|
|
5975
6190
|
function isMcpServerRecord(value) {
|
|
5976
6191
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5977
6192
|
}
|
|
5978
|
-
async function writeConfig(
|
|
6193
|
+
async function writeConfig(path28, cfg) {
|
|
5979
6194
|
const raw = JSON.stringify(cfg, null, 2);
|
|
5980
|
-
const tmp =
|
|
6195
|
+
const tmp = path28 + ".tmp";
|
|
5981
6196
|
await fsp4.writeFile(tmp, raw, "utf8");
|
|
5982
|
-
await fsp4.rename(tmp,
|
|
6197
|
+
await fsp4.rename(tmp, path28);
|
|
5983
6198
|
}
|
|
5984
6199
|
|
|
5985
6200
|
// src/slash-commands/mcp.ts
|
|
@@ -6509,18 +6724,18 @@ var noOpVault2 = {
|
|
|
6509
6724
|
};
|
|
6510
6725
|
async function patchGlobalConfig(globalConfigPath, mutate) {
|
|
6511
6726
|
let raw = "{}";
|
|
6512
|
-
let
|
|
6727
|
+
let fileExists2 = true;
|
|
6513
6728
|
try {
|
|
6514
6729
|
raw = await fsp4.readFile(globalConfigPath, "utf8");
|
|
6515
6730
|
} catch (err) {
|
|
6516
6731
|
if (err.code !== "ENOENT") throw err;
|
|
6517
|
-
|
|
6732
|
+
fileExists2 = false;
|
|
6518
6733
|
}
|
|
6519
6734
|
let parsed;
|
|
6520
6735
|
try {
|
|
6521
6736
|
parsed = JSON.parse(raw);
|
|
6522
6737
|
} catch (err) {
|
|
6523
|
-
if (
|
|
6738
|
+
if (fileExists2) {
|
|
6524
6739
|
throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
|
|
6525
6740
|
}
|
|
6526
6741
|
parsed = {};
|
|
@@ -6727,31 +6942,51 @@ function buildModelsCommand(opts) {
|
|
|
6727
6942
|
function buildNextCommand(opts) {
|
|
6728
6943
|
return {
|
|
6729
6944
|
name: "next",
|
|
6730
|
-
category: "
|
|
6731
|
-
description: "
|
|
6732
|
-
argsHint: "[on|off|toggle]",
|
|
6945
|
+
category: "Agent",
|
|
6946
|
+
description: "Show or select next-step suggestions. /next 1 to execute, /next list to view.",
|
|
6947
|
+
argsHint: "[on|off|toggle|list|clear|1 2 3...]",
|
|
6733
6948
|
help: [
|
|
6734
6949
|
"Usage:",
|
|
6735
|
-
" /next
|
|
6736
|
-
" /next on
|
|
6737
|
-
" /next off
|
|
6738
|
-
" /next toggle
|
|
6950
|
+
" /next Show whether next-task prediction is on or off",
|
|
6951
|
+
" /next on Enable \u2014 after each turn, show 1-3 predicted next steps",
|
|
6952
|
+
" /next off Disable (default)",
|
|
6953
|
+
" /next toggle Flip the current state",
|
|
6954
|
+
" /next list Show the current suggestion list",
|
|
6955
|
+
" /next clear Clear the suggestion list",
|
|
6956
|
+
" /next 1 Execute suggestion #1 as the next agent turn",
|
|
6957
|
+
" /next 1 2 3 Execute suggestions 1, 2, and 3 in sequence",
|
|
6958
|
+
" /next 1,2,3 Same \u2014 comma separators work too",
|
|
6739
6959
|
"",
|
|
6740
|
-
"
|
|
6741
|
-
|
|
6742
|
-
"
|
|
6960
|
+
"Suggestions are generated automatically after each turn (when prediction is",
|
|
6961
|
+
`on) or manually via ${color.cyan("/suggest")}. Selected suggestions execute`,
|
|
6962
|
+
"immediately \u2014 no refinement or classification step."
|
|
6743
6963
|
].join("\n"),
|
|
6744
6964
|
async run(args) {
|
|
6965
|
+
const trimmed = args.trim();
|
|
6966
|
+
if (/^\d[\d,\s]*$/.test(trimmed) && /\d/.test(trimmed)) {
|
|
6967
|
+
return handleSelection(trimmed, opts);
|
|
6968
|
+
}
|
|
6969
|
+
const arg = trimmed.toLowerCase();
|
|
6970
|
+
if (arg === "list" || arg === "ls" || arg === "show") {
|
|
6971
|
+
return handleList(opts);
|
|
6972
|
+
}
|
|
6973
|
+
if (arg === "clear" || arg === "reset") {
|
|
6974
|
+
opts.onSuggestions?.([]);
|
|
6975
|
+
return { message: color.dim("Suggestion list cleared.") };
|
|
6976
|
+
}
|
|
6745
6977
|
if (!opts.onNextPredict) {
|
|
6746
6978
|
const msg2 = "Next-task prediction is not available in this session.";
|
|
6747
6979
|
opts.renderer.writeWarning(msg2);
|
|
6748
6980
|
return { message: msg2 };
|
|
6749
6981
|
}
|
|
6750
|
-
const arg = args.trim().toLowerCase();
|
|
6751
6982
|
const current = opts.onNextPredict();
|
|
6752
6983
|
const label = (on) => on ? `${color.cyan("ON")} ${color.dim("(predicted next steps shown after each turn)")}` : `${color.green("OFF")} ${color.dim("(no predictions)")}`;
|
|
6753
6984
|
if (!arg || arg === "status") {
|
|
6754
|
-
const
|
|
6985
|
+
const suggestions = opts.onSuggestions?.() ?? [];
|
|
6986
|
+
const msg2 = [
|
|
6987
|
+
`Next-task prediction: ${label(current)}`,
|
|
6988
|
+
suggestions.length > 0 ? color.dim(` ${suggestions.length} suggestion(s) available \u2014 use /next list to view, /next 1 to execute`) : color.dim(" No suggestions stored \u2014 use /suggest to generate")
|
|
6989
|
+
].join("\n");
|
|
6755
6990
|
opts.renderer.write(msg2);
|
|
6756
6991
|
return { message: msg2 };
|
|
6757
6992
|
}
|
|
@@ -6763,7 +6998,7 @@ function buildNextCommand(opts) {
|
|
|
6763
6998
|
} else if (arg === "toggle" || arg === "cycle") {
|
|
6764
6999
|
target = !current;
|
|
6765
7000
|
} else {
|
|
6766
|
-
const msg2 = `Unknown argument: ${arg}. Use /next on, off, or
|
|
7001
|
+
const msg2 = `Unknown argument: ${arg}. Use /next on, off, toggle, list, clear, or a number (e.g. /next 1).`;
|
|
6767
7002
|
opts.renderer.writeWarning(msg2);
|
|
6768
7003
|
return { message: msg2 };
|
|
6769
7004
|
}
|
|
@@ -6774,6 +7009,67 @@ function buildNextCommand(opts) {
|
|
|
6774
7009
|
}
|
|
6775
7010
|
};
|
|
6776
7011
|
}
|
|
7012
|
+
function handleSelection(input, opts) {
|
|
7013
|
+
const parts = input.split(/[\s,]+/).filter(Boolean);
|
|
7014
|
+
const indices = parts.map((p) => Number.parseInt(p, 10)).filter((n) => !Number.isNaN(n) && n > 0);
|
|
7015
|
+
if (indices.length === 0) {
|
|
7016
|
+
return { message: color.amber("No valid suggestion numbers found. Use /next 1, /next 1 2 3, etc.") };
|
|
7017
|
+
}
|
|
7018
|
+
const suggestions = opts.onSuggestions?.() ?? [];
|
|
7019
|
+
if (suggestions.length === 0) {
|
|
7020
|
+
return {
|
|
7021
|
+
message: color.amber("No suggestions available. Run /suggest first, or enable prediction with /next on.")
|
|
7022
|
+
};
|
|
7023
|
+
}
|
|
7024
|
+
const invalid = indices.filter((i) => i > suggestions.length);
|
|
7025
|
+
if (invalid.length > 0) {
|
|
7026
|
+
const max = suggestions.length;
|
|
7027
|
+
return {
|
|
7028
|
+
message: color.amber(`Invalid suggestion number(s): ${invalid.join(", ")}. Valid range: 1\u2013${max}.`)
|
|
7029
|
+
};
|
|
7030
|
+
}
|
|
7031
|
+
const selected = indices.map((i) => suggestions[i - 1]).filter((s) => s !== void 0);
|
|
7032
|
+
if (selected.length === 1) {
|
|
7033
|
+
const text = selected[0] ?? "";
|
|
7034
|
+
return {
|
|
7035
|
+
message: `${color.green("\u25B6")} Executing suggestion #${indices[0]}: ${color.dim(text)}`,
|
|
7036
|
+
runText: text
|
|
7037
|
+
};
|
|
7038
|
+
}
|
|
7039
|
+
const tasks = selected.map((s, i) => `${i + 1}. ${s}`).join("\n");
|
|
7040
|
+
const runText = [
|
|
7041
|
+
`## Execute the following tasks in order`,
|
|
7042
|
+
"",
|
|
7043
|
+
tasks,
|
|
7044
|
+
"",
|
|
7045
|
+
"Complete each task before moving to the next. Report results as you go."
|
|
7046
|
+
].join("\n");
|
|
7047
|
+
return {
|
|
7048
|
+
message: `${color.green("\u25B6")} Executing ${selected.length} tasks: ${indices.join(", ")}`,
|
|
7049
|
+
runText
|
|
7050
|
+
};
|
|
7051
|
+
}
|
|
7052
|
+
function handleList(opts) {
|
|
7053
|
+
const suggestions = opts.onSuggestions?.() ?? [];
|
|
7054
|
+
if (suggestions.length === 0) {
|
|
7055
|
+
return {
|
|
7056
|
+
message: [
|
|
7057
|
+
color.dim("No suggestions available."),
|
|
7058
|
+
"",
|
|
7059
|
+
`Generate suggestions with ${color.cyan("/suggest")} or enable`,
|
|
7060
|
+
`prediction with ${color.cyan("/next on")}.`
|
|
7061
|
+
].join("\n")
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7064
|
+
const lines = [
|
|
7065
|
+
` ${color.cyan("\u{1F4A1} Suggestions")} ${color.dim(`(use /next 1, /next 1 2 3 to execute)`)}`,
|
|
7066
|
+
""
|
|
7067
|
+
];
|
|
7068
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
7069
|
+
lines.push(` ${color.bold(`${i + 1}.`)} ${suggestions[i]}`);
|
|
7070
|
+
}
|
|
7071
|
+
return { message: lines.join("\n") };
|
|
7072
|
+
}
|
|
6777
7073
|
|
|
6778
7074
|
// src/slash-commands/plugin.ts
|
|
6779
7075
|
function buildPluginCommand(opts) {
|
|
@@ -7111,18 +7407,18 @@ function fmtEntry(e) {
|
|
|
7111
7407
|
}
|
|
7112
7408
|
async function patchGlobalConfig2(globalConfigPath, mutate) {
|
|
7113
7409
|
let raw = "{}";
|
|
7114
|
-
let
|
|
7410
|
+
let fileExists2 = true;
|
|
7115
7411
|
try {
|
|
7116
7412
|
raw = await fsp4.readFile(globalConfigPath, "utf8");
|
|
7117
7413
|
} catch (err) {
|
|
7118
7414
|
if (err.code !== "ENOENT") throw err;
|
|
7119
|
-
|
|
7415
|
+
fileExists2 = false;
|
|
7120
7416
|
}
|
|
7121
7417
|
let parsed;
|
|
7122
7418
|
try {
|
|
7123
7419
|
parsed = JSON.parse(raw);
|
|
7124
7420
|
} catch (err) {
|
|
7125
|
-
if (
|
|
7421
|
+
if (fileExists2)
|
|
7126
7422
|
throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
|
|
7127
7423
|
parsed = {};
|
|
7128
7424
|
}
|
|
@@ -7562,6 +7858,161 @@ function buildModelCapsCommand(opts) {
|
|
|
7562
7858
|
}
|
|
7563
7859
|
};
|
|
7564
7860
|
}
|
|
7861
|
+
function collectContext(opts) {
|
|
7862
|
+
const parts = [];
|
|
7863
|
+
try {
|
|
7864
|
+
const gitStatus = execFileSync("git", ["status", "--short", "--branch"], {
|
|
7865
|
+
cwd: opts.projectRoot,
|
|
7866
|
+
encoding: "utf8",
|
|
7867
|
+
timeout: 5e3,
|
|
7868
|
+
windowsHide: true
|
|
7869
|
+
}).trim();
|
|
7870
|
+
if (gitStatus) {
|
|
7871
|
+
parts.push("### Git Status", "```", gitStatus, "```");
|
|
7872
|
+
}
|
|
7873
|
+
} catch {
|
|
7874
|
+
}
|
|
7875
|
+
parts.push(`Working directory: ${opts.cwd}`);
|
|
7876
|
+
parts.push(`Project root: ${opts.projectRoot}`);
|
|
7877
|
+
return parts.join("\n");
|
|
7878
|
+
}
|
|
7879
|
+
function buildSuggestPrompt(contextText) {
|
|
7880
|
+
return [
|
|
7881
|
+
"## Suggest Next Steps",
|
|
7882
|
+
"",
|
|
7883
|
+
"Based on the current project state below, generate 3-5 actionable next-step",
|
|
7884
|
+
"suggestions. Each suggestion must be a single imperative sentence that can be",
|
|
7885
|
+
"executed immediately. Be specific \u2014 mention file names, tool names, or commands",
|
|
7886
|
+
"when relevant. Do NOT include preamble, explanation, or wrap in code blocks.",
|
|
7887
|
+
"",
|
|
7888
|
+
"Rules:",
|
|
7889
|
+
'- One suggestion per line, prefixed with the number (e.g. "1. Run tests...")',
|
|
7890
|
+
"- Order by priority: most impactful first",
|
|
7891
|
+
"- Suggestions should be independent \u2014 user can pick any subset",
|
|
7892
|
+
'- If nothing is needed, say "No pending actions \u2014 everything is up to date."',
|
|
7893
|
+
"",
|
|
7894
|
+
contextText || "(No project context available \u2014 suggest generic next steps.)",
|
|
7895
|
+
"",
|
|
7896
|
+
"Output format (strict \u2014 no other text):",
|
|
7897
|
+
"1. First suggestion here",
|
|
7898
|
+
"2. Second suggestion here",
|
|
7899
|
+
"3. Third suggestion here"
|
|
7900
|
+
].join("\n");
|
|
7901
|
+
}
|
|
7902
|
+
function buildSuggestCommand(opts) {
|
|
7903
|
+
return {
|
|
7904
|
+
name: "suggest",
|
|
7905
|
+
aliases: ["next-steps", "what-next"],
|
|
7906
|
+
category: "Agent",
|
|
7907
|
+
description: "Generate context-aware next-step suggestions for the current session.",
|
|
7908
|
+
argsHint: "[--fast]",
|
|
7909
|
+
help: [
|
|
7910
|
+
"Usage:",
|
|
7911
|
+
" /suggest Generate suggestions using a lightweight subagent",
|
|
7912
|
+
" /suggest --fast Heuristic-only suggestions (no subagent, instant)",
|
|
7913
|
+
"",
|
|
7914
|
+
"Analyzes the current session state (git status, working directory, recent",
|
|
7915
|
+
"activity) and generates 3-5 actionable next-step suggestions. Suggestions",
|
|
7916
|
+
"are stored and can be selected with `/next 1`, `/next 1 2 3`, etc.",
|
|
7917
|
+
"",
|
|
7918
|
+
"Use `/next list` to see the current suggestions at any time."
|
|
7919
|
+
].join("\n"),
|
|
7920
|
+
async run(args, _ctx) {
|
|
7921
|
+
const trimmed = args.trim().toLowerCase();
|
|
7922
|
+
const fast = /\b(--fast|-f)\b/.test(trimmed);
|
|
7923
|
+
if (fast) {
|
|
7924
|
+
const suggestions = generateHeuristicSuggestions(opts);
|
|
7925
|
+
opts.onSuggestions?.(suggestions);
|
|
7926
|
+
const display = formatSuggestions(suggestions);
|
|
7927
|
+
return { message: display };
|
|
7928
|
+
}
|
|
7929
|
+
if (!opts.onSpawnAndWait) {
|
|
7930
|
+
const suggestions = generateHeuristicSuggestions(opts);
|
|
7931
|
+
opts.onSuggestions?.(suggestions);
|
|
7932
|
+
const display = formatSuggestions(suggestions) + "\n" + color.dim("(Heuristic fallback \u2014 multi-agent not enabled)");
|
|
7933
|
+
return { message: display };
|
|
7934
|
+
}
|
|
7935
|
+
const contextText = collectContext({
|
|
7936
|
+
cwd: opts.cwd,
|
|
7937
|
+
projectRoot: opts.projectRoot
|
|
7938
|
+
});
|
|
7939
|
+
const task = buildSuggestPrompt(contextText);
|
|
7940
|
+
opts.renderer.write(color.dim("Generating suggestions..."));
|
|
7941
|
+
try {
|
|
7942
|
+
const raw = await opts.onSpawnAndWait(task, {
|
|
7943
|
+
name: "suggest"
|
|
7944
|
+
});
|
|
7945
|
+
const suggestions = parseSuggestions(raw);
|
|
7946
|
+
if (suggestions.length === 0) {
|
|
7947
|
+
const fallback = ["No pending actions \u2014 everything is up to date."];
|
|
7948
|
+
opts.onSuggestions?.(fallback);
|
|
7949
|
+
return { message: formatSuggestions(fallback) };
|
|
7950
|
+
}
|
|
7951
|
+
opts.onSuggestions?.(suggestions);
|
|
7952
|
+
return { message: formatSuggestions(suggestions) };
|
|
7953
|
+
} catch (err) {
|
|
7954
|
+
const msg = `Suggestion generation failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
7955
|
+
opts.renderer.writeWarning(msg);
|
|
7956
|
+
return { message: msg };
|
|
7957
|
+
}
|
|
7958
|
+
}
|
|
7959
|
+
};
|
|
7960
|
+
}
|
|
7961
|
+
function parseSuggestions(raw) {
|
|
7962
|
+
const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
7963
|
+
const numbered = lines.filter((l) => /^\d+[.)]\s/.test(l)).map((l) => l.replace(/^\d+[.)]\s*/, "").trim());
|
|
7964
|
+
if (numbered.length > 0) return numbered.slice(0, 5);
|
|
7965
|
+
const bullets = lines.filter((l) => /^[-*•]\s/.test(l)).map((l) => l.replace(/^[-*•]\s*/, "").trim());
|
|
7966
|
+
if (bullets.length > 0) return bullets.slice(0, 5);
|
|
7967
|
+
return lines.filter((l) => l.length > 10 && !l.startsWith("#") && !l.startsWith("```")).slice(0, 5);
|
|
7968
|
+
}
|
|
7969
|
+
function generateHeuristicSuggestions(opts) {
|
|
7970
|
+
const suggestions = [];
|
|
7971
|
+
try {
|
|
7972
|
+
const gitStatus = execFileSync("git", ["status", "--short"], {
|
|
7973
|
+
cwd: opts.projectRoot,
|
|
7974
|
+
encoding: "utf8",
|
|
7975
|
+
timeout: 5e3,
|
|
7976
|
+
windowsHide: true
|
|
7977
|
+
}).trim();
|
|
7978
|
+
if (gitStatus) {
|
|
7979
|
+
const staged = gitStatus.split("\n").filter((l) => /^[MADRC]/.test(l)).length;
|
|
7980
|
+
const unstaged = gitStatus.split("\n").filter((l) => /^.[MADRC]/.test(l)).length;
|
|
7981
|
+
const untracked = gitStatus.split("\n").filter((l) => l.startsWith("??")).length;
|
|
7982
|
+
if (staged > 0) {
|
|
7983
|
+
suggestions.push(`Commit ${staged} staged file(s) with a descriptive message`);
|
|
7984
|
+
}
|
|
7985
|
+
if (unstaged > 0) {
|
|
7986
|
+
suggestions.push(`Stage and review ${unstaged} modified file(s)`);
|
|
7987
|
+
}
|
|
7988
|
+
if (untracked > 0) {
|
|
7989
|
+
suggestions.push(`Review ${untracked} untracked file(s) \u2014 add to git or .gitignore`);
|
|
7990
|
+
}
|
|
7991
|
+
}
|
|
7992
|
+
} catch {
|
|
7993
|
+
}
|
|
7994
|
+
if (suggestions.length === 0) {
|
|
7995
|
+
suggestions.push("Review recent changes with a diff");
|
|
7996
|
+
suggestions.push("Run the test suite to verify everything passes");
|
|
7997
|
+
suggestions.push("Check for lint or type errors");
|
|
7998
|
+
}
|
|
7999
|
+
return suggestions.slice(0, 5);
|
|
8000
|
+
}
|
|
8001
|
+
function formatSuggestions(suggestions) {
|
|
8002
|
+
if (suggestions.length === 0) {
|
|
8003
|
+
return color.dim("No suggestions available.");
|
|
8004
|
+
}
|
|
8005
|
+
const lines = [
|
|
8006
|
+
` ${color.cyan("\u{1F4A1} Next steps")} ${color.dim("(use /next 1, /next 2, or /next 1 2 3)")}`,
|
|
8007
|
+
""
|
|
8008
|
+
];
|
|
8009
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
8010
|
+
const num = color.bold(`${i + 1}.`);
|
|
8011
|
+
const text = suggestions[i] ?? "";
|
|
8012
|
+
lines.push(` ${num} ${text}`);
|
|
8013
|
+
}
|
|
8014
|
+
return lines.join("\n");
|
|
8015
|
+
}
|
|
7565
8016
|
var noOpVault4 = {
|
|
7566
8017
|
encrypt: (v) => v,
|
|
7567
8018
|
decrypt: (v) => v,
|
|
@@ -7959,7 +8410,7 @@ var DEFAULTS = {
|
|
|
7959
8410
|
cost: true
|
|
7960
8411
|
};
|
|
7961
8412
|
function resolveConfigPath() {
|
|
7962
|
-
return process.env[CONFIG_ENV] ??
|
|
8413
|
+
return process.env[CONFIG_ENV] ?? path10.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
7963
8414
|
}
|
|
7964
8415
|
async function loadStatuslineConfig() {
|
|
7965
8416
|
const p = resolveConfigPath();
|
|
@@ -7973,7 +8424,7 @@ async function loadStatuslineConfig() {
|
|
|
7973
8424
|
async function saveStatuslineConfig(cfg) {
|
|
7974
8425
|
const p = resolveConfigPath();
|
|
7975
8426
|
try {
|
|
7976
|
-
await fsp4.mkdir(
|
|
8427
|
+
await fsp4.mkdir(path10.dirname(p), { recursive: true });
|
|
7977
8428
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
7978
8429
|
} catch (err) {
|
|
7979
8430
|
throw new FsError({
|
|
@@ -8400,6 +8851,199 @@ function buildWorktreeCommand(opts) {
|
|
|
8400
8851
|
}
|
|
8401
8852
|
};
|
|
8402
8853
|
}
|
|
8854
|
+
async function discoverPackageFiles(projectRoot) {
|
|
8855
|
+
const files = [];
|
|
8856
|
+
const rootPkg = path10.join(projectRoot, "package.json");
|
|
8857
|
+
try {
|
|
8858
|
+
await fsp4.access(rootPkg);
|
|
8859
|
+
files.push(rootPkg);
|
|
8860
|
+
} catch {
|
|
8861
|
+
}
|
|
8862
|
+
const workspaceFile = path10.join(projectRoot, "pnpm-workspace.yaml");
|
|
8863
|
+
try {
|
|
8864
|
+
await fsp4.access(workspaceFile);
|
|
8865
|
+
const content = await fsp4.readFile(workspaceFile, "utf8");
|
|
8866
|
+
const globMatch = /packages?:\s*\[([^\]]+)\]/s.exec(content);
|
|
8867
|
+
const rawGlobs = globMatch?.[1];
|
|
8868
|
+
if (!rawGlobs) return files;
|
|
8869
|
+
const globs = rawGlobs.split(/[\s,]+/).filter(Boolean).map((g) => g.replace(/['"]/g, ""));
|
|
8870
|
+
for (const g of globs) {
|
|
8871
|
+
const dirPrefix = g.replace(/\/?\*$/, "").replace(/\/\*$/, "");
|
|
8872
|
+
const dir = path10.join(projectRoot, dirPrefix);
|
|
8873
|
+
try {
|
|
8874
|
+
const entries = await fsp4.readdir(dir, { withFileTypes: true });
|
|
8875
|
+
for (const e of entries) {
|
|
8876
|
+
if (!e.isDirectory()) continue;
|
|
8877
|
+
const subPkg = path10.join(dir, e.name, "package.json");
|
|
8878
|
+
try {
|
|
8879
|
+
await fsp4.access(subPkg);
|
|
8880
|
+
files.push(subPkg);
|
|
8881
|
+
} catch {
|
|
8882
|
+
}
|
|
8883
|
+
}
|
|
8884
|
+
} catch {
|
|
8885
|
+
}
|
|
8886
|
+
}
|
|
8887
|
+
} catch {
|
|
8888
|
+
}
|
|
8889
|
+
return files;
|
|
8890
|
+
}
|
|
8891
|
+
function buildTechStackTask(opts) {
|
|
8892
|
+
const pkgList = opts.packageFiles.map((f) => ` - ${path10.relative(opts.projectRoot, f)}`).join("\n");
|
|
8893
|
+
const header = opts.isInit ? [
|
|
8894
|
+
"## Tech Stack Audit \u2014 First-Time Project Init",
|
|
8895
|
+
"",
|
|
8896
|
+
"This project is being initialized for the first time. Scan its dependencies,",
|
|
8897
|
+
"check every package and framework version against the npm registry, and produce",
|
|
8898
|
+
"a report that warns about outdated choices. The LLM that scaffolded this project",
|
|
8899
|
+
"may have suggested stale version numbers \u2014 verify every single one.",
|
|
8900
|
+
""
|
|
8901
|
+
].join("\n") : [
|
|
8902
|
+
"## Tech Stack Audit \u2014 Full Project Scan",
|
|
8903
|
+
"",
|
|
8904
|
+
"Scan all project dependencies, verify every package version against the npm",
|
|
8905
|
+
"registry, and produce a report that flags outdated, dead, or obsolete packages.",
|
|
8906
|
+
""
|
|
8907
|
+
].join("\n");
|
|
8908
|
+
const outputPath = opts.outputFormat === "json" ? "techstack.json" : "techstack.md";
|
|
8909
|
+
return [
|
|
8910
|
+
header,
|
|
8911
|
+
"",
|
|
8912
|
+
"### Project package files to scan",
|
|
8913
|
+
`${pkgList || " - package.json (root only)"}`,
|
|
8914
|
+
"",
|
|
8915
|
+
"### Instructions",
|
|
8916
|
+
"",
|
|
8917
|
+
"1. **Read** each package.json and extract ALL dependencies (dependencies +",
|
|
8918
|
+
" devDependencies + peerDependencies). Include the workspace root.",
|
|
8919
|
+
"",
|
|
8920
|
+
"2. **For every dependency**, look up its latest version from the npm registry:",
|
|
8921
|
+
' - `fetch("https://registry.npmjs.org/<package>/latest")`',
|
|
8922
|
+
" - Extract the `version` field from the JSON response",
|
|
8923
|
+
" - Also check `description`, `license`, and `time` fields for age/dead checks",
|
|
8924
|
+
"",
|
|
8925
|
+
"3. **For each package, determine status:**",
|
|
8926
|
+
" - \u{1F7E2} CURRENT: installed version is within 1 minor of latest",
|
|
8927
|
+
" - \u{1F7E1} OUTDATED: installed version is behind latest (major or >1 minor gap)",
|
|
8928
|
+
" - \u{1F534} CRITICAL: package has known CVEs, is deprecated, or >2 years without release",
|
|
8929
|
+
" - \u2620\uFE0F DEAD: package is deprecated, archived, or superseded \u22655 years ago",
|
|
8930
|
+
"",
|
|
8931
|
+
"4. **Apply the tech-stack skill rules:**",
|
|
8932
|
+
' - Reject packages that are "prehistoric" (superseded \u22655 years ago)',
|
|
8933
|
+
" - Flag dead packages (no release >2 years + critical issues)",
|
|
8934
|
+
" - Prefer Node.js built-ins over third-party packages",
|
|
8935
|
+
"",
|
|
8936
|
+
`5. **Write the report** to \`${outputPath}\` in the project root:`,
|
|
8937
|
+
" - Markdown format: grouped by category, with version tables and warnings",
|
|
8938
|
+
" - JSON format: structured array with name, current, latest, status, notes",
|
|
8939
|
+
"",
|
|
8940
|
+
"6. **Report a summary to chat**: total packages scanned, counts per status,",
|
|
8941
|
+
" top 5 most urgent issues, and total cost estimate (optional).",
|
|
8942
|
+
"",
|
|
8943
|
+
"### Output format (markdown)",
|
|
8944
|
+
"",
|
|
8945
|
+
"```markdown",
|
|
8946
|
+
"# Tech Stack Report",
|
|
8947
|
+
"",
|
|
8948
|
+
"Generated: <date> \xB7 Scanned: <total> packages across <N> files",
|
|
8949
|
+
"",
|
|
8950
|
+
"## \u{1F7E2} Up to Date (<count>)",
|
|
8951
|
+
"| Package | Current | Latest | Age | Notes |",
|
|
8952
|
+
"|---------|---------|--------|-----|-------|",
|
|
8953
|
+
"",
|
|
8954
|
+
"## \u{1F7E1} Outdated (<count>)",
|
|
8955
|
+
"...",
|
|
8956
|
+
"",
|
|
8957
|
+
"## \u{1F534} Critical Issues (<count>)",
|
|
8958
|
+
"...",
|
|
8959
|
+
"",
|
|
8960
|
+
"## \u2620\uFE0F Dead / Obsolete (<count>)",
|
|
8961
|
+
"...",
|
|
8962
|
+
"",
|
|
8963
|
+
"## Recommendations",
|
|
8964
|
+
"- Top 3-5 actionable fixes",
|
|
8965
|
+
"```",
|
|
8966
|
+
"",
|
|
8967
|
+
"### Guardrails",
|
|
8968
|
+
"",
|
|
8969
|
+
"- Use `fetch()` with `AbortSignal.timeout(10000)` on every npm registry call.",
|
|
8970
|
+
"- Skip packages that return 404 (private/internal packages).",
|
|
8971
|
+
"- Deduplicate: same package in multiple package.json files = one row.",
|
|
8972
|
+
"- Do NOT modify any files except writing the report.",
|
|
8973
|
+
"- Run in 2-3 iterations max. Parallel fetch where possible.",
|
|
8974
|
+
"- **IMPORTANT**: Output the chat summary FIRST, then write the file. I need to see results."
|
|
8975
|
+
].join("\n");
|
|
8976
|
+
}
|
|
8977
|
+
function buildTechStackCommand(opts) {
|
|
8978
|
+
return {
|
|
8979
|
+
name: "techstack",
|
|
8980
|
+
category: "Inspect",
|
|
8981
|
+
aliases: ["tech", "deps"],
|
|
8982
|
+
description: "Scan all project dependencies, verify versions against npm, and produce a techstack report.",
|
|
8983
|
+
argsHint: "[--json] [--init]",
|
|
8984
|
+
help: [
|
|
8985
|
+
"Usage:",
|
|
8986
|
+
" /techstack Scan dependencies + write techstack.md report",
|
|
8987
|
+
" /techstack --json Write techstack.json instead of markdown",
|
|
8988
|
+
" /techstack --init Init-mode scan (compares scaffolded vs latest)",
|
|
8989
|
+
"",
|
|
8990
|
+
"Spawns a subagent that:",
|
|
8991
|
+
" 1. Reads every package.json in the project",
|
|
8992
|
+
" 2. Looks up latest versions on the npm registry",
|
|
8993
|
+
" 3. Flags outdated, dead, or obsolete packages",
|
|
8994
|
+
` 4. Writes a ${color.cyan("techstack.md")} (or .json) report to the project root`,
|
|
8995
|
+
"",
|
|
8996
|
+
"Uses the `tech-stack` skill for version verification rules.",
|
|
8997
|
+
`Hooked into ${color.cyan("/init")} \u2014 runs automatically on first project setup.`
|
|
8998
|
+
].join("\n"),
|
|
8999
|
+
async run(args, _ctx) {
|
|
9000
|
+
const trimmed = args.trim().toLowerCase();
|
|
9001
|
+
const outputFormat = /\b(--json|-j)\b/.test(trimmed) ? "json" : "md";
|
|
9002
|
+
const isInit = /\b(--init|-i)\b/.test(trimmed);
|
|
9003
|
+
let packageFiles = [];
|
|
9004
|
+
let discoveryNote = "";
|
|
9005
|
+
try {
|
|
9006
|
+
packageFiles = await discoverPackageFiles(opts.projectRoot);
|
|
9007
|
+
if (packageFiles.length === 0) {
|
|
9008
|
+
discoveryNote = color.amber(
|
|
9009
|
+
"\u26A0 No package.json files found. This does not look like a Node.js project."
|
|
9010
|
+
);
|
|
9011
|
+
}
|
|
9012
|
+
} catch (err) {
|
|
9013
|
+
discoveryNote = color.red(
|
|
9014
|
+
`Could not scan for package files: ${err instanceof Error ? err.message : String(err)}`
|
|
9015
|
+
);
|
|
9016
|
+
}
|
|
9017
|
+
const task = buildTechStackTask({
|
|
9018
|
+
projectRoot: opts.projectRoot,
|
|
9019
|
+
packageFiles,
|
|
9020
|
+
outputFormat,
|
|
9021
|
+
isInit
|
|
9022
|
+
});
|
|
9023
|
+
if (!opts.onSpawnAndWait) {
|
|
9024
|
+
const msg = "Multi-agent is not enabled in this session. Cannot spawn techstack subagent.";
|
|
9025
|
+
opts.renderer.writeWarning(msg);
|
|
9026
|
+
return { message: msg };
|
|
9027
|
+
}
|
|
9028
|
+
const header = isInit ? "Tech Stack Init Audit" : "Tech Stack Audit";
|
|
9029
|
+
const label = `${color.cyan("\u{1F50D}")} ${color.bold(header)} ${color.dim(`(${packageFiles.length} package files)`)}`;
|
|
9030
|
+
opts.renderer.write(label);
|
|
9031
|
+
if (discoveryNote) opts.renderer.write(discoveryNote);
|
|
9032
|
+
opts.renderer.write(
|
|
9033
|
+
color.dim(`Spawning tech-stack subagent \u2192 writes ${outputFormat === "json" ? "techstack.json" : "techstack.md"} when done.`)
|
|
9034
|
+
);
|
|
9035
|
+
try {
|
|
9036
|
+
const name = isInit ? "techstack-init" : "techstack-audit";
|
|
9037
|
+
const summary = await opts.onSpawnAndWait(task, { name });
|
|
9038
|
+
return { message: summary };
|
|
9039
|
+
} catch (err) {
|
|
9040
|
+
const msg = `Tech stack scan failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
9041
|
+
opts.renderer.writeWarning(msg);
|
|
9042
|
+
return { message: msg };
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
9045
|
+
};
|
|
9046
|
+
}
|
|
8403
9047
|
function buildYoloCommand(opts) {
|
|
8404
9048
|
return {
|
|
8405
9049
|
name: "yolo",
|
|
@@ -8458,11 +9102,14 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
8458
9102
|
buildClearCommand(opts),
|
|
8459
9103
|
buildCompactCommand(opts),
|
|
8460
9104
|
buildContextCommand(opts),
|
|
9105
|
+
buildDevCommand(opts),
|
|
8461
9106
|
buildCodebaseReindexCommand(opts),
|
|
9107
|
+
buildTechStackCommand(opts),
|
|
8462
9108
|
buildToolsCommand(opts),
|
|
8463
9109
|
buildPluginCommand(opts),
|
|
8464
9110
|
buildPruneCommand(opts),
|
|
8465
9111
|
buildMcpSlashCommand(opts),
|
|
9112
|
+
buildSuggestCommand(opts),
|
|
8466
9113
|
buildAuthCommand(opts),
|
|
8467
9114
|
buildDiagCommand(opts),
|
|
8468
9115
|
buildStatsCommand(opts),
|
|
@@ -8520,13 +9167,13 @@ var MANIFESTS = [
|
|
|
8520
9167
|
];
|
|
8521
9168
|
async function detectProjectKind(projectRoot) {
|
|
8522
9169
|
try {
|
|
8523
|
-
await fsp4.access(
|
|
9170
|
+
await fsp4.access(path10.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
8524
9171
|
return "initialized";
|
|
8525
9172
|
} catch {
|
|
8526
9173
|
}
|
|
8527
9174
|
for (const m of MANIFESTS) {
|
|
8528
9175
|
try {
|
|
8529
|
-
await fsp4.access(
|
|
9176
|
+
await fsp4.access(path10.join(projectRoot, m));
|
|
8530
9177
|
return "project";
|
|
8531
9178
|
} catch {
|
|
8532
9179
|
}
|
|
@@ -8534,8 +9181,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
8534
9181
|
return "empty";
|
|
8535
9182
|
}
|
|
8536
9183
|
async function scaffoldAgentsMd(projectRoot) {
|
|
8537
|
-
const dir =
|
|
8538
|
-
const file =
|
|
9184
|
+
const dir = path10.join(projectRoot, ".wrongstack");
|
|
9185
|
+
const file = path10.join(dir, "AGENTS.md");
|
|
8539
9186
|
const facts = await detectProjectFacts(projectRoot);
|
|
8540
9187
|
const body = renderAgentsTemplate(facts);
|
|
8541
9188
|
await fsp4.mkdir(dir, { recursive: true });
|
|
@@ -8548,7 +9195,7 @@ async function runProjectCheck(opts) {
|
|
|
8548
9195
|
if (kind === "initialized") {
|
|
8549
9196
|
renderer.write(
|
|
8550
9197
|
`
|
|
8551
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
9198
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path10.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
8552
9199
|
`
|
|
8553
9200
|
);
|
|
8554
9201
|
return true;
|
|
@@ -8579,7 +9226,7 @@ async function runProjectCheck(opts) {
|
|
|
8579
9226
|
}
|
|
8580
9227
|
return true;
|
|
8581
9228
|
}
|
|
8582
|
-
const gitDir =
|
|
9229
|
+
const gitDir = path10.join(projectRoot, ".git");
|
|
8583
9230
|
let hasGit = false;
|
|
8584
9231
|
try {
|
|
8585
9232
|
await fsp4.access(gitDir);
|
|
@@ -8773,7 +9420,7 @@ var ReadlineInputReader = class {
|
|
|
8773
9420
|
history = [];
|
|
8774
9421
|
pending = false;
|
|
8775
9422
|
constructor(opts = {}) {
|
|
8776
|
-
this.historyFile = opts.historyFile ??
|
|
9423
|
+
this.historyFile = opts.historyFile ?? path10.join(os2.homedir(), ".wrongstack", "history");
|
|
8777
9424
|
}
|
|
8778
9425
|
async loadHistory() {
|
|
8779
9426
|
try {
|
|
@@ -8785,7 +9432,7 @@ var ReadlineInputReader = class {
|
|
|
8785
9432
|
}
|
|
8786
9433
|
async saveHistory() {
|
|
8787
9434
|
try {
|
|
8788
|
-
await fsp4.mkdir(
|
|
9435
|
+
await fsp4.mkdir(path10.dirname(this.historyFile), { recursive: true });
|
|
8789
9436
|
await fsp4.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
8790
9437
|
} catch {
|
|
8791
9438
|
}
|
|
@@ -8823,7 +9470,10 @@ var ReadlineInputReader = class {
|
|
|
8823
9470
|
const fresh = this.ensure();
|
|
8824
9471
|
this.installPromptGuard(fresh);
|
|
8825
9472
|
return new Promise((resolve5) => {
|
|
9473
|
+
let settled = false;
|
|
8826
9474
|
const settle = (line) => {
|
|
9475
|
+
if (settled) return;
|
|
9476
|
+
settled = true;
|
|
8827
9477
|
setOutputLineGuard(null);
|
|
8828
9478
|
resolve5(line);
|
|
8829
9479
|
};
|
|
@@ -8835,6 +9485,7 @@ var ReadlineInputReader = class {
|
|
|
8835
9485
|
settle(line);
|
|
8836
9486
|
});
|
|
8837
9487
|
fresh.once("close", () => settle(""));
|
|
9488
|
+
fresh.on && fresh.on("error", (_e) => settle(""));
|
|
8838
9489
|
}).then((result) => {
|
|
8839
9490
|
this.rl?.close();
|
|
8840
9491
|
return result;
|
|
@@ -9074,20 +9725,20 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
9074
9725
|
if (PROTECTED_BASENAMES.has(filename)) {
|
|
9075
9726
|
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
9076
9727
|
}
|
|
9077
|
-
if (filename !==
|
|
9728
|
+
if (filename !== path10.basename(filename)) {
|
|
9078
9729
|
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
9079
9730
|
}
|
|
9080
9731
|
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
9081
9732
|
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
9082
9733
|
}
|
|
9083
|
-
const resolvedParent =
|
|
9734
|
+
const resolvedParent = path10.resolve(parentDir);
|
|
9084
9735
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
9085
9736
|
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
9086
9737
|
}
|
|
9087
9738
|
}
|
|
9088
9739
|
async function safeDelete(filePath) {
|
|
9089
|
-
const dir =
|
|
9090
|
-
const filename =
|
|
9740
|
+
const dir = path10.dirname(filePath);
|
|
9741
|
+
const filename = path10.basename(filePath);
|
|
9091
9742
|
try {
|
|
9092
9743
|
assertSafeToDelete(filename, dir);
|
|
9093
9744
|
await fsp4.unlink(filePath);
|
|
@@ -9132,16 +9783,16 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
9132
9783
|
}
|
|
9133
9784
|
var defaultHomeDir = () => os2__default.homedir();
|
|
9134
9785
|
function historyDir(homeFn = defaultHomeDir) {
|
|
9135
|
-
return
|
|
9786
|
+
return path10.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
9136
9787
|
}
|
|
9137
9788
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
9138
|
-
return
|
|
9789
|
+
return path10.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
9139
9790
|
}
|
|
9140
9791
|
function configPath(homeFn = defaultHomeDir) {
|
|
9141
|
-
return
|
|
9792
|
+
return path10.join(homeFn(), ".wrongstack", "config.json");
|
|
9142
9793
|
}
|
|
9143
9794
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
9144
|
-
return
|
|
9795
|
+
return path10.join(homeFn(), ".wrongstack", "config.json.last");
|
|
9145
9796
|
}
|
|
9146
9797
|
function entryId(ts) {
|
|
9147
9798
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -9196,17 +9847,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
9196
9847
|
}
|
|
9197
9848
|
if (content !== void 0) {
|
|
9198
9849
|
try {
|
|
9199
|
-
const bakPath =
|
|
9850
|
+
const bakPath = path10.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
9200
9851
|
await atomicWrite(bakPath, content);
|
|
9201
9852
|
} catch {
|
|
9202
9853
|
}
|
|
9203
9854
|
}
|
|
9204
9855
|
try {
|
|
9205
|
-
const dir =
|
|
9856
|
+
const dir = path10.join(homeFn(), ".wrongstack");
|
|
9206
9857
|
const files = await fsp4.readdir(dir);
|
|
9207
9858
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
9208
9859
|
for (const f of baks.slice(10)) {
|
|
9209
|
-
await safeDelete(
|
|
9860
|
+
await safeDelete(path10.join(dir, f));
|
|
9210
9861
|
}
|
|
9211
9862
|
} catch {
|
|
9212
9863
|
}
|
|
@@ -9224,7 +9875,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
9224
9875
|
};
|
|
9225
9876
|
try {
|
|
9226
9877
|
await fsp4.writeFile(
|
|
9227
|
-
|
|
9878
|
+
path10.join(historyDir(homeFn), `${id}.json`),
|
|
9228
9879
|
JSON.stringify(entry, null, 2),
|
|
9229
9880
|
"utf8"
|
|
9230
9881
|
);
|
|
@@ -9232,7 +9883,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
9232
9883
|
throw new FsError({
|
|
9233
9884
|
message: err instanceof Error ? err.message : String(err),
|
|
9234
9885
|
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
9235
|
-
path:
|
|
9886
|
+
path: path10.join(historyDir(homeFn), `${id}.json`),
|
|
9236
9887
|
cause: err
|
|
9237
9888
|
});
|
|
9238
9889
|
}
|
|
@@ -9247,7 +9898,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
9247
9898
|
}
|
|
9248
9899
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
9249
9900
|
try {
|
|
9250
|
-
const raw = await fsp4.readFile(
|
|
9901
|
+
const raw = await fsp4.readFile(path10.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
9251
9902
|
return JSON.parse(raw);
|
|
9252
9903
|
} catch {
|
|
9253
9904
|
return null;
|
|
@@ -9313,10 +9964,10 @@ var theme = { primary: color.amber };
|
|
|
9313
9964
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
|
|
9314
9965
|
try {
|
|
9315
9966
|
const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
|
|
9316
|
-
const
|
|
9967
|
+
const fs30 = await import('fs/promises');
|
|
9317
9968
|
let existing = {};
|
|
9318
9969
|
try {
|
|
9319
|
-
const raw = await
|
|
9970
|
+
const raw = await fs30.readFile(configPath2, "utf8");
|
|
9320
9971
|
existing = JSON.parse(raw);
|
|
9321
9972
|
} catch {
|
|
9322
9973
|
}
|
|
@@ -9652,12 +10303,12 @@ function pickGroupIndex(opts) {
|
|
|
9652
10303
|
try {
|
|
9653
10304
|
let current = 0;
|
|
9654
10305
|
try {
|
|
9655
|
-
const parsed = Number.parseInt(
|
|
10306
|
+
const parsed = Number.parseInt(fs15.readFileSync(opts.cursorFile, "utf8").trim(), 10);
|
|
9656
10307
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
9657
10308
|
} catch {
|
|
9658
10309
|
}
|
|
9659
|
-
|
|
9660
|
-
|
|
10310
|
+
fs15.mkdirSync(path10.dirname(opts.cursorFile), { recursive: true });
|
|
10311
|
+
fs15.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
9661
10312
|
return current;
|
|
9662
10313
|
} catch {
|
|
9663
10314
|
}
|
|
@@ -9993,14 +10644,14 @@ function summarize(value, name) {
|
|
|
9993
10644
|
if (typeof v === "object" && v !== null) {
|
|
9994
10645
|
const o = v;
|
|
9995
10646
|
if (name === "edit") {
|
|
9996
|
-
const
|
|
10647
|
+
const path28 = typeof o["path"] === "string" ? o["path"] : "";
|
|
9997
10648
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
9998
|
-
return `${
|
|
10649
|
+
return `${path28} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
9999
10650
|
}
|
|
10000
10651
|
if (name === "write") {
|
|
10001
|
-
const
|
|
10652
|
+
const path28 = typeof o["path"] === "string" ? o["path"] : "";
|
|
10002
10653
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
10003
|
-
return bytes !== void 0 ? `${
|
|
10654
|
+
return bytes !== void 0 ? `${path28} ${bytes}B` : path28;
|
|
10004
10655
|
}
|
|
10005
10656
|
if (typeof o["count"] === "number") {
|
|
10006
10657
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -11218,7 +11869,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
11218
11869
|
}
|
|
11219
11870
|
try {
|
|
11220
11871
|
await fsp4.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
11221
|
-
const probe =
|
|
11872
|
+
const probe = path10.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
11222
11873
|
await fsp4.writeFile(probe, "");
|
|
11223
11874
|
await fsp4.unlink(probe);
|
|
11224
11875
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -11321,8 +11972,8 @@ var exportCmd = async (args, deps) => {
|
|
|
11321
11972
|
return 1;
|
|
11322
11973
|
}
|
|
11323
11974
|
if (output) {
|
|
11324
|
-
await fsp4.mkdir(
|
|
11325
|
-
await fsp4.writeFile(
|
|
11975
|
+
await fsp4.mkdir(path10.dirname(path10.resolve(deps.cwd, output)), { recursive: true });
|
|
11976
|
+
await fsp4.writeFile(path10.resolve(deps.cwd, output), rendered, "utf8");
|
|
11326
11977
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
11327
11978
|
`);
|
|
11328
11979
|
} else {
|
|
@@ -11395,8 +12046,8 @@ var initCmd = async (_args, deps) => {
|
|
|
11395
12046
|
const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
|
|
11396
12047
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
11397
12048
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
11398
|
-
await fsp4.mkdir(
|
|
11399
|
-
const agentsFile =
|
|
12049
|
+
await fsp4.mkdir(path10.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
12050
|
+
const agentsFile = path10.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
11400
12051
|
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
11401
12052
|
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
11402
12053
|
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
@@ -11853,7 +12504,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
11853
12504
|
return 0;
|
|
11854
12505
|
};
|
|
11855
12506
|
var projectsCmd = async (_args, deps) => {
|
|
11856
|
-
const projectsRoot =
|
|
12507
|
+
const projectsRoot = path10.join(deps.paths.globalRoot, "projects");
|
|
11857
12508
|
try {
|
|
11858
12509
|
const entries = await fsp4.readdir(projectsRoot);
|
|
11859
12510
|
if (entries.length === 0) {
|
|
@@ -11863,7 +12514,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
11863
12514
|
for (const hash of entries) {
|
|
11864
12515
|
try {
|
|
11865
12516
|
const meta = JSON.parse(
|
|
11866
|
-
await fsp4.readFile(
|
|
12517
|
+
await fsp4.readFile(path10.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
11867
12518
|
);
|
|
11868
12519
|
deps.renderer.write(
|
|
11869
12520
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -12070,20 +12721,20 @@ ${navLines.join(" \xB7 ")}
|
|
|
12070
12721
|
async function mutateModelsConfig(deps, mutator) {
|
|
12071
12722
|
const vault = deps.vault;
|
|
12072
12723
|
const configPath2 = deps.paths.globalConfig;
|
|
12073
|
-
let
|
|
12724
|
+
let fileExists2 = true;
|
|
12074
12725
|
let raw;
|
|
12075
12726
|
try {
|
|
12076
12727
|
raw = await fsp4.readFile(configPath2, "utf8");
|
|
12077
12728
|
} catch (err) {
|
|
12078
12729
|
if (err.code !== "ENOENT") throw err;
|
|
12079
|
-
|
|
12730
|
+
fileExists2 = false;
|
|
12080
12731
|
raw = "{}";
|
|
12081
12732
|
}
|
|
12082
12733
|
let parsed;
|
|
12083
12734
|
try {
|
|
12084
12735
|
parsed = JSON.parse(raw);
|
|
12085
12736
|
} catch (err) {
|
|
12086
|
-
if (
|
|
12737
|
+
if (fileExists2) {
|
|
12087
12738
|
throw new Error(
|
|
12088
12739
|
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}).`
|
|
12089
12740
|
);
|
|
@@ -12242,7 +12893,7 @@ async function listFleetRuns(deps) {
|
|
|
12242
12893
|
}
|
|
12243
12894
|
const runs = [];
|
|
12244
12895
|
for (const id of entries) {
|
|
12245
|
-
const runDir =
|
|
12896
|
+
const runDir = path10.join(deps.paths.projectSessions, id);
|
|
12246
12897
|
let stat4;
|
|
12247
12898
|
try {
|
|
12248
12899
|
stat4 = await fsp4.stat(runDir);
|
|
@@ -12255,17 +12906,17 @@ async function listFleetRuns(deps) {
|
|
|
12255
12906
|
let subagentCount = 0;
|
|
12256
12907
|
let subagentsDir;
|
|
12257
12908
|
try {
|
|
12258
|
-
await fsp4.access(
|
|
12909
|
+
await fsp4.access(path10.join(runDir, "fleet.json"));
|
|
12259
12910
|
manifest = true;
|
|
12260
12911
|
} catch {
|
|
12261
12912
|
}
|
|
12262
12913
|
try {
|
|
12263
|
-
await fsp4.access(
|
|
12914
|
+
await fsp4.access(path10.join(runDir, "checkpoint.json"));
|
|
12264
12915
|
checkpoint = true;
|
|
12265
12916
|
} catch {
|
|
12266
12917
|
}
|
|
12267
12918
|
try {
|
|
12268
|
-
subagentsDir =
|
|
12919
|
+
subagentsDir = path10.join(runDir, "subagents");
|
|
12269
12920
|
const files = await fsp4.readdir(subagentsDir);
|
|
12270
12921
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
12271
12922
|
} catch {
|
|
@@ -12294,7 +12945,7 @@ async function listFleetRuns(deps) {
|
|
|
12294
12945
|
return 0;
|
|
12295
12946
|
}
|
|
12296
12947
|
async function showFleetRun(runId, deps) {
|
|
12297
|
-
const runDir =
|
|
12948
|
+
const runDir = path10.join(deps.paths.projectSessions, runId);
|
|
12298
12949
|
let stat4;
|
|
12299
12950
|
try {
|
|
12300
12951
|
stat4 = await fsp4.stat(runDir);
|
|
@@ -12311,7 +12962,7 @@ async function showFleetRun(runId, deps) {
|
|
|
12311
12962
|
deps.renderer.write(color.bold(`
|
|
12312
12963
|
Fleet Run: ${runId}
|
|
12313
12964
|
`) + "\n");
|
|
12314
|
-
const manifestPath =
|
|
12965
|
+
const manifestPath = path10.join(runDir, "fleet.json");
|
|
12315
12966
|
let manifestData = null;
|
|
12316
12967
|
try {
|
|
12317
12968
|
manifestData = await fsp4.readFile(manifestPath, "utf8");
|
|
@@ -12327,7 +12978,7 @@ Fleet Run: ${runId}
|
|
|
12327
12978
|
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
12328
12979
|
`);
|
|
12329
12980
|
}
|
|
12330
|
-
const checkpointPath =
|
|
12981
|
+
const checkpointPath = path10.join(runDir, "checkpoint.json");
|
|
12331
12982
|
let checkpointData = null;
|
|
12332
12983
|
try {
|
|
12333
12984
|
checkpointData = await fsp4.readFile(checkpointPath, "utf8");
|
|
@@ -12374,7 +13025,7 @@ Fleet Run: ${runId}
|
|
|
12374
13025
|
} catch {
|
|
12375
13026
|
}
|
|
12376
13027
|
}
|
|
12377
|
-
const subagentsDir =
|
|
13028
|
+
const subagentsDir = path10.join(runDir, "subagents");
|
|
12378
13029
|
let subagentFiles = [];
|
|
12379
13030
|
try {
|
|
12380
13031
|
subagentFiles = await fsp4.readdir(subagentsDir);
|
|
@@ -12386,7 +13037,7 @@ Fleet Run: ${runId}
|
|
|
12386
13037
|
Subagent transcripts (${subagentFiles.length}):
|
|
12387
13038
|
`);
|
|
12388
13039
|
for (const f of subagentFiles.sort()) {
|
|
12389
|
-
const filePath =
|
|
13040
|
+
const filePath = path10.join(subagentsDir, f);
|
|
12390
13041
|
let size;
|
|
12391
13042
|
try {
|
|
12392
13043
|
const s = await fsp4.stat(filePath);
|
|
@@ -12403,7 +13054,7 @@ Fleet Run: ${runId}
|
|
|
12403
13054
|
${color.dim("\u25CB")} No subagent transcripts
|
|
12404
13055
|
`);
|
|
12405
13056
|
}
|
|
12406
|
-
const sharedDir =
|
|
13057
|
+
const sharedDir = path10.join(runDir, "shared");
|
|
12407
13058
|
try {
|
|
12408
13059
|
const files = await fsp4.readdir(sharedDir);
|
|
12409
13060
|
deps.renderer.write(`
|
|
@@ -12570,7 +13221,7 @@ function findSessionId(args) {
|
|
|
12570
13221
|
var rewindCmd = async (args, deps) => {
|
|
12571
13222
|
const flags = parseRewindFlags(args);
|
|
12572
13223
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
12573
|
-
const sessionsDir =
|
|
13224
|
+
const sessionsDir = path10.join(wpaths.globalRoot, "sessions");
|
|
12574
13225
|
const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
|
|
12575
13226
|
let sessionId = findSessionId(args);
|
|
12576
13227
|
if (!sessionId) {
|
|
@@ -12810,10 +13461,10 @@ var auditCmd = async (args, deps) => {
|
|
|
12810
13461
|
return verify.ok ? 0 : 1;
|
|
12811
13462
|
};
|
|
12812
13463
|
async function listAudits(log, dir, deps) {
|
|
12813
|
-
const
|
|
13464
|
+
const fs30 = await import('fs/promises');
|
|
12814
13465
|
let entries;
|
|
12815
13466
|
try {
|
|
12816
|
-
entries = await
|
|
13467
|
+
entries = await fs30.readdir(dir);
|
|
12817
13468
|
} catch {
|
|
12818
13469
|
deps.renderer.write(
|
|
12819
13470
|
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
@@ -13040,10 +13691,10 @@ function roleCat(role) {
|
|
|
13040
13691
|
function createProviderForId(providerId, cfg) {
|
|
13041
13692
|
const savedCfg = cfg.providers?.[providerId];
|
|
13042
13693
|
const resolvedProviderId = savedCfg?.type ?? providerId;
|
|
13043
|
-
const cfgWithType =
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13694
|
+
const cfgWithType = Object.assign(
|
|
13695
|
+
{ type: resolvedProviderId },
|
|
13696
|
+
savedCfg ?? { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }
|
|
13697
|
+
);
|
|
13047
13698
|
try {
|
|
13048
13699
|
return makeProviderFromConfig(resolvedProviderId, cfgWithType);
|
|
13049
13700
|
} catch {
|
|
@@ -13082,7 +13733,7 @@ Output ONLY a ranked list, one per line:
|
|
|
13082
13733
|
const text = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
|
|
13083
13734
|
const rankings = [];
|
|
13084
13735
|
for (const line of text.split("\n")) {
|
|
13085
|
-
const m = line.match(/^\s*(\d+)[
|
|
13736
|
+
const m = line.match(/^\s*(\d+)[.)]\s*Response\s+([A-Z])/i);
|
|
13086
13737
|
if (m) {
|
|
13087
13738
|
const label = m[2].toUpperCase();
|
|
13088
13739
|
const idx = labelToIdx.get(label);
|
|
@@ -13288,10 +13939,17 @@ var modeldiagCmd = async (args, deps) => {
|
|
|
13288
13939
|
const providersEqIdx = benchArgs.findIndex((a) => a.startsWith("--providers="));
|
|
13289
13940
|
let providerFilter;
|
|
13290
13941
|
if (providersEqIdx >= 0) {
|
|
13291
|
-
|
|
13292
|
-
|
|
13942
|
+
const rawFilter = benchArgs[providersEqIdx]?.replace("--providers=", "").split(",");
|
|
13943
|
+
if (rawFilter) {
|
|
13944
|
+
providerFilter = rawFilter.map((s) => s.trim()).filter(Boolean);
|
|
13945
|
+
benchArgs.splice(providersEqIdx, 1);
|
|
13946
|
+
}
|
|
13293
13947
|
}
|
|
13294
13948
|
const benchRole = benchArgs[0];
|
|
13949
|
+
if (!benchRole) {
|
|
13950
|
+
writeLine(`${color.amber("No benchmark role specified")}. Usage: wstack diag bench <role> [prompt]`);
|
|
13951
|
+
return 1;
|
|
13952
|
+
}
|
|
13295
13953
|
const benchPrompt = benchArgs.slice(1).join(" ");
|
|
13296
13954
|
const cat = roleCat(benchRole);
|
|
13297
13955
|
const candidates = rankModels(providers, hasKey, cat, 5);
|
|
@@ -13706,22 +14364,22 @@ function fmtDuration(ms) {
|
|
|
13706
14364
|
const remMin = m - h * 60;
|
|
13707
14365
|
return `${h}h${remMin}m`;
|
|
13708
14366
|
}
|
|
13709
|
-
function fmtTaskResultLine(r,
|
|
14367
|
+
function fmtTaskResultLine(r, color62) {
|
|
13710
14368
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
13711
14369
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
13712
14370
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
13713
14371
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
13714
|
-
const errKindChip = errKind ?
|
|
13715
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
14372
|
+
const errKindChip = errKind ? color62.dim(` [${errKind}]`) : "";
|
|
14373
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color62.dim(errTail)}` : "";
|
|
13716
14374
|
switch (r.status) {
|
|
13717
14375
|
case "success":
|
|
13718
|
-
return { mark:
|
|
14376
|
+
return { mark: color62.green("\u2713"), stats, tail: "" };
|
|
13719
14377
|
case "timeout":
|
|
13720
|
-
return { mark:
|
|
14378
|
+
return { mark: color62.yellow("\u23F1"), stats: `${color62.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
13721
14379
|
case "stopped":
|
|
13722
|
-
return { mark:
|
|
14380
|
+
return { mark: color62.dim("\u2298"), stats: `${color62.dim("stopped")} ${stats}`, tail: errSnip };
|
|
13723
14381
|
case "failed":
|
|
13724
|
-
return { mark:
|
|
14382
|
+
return { mark: color62.red("\u2717"), stats: `${color62.red("failed")} ${stats}`, tail: errSnip };
|
|
13725
14383
|
}
|
|
13726
14384
|
}
|
|
13727
14385
|
|
|
@@ -13739,7 +14397,7 @@ function resolveBundledSkillsDir() {
|
|
|
13739
14397
|
try {
|
|
13740
14398
|
const req2 = createRequire(import.meta.url);
|
|
13741
14399
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
13742
|
-
return
|
|
14400
|
+
return path10.join(path10.dirname(corePkg), "skills");
|
|
13743
14401
|
} catch {
|
|
13744
14402
|
return void 0;
|
|
13745
14403
|
}
|
|
@@ -13989,7 +14647,7 @@ async function boot(argv) {
|
|
|
13989
14647
|
} catch {
|
|
13990
14648
|
}
|
|
13991
14649
|
printLaunchHints(renderer, flags, {
|
|
13992
|
-
cursorFile:
|
|
14650
|
+
cursorFile: path10.join(wpaths.cacheDir, "hint-cursor")
|
|
13993
14651
|
});
|
|
13994
14652
|
}
|
|
13995
14653
|
return {
|
|
@@ -14010,7 +14668,7 @@ async function boot(argv) {
|
|
|
14010
14668
|
}
|
|
14011
14669
|
async function checkGitInCwd(opts) {
|
|
14012
14670
|
const { cwd, renderer, reader } = opts;
|
|
14013
|
-
const cwdGit =
|
|
14671
|
+
const cwdGit = path10.join(cwd, ".git");
|
|
14014
14672
|
let hasCwdGit = false;
|
|
14015
14673
|
try {
|
|
14016
14674
|
await fsp4.access(cwdGit);
|
|
@@ -14051,10 +14709,10 @@ async function checkGitInCwd(opts) {
|
|
|
14051
14709
|
}
|
|
14052
14710
|
}
|
|
14053
14711
|
}
|
|
14054
|
-
const parentDir =
|
|
14712
|
+
const parentDir = path10.dirname(cwd);
|
|
14055
14713
|
if (parentDir !== cwd) {
|
|
14056
14714
|
try {
|
|
14057
|
-
await fsp4.access(
|
|
14715
|
+
await fsp4.access(path10.join(parentDir, ".git"));
|
|
14058
14716
|
renderer.write(
|
|
14059
14717
|
` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
|
|
14060
14718
|
`
|
|
@@ -14371,11 +15029,29 @@ async function predictNextTasks(input, opts) {
|
|
|
14371
15029
|
}
|
|
14372
15030
|
}
|
|
14373
15031
|
init_sdd();
|
|
15032
|
+
function parseSuggestionsFromOutput(finalText) {
|
|
15033
|
+
const patterns = [
|
|
15034
|
+
/💡\s*Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i,
|
|
15035
|
+
/##?\s*Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i,
|
|
15036
|
+
/Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i
|
|
15037
|
+
];
|
|
15038
|
+
for (const pat of patterns) {
|
|
15039
|
+
const m = pat.exec(finalText);
|
|
15040
|
+
if (m && m[1]) {
|
|
15041
|
+
const block = m[1].trim();
|
|
15042
|
+
const lines = block.split("\n").filter(Boolean);
|
|
15043
|
+
const suggestions = lines.map((l) => l.replace(/^\d+\.\s*/, "").trim()).filter((s) => s.length > 3);
|
|
15044
|
+
if (suggestions.length > 0) return suggestions.slice(0, 5);
|
|
15045
|
+
}
|
|
15046
|
+
}
|
|
15047
|
+
return null;
|
|
15048
|
+
}
|
|
14374
15049
|
async function runRepl(opts) {
|
|
14375
15050
|
if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
|
|
14376
15051
|
await renderGoalBanner(opts);
|
|
14377
15052
|
let activeCtrl;
|
|
14378
15053
|
let interrupts = 0;
|
|
15054
|
+
let autoIterCount = 0;
|
|
14379
15055
|
let exiting = false;
|
|
14380
15056
|
const onSigint = () => {
|
|
14381
15057
|
interrupts++;
|
|
@@ -14431,6 +15107,28 @@ async function runRepl(opts) {
|
|
|
14431
15107
|
`
|
|
14432
15108
|
);
|
|
14433
15109
|
}
|
|
15110
|
+
if (engine.currentState === "stopped") {
|
|
15111
|
+
const goal = await loadGoalSafe(opts);
|
|
15112
|
+
if (goal?.goalState === "completed") {
|
|
15113
|
+
const u = goal.journal.length > 0 ? summarizeUsage(goal) : null;
|
|
15114
|
+
const costLine = u && u.iterationsWithUsage > 0 ? color.dim(` \u2014 ${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out \xB7 ${goal.iterations} iterations`) : goal.iterations > 0 ? color.dim(` \u2014 ${goal.iterations} iterations`) : "";
|
|
15115
|
+
opts.renderer.write(
|
|
15116
|
+
color.green(`
|
|
15117
|
+
\u{1F3AF} Goal completed!${costLine}
|
|
15118
|
+
|
|
15119
|
+
`) + color.dim(" Goal cleared. Use /goal set <mission> to create a new goal.\n")
|
|
15120
|
+
);
|
|
15121
|
+
if (opts.projectRoot) {
|
|
15122
|
+
try {
|
|
15123
|
+
const { unlink: unlink4 } = await import('fs/promises');
|
|
15124
|
+
await unlink4(goalFilePath(opts.projectRoot));
|
|
15125
|
+
} catch {
|
|
15126
|
+
}
|
|
15127
|
+
}
|
|
15128
|
+
}
|
|
15129
|
+
opts.onAutonomy?.("off");
|
|
15130
|
+
continue;
|
|
15131
|
+
}
|
|
14434
15132
|
} catch (err) {
|
|
14435
15133
|
opts.renderer.writeError(
|
|
14436
15134
|
`[eternal] ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -14477,6 +15175,28 @@ async function runRepl(opts) {
|
|
|
14477
15175
|
`
|
|
14478
15176
|
);
|
|
14479
15177
|
}
|
|
15178
|
+
if (engine.currentState === "stopped") {
|
|
15179
|
+
const goal = await loadGoalSafe(opts);
|
|
15180
|
+
if (goal?.goalState === "completed") {
|
|
15181
|
+
const u = goal.journal.length > 0 ? summarizeUsage(goal) : null;
|
|
15182
|
+
const costLine = u && u.iterationsWithUsage > 0 ? color.dim(` \u2014 ${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out \xB7 ${goal.iterations} iterations`) : goal.iterations > 0 ? color.dim(` \u2014 ${goal.iterations} iterations`) : "";
|
|
15183
|
+
opts.renderer.write(
|
|
15184
|
+
color.green(`
|
|
15185
|
+
\u{1F3AF} Goal completed!${costLine}
|
|
15186
|
+
|
|
15187
|
+
`) + color.dim(" Goal cleared. Use /goal set <mission> to create a new goal.\n")
|
|
15188
|
+
);
|
|
15189
|
+
if (opts.projectRoot) {
|
|
15190
|
+
try {
|
|
15191
|
+
const { unlink: unlink4 } = await import('fs/promises');
|
|
15192
|
+
await unlink4(goalFilePath(opts.projectRoot));
|
|
15193
|
+
} catch {
|
|
15194
|
+
}
|
|
15195
|
+
}
|
|
15196
|
+
}
|
|
15197
|
+
opts.onAutonomy?.("off");
|
|
15198
|
+
continue;
|
|
15199
|
+
}
|
|
14480
15200
|
} catch (err) {
|
|
14481
15201
|
opts.renderer.writeError(
|
|
14482
15202
|
`[parallel] ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -14486,6 +15206,96 @@ async function runRepl(opts) {
|
|
|
14486
15206
|
continue;
|
|
14487
15207
|
}
|
|
14488
15208
|
}
|
|
15209
|
+
{
|
|
15210
|
+
const mode = opts.getAutonomy?.() ?? "off";
|
|
15211
|
+
const suggestions = opts.getSuggestions?.() ?? [];
|
|
15212
|
+
if (mode === "suggest" && suggestions.length > 0) {
|
|
15213
|
+
const lines = suggestions.map(
|
|
15214
|
+
(s, i) => ` ${color.bold(`${i + 1}.`)} ${color.dim(s)}`
|
|
15215
|
+
);
|
|
15216
|
+
opts.renderer.write(
|
|
15217
|
+
`
|
|
15218
|
+
${color.cyan(" \u{1F4A1} Suggested next steps")} ${color.dim("(use /next 1, /next 2, or /next 1 2 3)")}
|
|
15219
|
+
${lines.join("\n")}
|
|
15220
|
+
|
|
15221
|
+
`
|
|
15222
|
+
);
|
|
15223
|
+
}
|
|
15224
|
+
if (mode === "auto" && suggestions.length > 0) {
|
|
15225
|
+
const maxIter = opts.autoProceedMaxIterations ?? 50;
|
|
15226
|
+
if (maxIter > 0 && autoIterCount >= maxIter) {
|
|
15227
|
+
console.log(JSON.stringify({
|
|
15228
|
+
level: "info",
|
|
15229
|
+
event: "auto_proceed_limit_reached",
|
|
15230
|
+
iterations: autoIterCount,
|
|
15231
|
+
maxIterations: maxIter
|
|
15232
|
+
}));
|
|
15233
|
+
opts.renderer.write(
|
|
15234
|
+
`
|
|
15235
|
+
${color.amber(" \u26A0 Auto-proceed limit reached")} \u2014 ${color.dim(`${autoIterCount} iterations. Waiting for input.`)}
|
|
15236
|
+
|
|
15237
|
+
`
|
|
15238
|
+
);
|
|
15239
|
+
autoIterCount = 0;
|
|
15240
|
+
} else {
|
|
15241
|
+
const top = suggestions[0] ?? "";
|
|
15242
|
+
const delay = opts.autoProceedDelayMs ?? 45e3;
|
|
15243
|
+
if (opts.onValidateAutoProceed) {
|
|
15244
|
+
try {
|
|
15245
|
+
const lastOutput = (
|
|
15246
|
+
/* last known agent output — use the stored suggestions
|
|
15247
|
+
and any recent context the host can provide */
|
|
15248
|
+
""
|
|
15249
|
+
);
|
|
15250
|
+
const ok = await opts.onValidateAutoProceed(top, lastOutput);
|
|
15251
|
+
if (!ok) {
|
|
15252
|
+
opts.renderer.write(
|
|
15253
|
+
`
|
|
15254
|
+
${color.amber(" \u26A0 Auto-proceed held")} \u2014 ${color.dim("suggestions need review. Waiting for input.")}
|
|
15255
|
+
|
|
15256
|
+
`
|
|
15257
|
+
);
|
|
15258
|
+
const lines = suggestions.map(
|
|
15259
|
+
(s, i) => ` ${color.bold(`${i + 1}.`)} ${color.dim(s)}`
|
|
15260
|
+
);
|
|
15261
|
+
opts.renderer.write(
|
|
15262
|
+
`${color.dim(" Next steps:")}
|
|
15263
|
+
${lines.join("\n")}
|
|
15264
|
+
|
|
15265
|
+
`
|
|
15266
|
+
);
|
|
15267
|
+
autoIterCount = 0;
|
|
15268
|
+
} else {
|
|
15269
|
+
const ctrl = new AbortController();
|
|
15270
|
+
activeCtrl = ctrl;
|
|
15271
|
+
try {
|
|
15272
|
+
autoIterCount++;
|
|
15273
|
+
await runAutoProceed(opts, top, delay, ctrl);
|
|
15274
|
+
} finally {
|
|
15275
|
+
activeCtrl = void 0;
|
|
15276
|
+
}
|
|
15277
|
+
continue;
|
|
15278
|
+
}
|
|
15279
|
+
} catch {
|
|
15280
|
+
opts.renderer.write(
|
|
15281
|
+
color.dim(" Auto-proceed validation unavailable \u2014 waiting for input.\n")
|
|
15282
|
+
);
|
|
15283
|
+
autoIterCount = 0;
|
|
15284
|
+
}
|
|
15285
|
+
} else {
|
|
15286
|
+
const ctrl = new AbortController();
|
|
15287
|
+
activeCtrl = ctrl;
|
|
15288
|
+
try {
|
|
15289
|
+
autoIterCount++;
|
|
15290
|
+
await runAutoProceed(opts, top, delay, ctrl);
|
|
15291
|
+
} finally {
|
|
15292
|
+
activeCtrl = void 0;
|
|
15293
|
+
}
|
|
15294
|
+
continue;
|
|
15295
|
+
}
|
|
15296
|
+
}
|
|
15297
|
+
}
|
|
15298
|
+
}
|
|
14489
15299
|
let raw;
|
|
14490
15300
|
try {
|
|
14491
15301
|
raw = await readPossiblyMultiline(opts);
|
|
@@ -14579,6 +15389,10 @@ ${color.dim(taskList2)}
|
|
|
14579
15389
|
}
|
|
14580
15390
|
}
|
|
14581
15391
|
}
|
|
15392
|
+
if (opts.onSuggestionsParsed) {
|
|
15393
|
+
const parsed = parseSuggestionsFromOutput(runResult.finalText);
|
|
15394
|
+
opts.onSuggestionsParsed(parsed);
|
|
15395
|
+
}
|
|
14582
15396
|
}
|
|
14583
15397
|
} catch (_runErr) {
|
|
14584
15398
|
opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
|
|
@@ -14739,6 +15553,10 @@ ${color.dim(taskList2)}
|
|
|
14739
15553
|
}
|
|
14740
15554
|
}
|
|
14741
15555
|
}
|
|
15556
|
+
if (result.status === "done" && result.finalText && opts.onSuggestionsParsed) {
|
|
15557
|
+
const parsed = parseSuggestionsFromOutput(result.finalText);
|
|
15558
|
+
opts.onSuggestionsParsed(parsed);
|
|
15559
|
+
}
|
|
14742
15560
|
if (opts.tokenCounter && before) {
|
|
14743
15561
|
const after = opts.tokenCounter.total();
|
|
14744
15562
|
const costAfter = opts.tokenCounter.estimateCost().total;
|
|
@@ -14801,6 +15619,10 @@ ${color.cyan(" Suggested next steps:")}
|
|
|
14801
15619
|
${suggestResult.finalText}
|
|
14802
15620
|
`
|
|
14803
15621
|
);
|
|
15622
|
+
if (opts.onSuggestionsParsed) {
|
|
15623
|
+
const parsed = parseSuggestionsFromOutput(suggestResult.finalText);
|
|
15624
|
+
opts.onSuggestionsParsed(parsed);
|
|
15625
|
+
}
|
|
14804
15626
|
}
|
|
14805
15627
|
} catch {
|
|
14806
15628
|
} finally {
|
|
@@ -14931,6 +15753,79 @@ async function renderGoalBanner(opts) {
|
|
|
14931
15753
|
}
|
|
14932
15754
|
opts.renderer.write("\n");
|
|
14933
15755
|
}
|
|
15756
|
+
async function runAutoProceed(opts, suggestion, delayMs, ctrl) {
|
|
15757
|
+
const truncated = suggestion.length > 80 ? `${suggestion.slice(0, 77)}\u2026` : suggestion;
|
|
15758
|
+
console.log(JSON.stringify({
|
|
15759
|
+
level: "info",
|
|
15760
|
+
event: "auto_proceed_started",
|
|
15761
|
+
suggestion: truncated,
|
|
15762
|
+
delayMs
|
|
15763
|
+
}));
|
|
15764
|
+
try {
|
|
15765
|
+
await autoProceedCountdown(opts, delayMs, suggestion, ctrl.signal);
|
|
15766
|
+
const runBlocks = [{ type: "text", text: suggestion }];
|
|
15767
|
+
const runResult = await opts.agent.run(runBlocks, { signal: ctrl.signal });
|
|
15768
|
+
console.log(JSON.stringify({
|
|
15769
|
+
level: "info",
|
|
15770
|
+
event: "auto_proceed_completed",
|
|
15771
|
+
suggestion: truncated,
|
|
15772
|
+
status: runResult.status,
|
|
15773
|
+
iterations: runResult.iterations
|
|
15774
|
+
}));
|
|
15775
|
+
opts.onAgentIterationComplete?.(
|
|
15776
|
+
estimateRequestTokensCalibrated(
|
|
15777
|
+
opts.agent.ctx.messages,
|
|
15778
|
+
opts.agent.ctx.systemPrompt,
|
|
15779
|
+
opts.agent.ctx.tools ?? []
|
|
15780
|
+
).total
|
|
15781
|
+
);
|
|
15782
|
+
if (runResult.status === "done" && runResult.finalText && opts.onSuggestionsParsed) {
|
|
15783
|
+
const parsed = parseSuggestionsFromOutput(runResult.finalText);
|
|
15784
|
+
opts.onSuggestionsParsed(parsed);
|
|
15785
|
+
}
|
|
15786
|
+
} finally {
|
|
15787
|
+
}
|
|
15788
|
+
}
|
|
15789
|
+
async function autoProceedCountdown(opts, delayMs, suggestion, signal) {
|
|
15790
|
+
const sec = Math.ceil(delayMs / 1e3);
|
|
15791
|
+
const truncated = suggestion.length > 100 ? `${suggestion.slice(0, 97)}\u2026` : suggestion;
|
|
15792
|
+
opts.renderer.write(
|
|
15793
|
+
`
|
|
15794
|
+
${color.cyan("\u23F3 Auto-proceeding")} in ${sec}s\u2026 ${color.dim("(Ctrl+C to cancel)")}
|
|
15795
|
+
`
|
|
15796
|
+
);
|
|
15797
|
+
opts.renderer.write(`${color.dim(" \u25B8")} ${color.dim(truncated)}
|
|
15798
|
+
`);
|
|
15799
|
+
const start = Date.now();
|
|
15800
|
+
let lastSec = sec;
|
|
15801
|
+
return new Promise((resolve5, reject) => {
|
|
15802
|
+
const onAbort = () => {
|
|
15803
|
+
clearInterval(interval);
|
|
15804
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
15805
|
+
};
|
|
15806
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
15807
|
+
const interval = setInterval(() => {
|
|
15808
|
+
if (signal.aborted) return;
|
|
15809
|
+
const elapsed = Date.now() - start;
|
|
15810
|
+
const remaining = Math.max(0, Math.ceil((delayMs - elapsed) / 1e3));
|
|
15811
|
+
if (remaining <= 0) {
|
|
15812
|
+
clearInterval(interval);
|
|
15813
|
+
signal.removeEventListener("abort", onAbort);
|
|
15814
|
+
opts.renderer.write(color.dim(` \u21B3 Proceeding with: ${truncated}
|
|
15815
|
+
`));
|
|
15816
|
+
resolve5();
|
|
15817
|
+
return;
|
|
15818
|
+
}
|
|
15819
|
+
if (remaining !== lastSec) {
|
|
15820
|
+
lastSec = remaining;
|
|
15821
|
+
opts.renderer.write(
|
|
15822
|
+
color.dim(` \u23F3 ${remaining}s remaining\u2026 (Ctrl+C to cancel)
|
|
15823
|
+
`)
|
|
15824
|
+
);
|
|
15825
|
+
}
|
|
15826
|
+
}, 500);
|
|
15827
|
+
});
|
|
15828
|
+
}
|
|
14934
15829
|
async function readPossiblyMultiline(opts) {
|
|
14935
15830
|
const firstPrompt = theme2.primary("\u203A ");
|
|
14936
15831
|
const contPrompt = color.dim("\xB7 ");
|
|
@@ -15023,6 +15918,11 @@ async function execute(deps) {
|
|
|
15023
15918
|
getAutonomy,
|
|
15024
15919
|
onAutonomy,
|
|
15025
15920
|
getNextPredict,
|
|
15921
|
+
onSuggestionsParsed,
|
|
15922
|
+
getSuggestions,
|
|
15923
|
+
autoProceedDelayMs,
|
|
15924
|
+
autoProceedMaxIterations,
|
|
15925
|
+
onValidateAutoProceed,
|
|
15026
15926
|
getEternalEngine,
|
|
15027
15927
|
getParallelEngine,
|
|
15028
15928
|
subscribeEternalIteration,
|
|
@@ -15251,6 +16151,7 @@ async function execute(deps) {
|
|
|
15251
16151
|
auditLevel: cfg.session?.auditLevel ?? "standard",
|
|
15252
16152
|
indexOnStart: cfg.indexing?.onSessionStart !== false,
|
|
15253
16153
|
maxIterations: cfg.tools?.maxIterations ?? 500,
|
|
16154
|
+
autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
|
|
15254
16155
|
debugStream: cfg.debugStream ?? false,
|
|
15255
16156
|
configScope: cfg.configScope ?? "global",
|
|
15256
16157
|
enhanceDelayMs: cfg.autonomy?.enhanceDelayMs ?? 6e4
|
|
@@ -15334,11 +16235,15 @@ async function execute(deps) {
|
|
|
15334
16235
|
autonomy.enhanceDelayMs = s.enhanceDelayMs;
|
|
15335
16236
|
decrypted.autonomy = autonomy;
|
|
15336
16237
|
}
|
|
16238
|
+
if (s.autoProceedMaxIterations !== void 0) {
|
|
16239
|
+
const autonomy = decrypted.autonomy ?? {};
|
|
16240
|
+
autonomy.autoProceedMaxIterations = s.autoProceedMaxIterations;
|
|
16241
|
+
decrypted.autonomy = autonomy;
|
|
16242
|
+
}
|
|
15337
16243
|
const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
|
|
15338
16244
|
const encrypted = encryptConfigSecrets$1(toWrite, vault);
|
|
15339
16245
|
if (targetPath !== wpaths.globalConfig) {
|
|
15340
|
-
await fsp4.mkdir(
|
|
15341
|
-
});
|
|
16246
|
+
await fsp4.mkdir(path10.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
|
|
15342
16247
|
}
|
|
15343
16248
|
await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
15344
16249
|
configStore.update({
|
|
@@ -15428,13 +16333,15 @@ async function execute(deps) {
|
|
|
15428
16333
|
return typeof metaMode === "string" ? metaMode : modeId ?? "default";
|
|
15429
16334
|
},
|
|
15430
16335
|
registerDebugStreamCallback: (cb) => {
|
|
15431
|
-
void import('@wrongstack/providers').then(
|
|
15432
|
-
(
|
|
16336
|
+
void import('@wrongstack/providers').then(({ setDebugStreamCallback }) => setDebugStreamCallback(cb)).catch(
|
|
16337
|
+
(err) => console.error("[execution] failed to register debug stream callback:", err)
|
|
15433
16338
|
);
|
|
15434
16339
|
},
|
|
15435
16340
|
restoreDebugStreamCallback: () => {
|
|
15436
16341
|
void import('@wrongstack/providers').then(
|
|
15437
16342
|
({ setDebugStreamCallback, defaultDebugStreamCallback }) => setDebugStreamCallback(defaultDebugStreamCallback)
|
|
16343
|
+
).catch(
|
|
16344
|
+
(err) => console.error("[execution] failed to restore debug stream callback:", err)
|
|
15438
16345
|
);
|
|
15439
16346
|
}
|
|
15440
16347
|
});
|
|
@@ -15466,11 +16373,16 @@ async function execute(deps) {
|
|
|
15466
16373
|
supportsVision,
|
|
15467
16374
|
attachments,
|
|
15468
16375
|
effectiveMaxContext,
|
|
15469
|
-
projectName:
|
|
16376
|
+
projectName: path10.basename(projectRoot) || void 0,
|
|
15470
16377
|
projectRoot,
|
|
15471
16378
|
getAutonomy,
|
|
15472
16379
|
onAutonomy,
|
|
15473
16380
|
getNextPredict,
|
|
16381
|
+
onSuggestionsParsed,
|
|
16382
|
+
getSuggestions,
|
|
16383
|
+
autoProceedDelayMs,
|
|
16384
|
+
autoProceedMaxIterations,
|
|
16385
|
+
onValidateAutoProceed,
|
|
15474
16386
|
getEternalEngine,
|
|
15475
16387
|
getParallelEngine,
|
|
15476
16388
|
skillLoader,
|
|
@@ -15481,7 +16393,7 @@ async function execute(deps) {
|
|
|
15481
16393
|
onAgentIterationComplete: director ? (tokens) => director.setLeaderContextPressure(tokens) : void 0
|
|
15482
16394
|
});
|
|
15483
16395
|
} finally {
|
|
15484
|
-
await webuiPromise.catch(() =>
|
|
16396
|
+
await webuiPromise.catch((err) => console.debug(`[execution] webui shutdown failed: ${err}`));
|
|
15485
16397
|
}
|
|
15486
16398
|
} else {
|
|
15487
16399
|
code = await runRepl({
|
|
@@ -15494,10 +16406,15 @@ async function execute(deps) {
|
|
|
15494
16406
|
supportsVision,
|
|
15495
16407
|
attachments,
|
|
15496
16408
|
effectiveMaxContext,
|
|
15497
|
-
projectName:
|
|
16409
|
+
projectName: path10.basename(projectRoot) || void 0,
|
|
15498
16410
|
getAutonomy,
|
|
15499
16411
|
onAutonomy,
|
|
15500
16412
|
getNextPredict,
|
|
16413
|
+
onSuggestionsParsed,
|
|
16414
|
+
getSuggestions,
|
|
16415
|
+
autoProceedDelayMs,
|
|
16416
|
+
onValidateAutoProceed,
|
|
16417
|
+
autoProceedMaxIterations,
|
|
15501
16418
|
getEternalEngine,
|
|
15502
16419
|
getParallelEngine,
|
|
15503
16420
|
skillLoader,
|
|
@@ -15626,7 +16543,7 @@ var MultiAgentHost = class {
|
|
|
15626
16543
|
doneCondition: { type: "all_tasks_done" },
|
|
15627
16544
|
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
15628
16545
|
};
|
|
15629
|
-
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ?
|
|
16546
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path10.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
15630
16547
|
this.director = new Director({
|
|
15631
16548
|
config: coordinatorConfig,
|
|
15632
16549
|
manifestPath: this.opts.manifestPath,
|
|
@@ -15961,10 +16878,30 @@ var MultiAgentHost = class {
|
|
|
15961
16878
|
model: opts?.model,
|
|
15962
16879
|
tools: opts?.tools
|
|
15963
16880
|
};
|
|
15964
|
-
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
|
|
16881
|
+
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig, description);
|
|
15965
16882
|
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
15966
16883
|
return { subagentId, taskId };
|
|
15967
16884
|
}
|
|
16885
|
+
/**
|
|
16886
|
+
* Spawn a fresh subagent, assign a task, and **await** its completion.
|
|
16887
|
+
*
|
|
16888
|
+
* Unlike `spawn()`, which returns immediately with spawn metadata, this
|
|
16889
|
+
* method blocks until the subagent finishes (success, failure, or timeout)
|
|
16890
|
+
* and returns the full `TaskResult`. Use this when the caller needs the
|
|
16891
|
+
* subagent's actual output — e.g. `/techstack` displaying the generated report
|
|
16892
|
+
* in chat, or `/spawn` showing the result inline.
|
|
16893
|
+
*
|
|
16894
|
+
* Optional `opts` lets the caller override the subagent's provider, model,
|
|
16895
|
+
* and tool slice per spawn.
|
|
16896
|
+
*/
|
|
16897
|
+
async spawnAndWait(description, opts) {
|
|
16898
|
+
const { taskId } = await this.spawn(description, opts);
|
|
16899
|
+
if (!this.director) throw new Error("Director is not initialized");
|
|
16900
|
+
const results = await this.director.awaitTasks([taskId]);
|
|
16901
|
+
const result = results[0];
|
|
16902
|
+
if (!result) throw new Error(`Task ${taskId} completed but no result returned`);
|
|
16903
|
+
return result;
|
|
16904
|
+
}
|
|
15968
16905
|
/**
|
|
15969
16906
|
* Common spawn + assign logic shared by both director mode and raw
|
|
15970
16907
|
* coordinator mode. Extracts the identical body from the two branches
|
|
@@ -15974,11 +16911,11 @@ var MultiAgentHost = class {
|
|
|
15974
16911
|
* Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
|
|
15975
16912
|
* and event emission — the helper only talks to the coordinator.
|
|
15976
16913
|
*/
|
|
15977
|
-
async _spawnAndAssign(subagentConfig) {
|
|
16914
|
+
async _spawnAndAssign(subagentConfig, description = "") {
|
|
15978
16915
|
const taskId = randomUUID();
|
|
15979
16916
|
if (!this.director) throw new Error("Director is not initialized");
|
|
15980
16917
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
15981
|
-
await this.director.assign({ id: taskId, description
|
|
16918
|
+
await this.director.assign({ id: taskId, description, subagentId });
|
|
15982
16919
|
return { subagentId, taskId };
|
|
15983
16920
|
}
|
|
15984
16921
|
/**
|
|
@@ -16095,16 +17032,16 @@ var MultiAgentHost = class {
|
|
|
16095
17032
|
if (this.director) return this.director;
|
|
16096
17033
|
this.opts.directorMode = true;
|
|
16097
17034
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
16098
|
-
this.opts.manifestPath =
|
|
17035
|
+
this.opts.manifestPath = path10.join(this.opts.fleetRoot, "fleet.json");
|
|
16099
17036
|
}
|
|
16100
17037
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
16101
|
-
this.opts.sharedScratchpadPath =
|
|
17038
|
+
this.opts.sharedScratchpadPath = path10.join(this.opts.fleetRoot, "shared");
|
|
16102
17039
|
}
|
|
16103
17040
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
16104
|
-
this.opts.sessionsRoot =
|
|
17041
|
+
this.opts.sessionsRoot = path10.join(this.opts.fleetRoot, "subagents");
|
|
16105
17042
|
}
|
|
16106
17043
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
16107
|
-
this.opts.stateCheckpointPath =
|
|
17044
|
+
this.opts.stateCheckpointPath = path10.join(this.opts.fleetRoot, "director-state.json");
|
|
16108
17045
|
}
|
|
16109
17046
|
await this.ensureDirector();
|
|
16110
17047
|
return this.director ?? null;
|
|
@@ -16276,11 +17213,11 @@ var SessionStats = class {
|
|
|
16276
17213
|
if (e.name === "bash") this.bashCommands++;
|
|
16277
17214
|
else if (e.name === "fetch") this.fetches++;
|
|
16278
17215
|
if (!e.ok) return;
|
|
16279
|
-
const
|
|
16280
|
-
if (e.name === "read" &&
|
|
16281
|
-
else if (e.name === "edit" &&
|
|
16282
|
-
else if (e.name === "write" &&
|
|
16283
|
-
this.writtenPaths.add(
|
|
17216
|
+
const path28 = typeof input?.path === "string" ? input.path : void 0;
|
|
17217
|
+
if (e.name === "read" && path28) this.readPaths.add(path28);
|
|
17218
|
+
else if (e.name === "edit" && path28) this.editedPaths.add(path28);
|
|
17219
|
+
else if (e.name === "write" && path28) {
|
|
17220
|
+
this.writtenPaths.add(path28);
|
|
16284
17221
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
16285
17222
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
16286
17223
|
}
|
|
@@ -16885,7 +17822,12 @@ async function setupCompaction(params) {
|
|
|
16885
17822
|
effectiveMaxContext,
|
|
16886
17823
|
// Calibrated estimator: recordActualUsage() is called after each API
|
|
16887
17824
|
// response so this converges on real token counts for compaction decisions.
|
|
16888
|
-
(ctx) => estimateRequestTokensCalibrated(
|
|
17825
|
+
(ctx) => estimateRequestTokensCalibrated(
|
|
17826
|
+
ctx.messages,
|
|
17827
|
+
ctx.systemPrompt,
|
|
17828
|
+
ctx.tools ?? [],
|
|
17829
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
17830
|
+
).total,
|
|
16889
17831
|
initialPolicy.thresholds,
|
|
16890
17832
|
{
|
|
16891
17833
|
aggressiveOn: initialPolicy.aggressiveOn,
|
|
@@ -17102,7 +18044,7 @@ function setupMetrics(params) {
|
|
|
17102
18044
|
const dumpMetrics = () => {
|
|
17103
18045
|
if (!metricsSink) return;
|
|
17104
18046
|
try {
|
|
17105
|
-
const out =
|
|
18047
|
+
const out = path10.join(wpaths.projectSessions, "metrics.json");
|
|
17106
18048
|
const snap = metricsSink.snapshot();
|
|
17107
18049
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
17108
18050
|
} catch {
|
|
@@ -17177,7 +18119,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
17177
18119
|
if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
|
|
17178
18120
|
const fp = payload.toolUse.input?.file_path;
|
|
17179
18121
|
if (typeof fp === "string" && fp.length > 0) {
|
|
17180
|
-
const abs =
|
|
18122
|
+
const abs = path10.resolve(payload.ctx.cwd, fp);
|
|
17181
18123
|
if (isIndexableFile(abs)) {
|
|
17182
18124
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
17183
18125
|
}
|
|
@@ -17192,11 +18134,11 @@ async function setupCodebaseIndexing(deps) {
|
|
|
17192
18134
|
let watcher;
|
|
17193
18135
|
if (idx.watchExternal) {
|
|
17194
18136
|
try {
|
|
17195
|
-
watcher =
|
|
18137
|
+
watcher = fs15.watch(projectRoot, { recursive: true }, (_event, filename) => {
|
|
17196
18138
|
if (!filename) return;
|
|
17197
18139
|
const rel = filename.toString();
|
|
17198
18140
|
if (isIgnored(rel)) return;
|
|
17199
|
-
const abs =
|
|
18141
|
+
const abs = path10.resolve(projectRoot, rel);
|
|
17200
18142
|
if (!isIndexableFile(abs)) return;
|
|
17201
18143
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
17202
18144
|
});
|
|
@@ -17443,7 +18385,7 @@ async function setupSession(params) {
|
|
|
17443
18385
|
} = params;
|
|
17444
18386
|
sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
|
|
17445
18387
|
if (count > 0) renderer.writeInfo(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
|
|
17446
|
-
}).catch(() =>
|
|
18388
|
+
}).catch((err) => console.debug(`[session] prune failed: ${err}`));
|
|
17447
18389
|
let resumeId = typeof flags["resume"] === "string" ? flags["resume"] : void 0;
|
|
17448
18390
|
const recoveryLock = new RecoveryLock({ dir: wpaths.projectSessions, sessionStore });
|
|
17449
18391
|
if (!resumeId && !flags["no-recovery"]) {
|
|
@@ -17485,9 +18427,9 @@ async function setupSession(params) {
|
|
|
17485
18427
|
const sessionRef = { current: session };
|
|
17486
18428
|
await recoveryLock.write(session?.id).catch(() => void 0);
|
|
17487
18429
|
const attachments = new DefaultAttachmentStore({
|
|
17488
|
-
spoolDir:
|
|
18430
|
+
spoolDir: path10.join(wpaths.projectSessions, session?.id, "attachments")
|
|
17489
18431
|
});
|
|
17490
|
-
const queueStore = new QueueStore({ dir:
|
|
18432
|
+
const queueStore = new QueueStore({ dir: path10.join(wpaths.projectSessions, session?.id) });
|
|
17491
18433
|
const ctxSignal = new AbortController().signal;
|
|
17492
18434
|
const context = new Context({
|
|
17493
18435
|
systemPrompt,
|
|
@@ -17500,7 +18442,7 @@ async function setupSession(params) {
|
|
|
17500
18442
|
model: config.model
|
|
17501
18443
|
});
|
|
17502
18444
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
17503
|
-
const todosCheckpointPath =
|
|
18445
|
+
const todosCheckpointPath = path10.join(wpaths.projectSessions, `${session?.id}.todos.json`);
|
|
17504
18446
|
if (resumeId) {
|
|
17505
18447
|
try {
|
|
17506
18448
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -17518,15 +18460,15 @@ async function setupSession(params) {
|
|
|
17518
18460
|
todosCheckpointPath,
|
|
17519
18461
|
session?.id
|
|
17520
18462
|
);
|
|
17521
|
-
const planPath =
|
|
18463
|
+
const planPath = path10.join(wpaths.projectSessions, `${session?.id}.plan.json`);
|
|
17522
18464
|
context.state.setMeta("plan.path", planPath);
|
|
17523
|
-
const taskPath =
|
|
18465
|
+
const taskPath = path10.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
|
|
17524
18466
|
context.state.setMeta("task.path", taskPath);
|
|
17525
18467
|
let dirState;
|
|
17526
18468
|
if (resumeId) {
|
|
17527
18469
|
try {
|
|
17528
|
-
const fleetRoot =
|
|
17529
|
-
dirState = await loadDirectorState(
|
|
18470
|
+
const fleetRoot = path10.join(wpaths.projectSessions, session?.id);
|
|
18471
|
+
dirState = await loadDirectorState(path10.join(fleetRoot, "director-state.json"));
|
|
17530
18472
|
if (dirState) {
|
|
17531
18473
|
const tCounts = {};
|
|
17532
18474
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -17566,7 +18508,7 @@ function resolveBundledSkillsDir2() {
|
|
|
17566
18508
|
try {
|
|
17567
18509
|
const req2 = createRequire(import.meta.url);
|
|
17568
18510
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
17569
|
-
return
|
|
18511
|
+
return path10.join(path10.dirname(corePkg), "skills");
|
|
17570
18512
|
} catch {
|
|
17571
18513
|
return void 0;
|
|
17572
18514
|
}
|
|
@@ -17677,9 +18619,8 @@ async function main(argv) {
|
|
|
17677
18619
|
updateInfo
|
|
17678
18620
|
} = ctx;
|
|
17679
18621
|
updateInfo = await printUpdateNotice(updateInfo);
|
|
17680
|
-
const { setDebugStreamEnabled
|
|
18622
|
+
const { setDebugStreamEnabled } = await import('@wrongstack/providers');
|
|
17681
18623
|
if (config.debugStream) setDebugStreamEnabled(true);
|
|
17682
|
-
setDebugStreamCallback(defaultDebugStreamCallback);
|
|
17683
18624
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
17684
18625
|
const events = new EventBus();
|
|
17685
18626
|
events.setLogger(logger);
|
|
@@ -17766,7 +18707,7 @@ async function main(argv) {
|
|
|
17766
18707
|
modeId,
|
|
17767
18708
|
modePrompt,
|
|
17768
18709
|
modelCapabilities,
|
|
17769
|
-
planPath: () => sessionRef.current ?
|
|
18710
|
+
planPath: () => sessionRef.current ? path10.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
|
|
17770
18711
|
contributors: [
|
|
17771
18712
|
// Injects the ETERNAL AUTONOMY block when the user has activated
|
|
17772
18713
|
// a long-running autonomy engine. Without this, the per-iteration
|
|
@@ -18136,6 +19077,7 @@ async function main(argv) {
|
|
|
18136
19077
|
})();
|
|
18137
19078
|
autonomyModeRef.current = autonomyMode;
|
|
18138
19079
|
let nextPredictEnabled = config.nextPrediction === true;
|
|
19080
|
+
let currentSuggestions = [];
|
|
18139
19081
|
let eternalEngine = null;
|
|
18140
19082
|
let parallelEngine = null;
|
|
18141
19083
|
const eternalListeners = /* @__PURE__ */ new Set();
|
|
@@ -18156,12 +19098,12 @@ async function main(argv) {
|
|
|
18156
19098
|
}
|
|
18157
19099
|
}
|
|
18158
19100
|
};
|
|
18159
|
-
const fleetRoot = directorMode ?
|
|
18160
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
18161
|
-
const sharedScratchpadPath = directorMode ?
|
|
18162
|
-
const subagentSessionsRoot = directorMode ?
|
|
18163
|
-
const stateCheckpointPath = directorMode ?
|
|
18164
|
-
const fleetRootForPromotion =
|
|
19101
|
+
const fleetRoot = directorMode ? path10.join(wpaths.projectSessions, session.id) : void 0;
|
|
19102
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path10.join(expectDefined(fleetRoot), "fleet.json") : void 0;
|
|
19103
|
+
const sharedScratchpadPath = directorMode ? path10.join(expectDefined(fleetRoot), "shared") : void 0;
|
|
19104
|
+
const subagentSessionsRoot = directorMode ? path10.join(expectDefined(fleetRoot), "subagents") : void 0;
|
|
19105
|
+
const stateCheckpointPath = directorMode ? path10.join(expectDefined(fleetRoot), "director-state.json") : void 0;
|
|
19106
|
+
const fleetRootForPromotion = path10.join(wpaths.projectSessions, session.id);
|
|
18165
19107
|
const brainQueue = new BrainDecisionQueue(events);
|
|
18166
19108
|
const brain = new ObservableBrainArbiter(
|
|
18167
19109
|
new HumanEscalatingBrainArbiter(new DefaultBrainArbiter(), brainQueue),
|
|
@@ -18334,6 +19276,25 @@ async function main(argv) {
|
|
|
18334
19276
|
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
18335
19277
|
return `Spawned subagent ${subagentId}${tag} for task ${taskId}. Use /agents to track progress.`;
|
|
18336
19278
|
},
|
|
19279
|
+
onSpawnAndWait: async (description, spawnOpts) => {
|
|
19280
|
+
const result = await multiAgentHost.spawnAndWait(description, spawnOpts);
|
|
19281
|
+
const tags = [];
|
|
19282
|
+
if (spawnOpts?.provider) tags.push(spawnOpts.provider);
|
|
19283
|
+
if (spawnOpts?.model) tags.push(spawnOpts.model);
|
|
19284
|
+
if (spawnOpts?.name) tags.push(spawnOpts.name);
|
|
19285
|
+
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
19286
|
+
const secs = (result.durationMs / 1e3).toFixed(result.durationMs < 1e4 ? 1 : 0);
|
|
19287
|
+
const icon = result.status === "success" ? "\u2713" : result.status === "timeout" ? "\u23F1" : result.status === "stopped" ? "\u2298" : "\u2717";
|
|
19288
|
+
const resultPreview = typeof result.result === "string" && result.result.trim() ? `
|
|
19289
|
+
${color.dim("\u2500".repeat(40))}
|
|
19290
|
+
${result.result.trim().slice(0, 600)}${result.result.trim().length > 600 ? "\n\u2026" : ""}
|
|
19291
|
+
${color.dim("\u2500".repeat(40))}` : "";
|
|
19292
|
+
return [
|
|
19293
|
+
`${icon} ${color.bold(tag ? tag.slice(1) : "subagent")} ${result.status} (${result.iterations} iter \xB7 ${result.toolCalls} tools \xB7 ${secs}s)`,
|
|
19294
|
+
resultPreview,
|
|
19295
|
+
result.error ? ` ${color.amber(`error: ${result.error.message}`)}` : ""
|
|
19296
|
+
].filter(Boolean).join("\n");
|
|
19297
|
+
},
|
|
18337
19298
|
onAgents: (subagentId) => {
|
|
18338
19299
|
const s = multiAgentHost.status();
|
|
18339
19300
|
if (subagentId) {
|
|
@@ -18526,7 +19487,7 @@ async function main(argv) {
|
|
|
18526
19487
|
return director.spawn(cfg);
|
|
18527
19488
|
},
|
|
18528
19489
|
onFleetLog: async (subagentId, mode) => {
|
|
18529
|
-
const subagentsRoot =
|
|
19490
|
+
const subagentsRoot = path10.join(fleetRootForPromotion, "subagents");
|
|
18530
19491
|
let runDirs;
|
|
18531
19492
|
try {
|
|
18532
19493
|
runDirs = await fsp4.readdir(subagentsRoot);
|
|
@@ -18535,7 +19496,7 @@ async function main(argv) {
|
|
|
18535
19496
|
}
|
|
18536
19497
|
const found = [];
|
|
18537
19498
|
for (const runId of runDirs) {
|
|
18538
|
-
const runDir =
|
|
19499
|
+
const runDir = path10.join(subagentsRoot, runId);
|
|
18539
19500
|
let files;
|
|
18540
19501
|
try {
|
|
18541
19502
|
files = await fsp4.readdir(runDir);
|
|
@@ -18544,7 +19505,7 @@ async function main(argv) {
|
|
|
18544
19505
|
}
|
|
18545
19506
|
for (const f of files) {
|
|
18546
19507
|
if (!f.endsWith(".jsonl")) continue;
|
|
18547
|
-
const full =
|
|
19508
|
+
const full = path10.join(runDir, f);
|
|
18548
19509
|
try {
|
|
18549
19510
|
const stat4 = await fsp4.stat(full);
|
|
18550
19511
|
found.push({
|
|
@@ -18645,7 +19606,7 @@ async function main(argv) {
|
|
|
18645
19606
|
}
|
|
18646
19607
|
const dir = await multiAgentHost.ensureDirector();
|
|
18647
19608
|
if (!dir) return "Director is not available.";
|
|
18648
|
-
const dirStatePath =
|
|
19609
|
+
const dirStatePath = path10.join(fleetRootForPromotion, "director-state.json");
|
|
18649
19610
|
const prior = await loadDirectorState(dirStatePath);
|
|
18650
19611
|
if (!prior) {
|
|
18651
19612
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -18714,9 +19675,9 @@ async function main(argv) {
|
|
|
18714
19675
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
18715
19676
|
toolRegistry.register(tool);
|
|
18716
19677
|
}
|
|
18717
|
-
const mp =
|
|
18718
|
-
const sp =
|
|
18719
|
-
const ss =
|
|
19678
|
+
const mp = path10.join(fleetRootForPromotion, "fleet.json");
|
|
19679
|
+
const sp = path10.join(fleetRootForPromotion, "shared");
|
|
19680
|
+
const ss = path10.join(fleetRootForPromotion, "subagents");
|
|
18720
19681
|
const lines = [
|
|
18721
19682
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
18722
19683
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -18790,6 +19751,12 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
18790
19751
|
}
|
|
18791
19752
|
return nextPredictEnabled;
|
|
18792
19753
|
},
|
|
19754
|
+
onSuggestions: (suggestions) => {
|
|
19755
|
+
if (suggestions !== void 0) {
|
|
19756
|
+
currentSuggestions = suggestions;
|
|
19757
|
+
}
|
|
19758
|
+
return currentSuggestions;
|
|
19759
|
+
},
|
|
18793
19760
|
onAutonomy: (setTo) => {
|
|
18794
19761
|
if (setTo !== void 0) {
|
|
18795
19762
|
autonomyMode = setTo;
|
|
@@ -18866,6 +19833,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
18866
19833
|
message: `\u26A0 ${color.yellow(`${lines.length} uncommitted change${lines.length > 1 ? "s" : ""}`)} \u2014 session ended without commit`
|
|
18867
19834
|
};
|
|
18868
19835
|
}
|
|
19836
|
+
return void 0;
|
|
18869
19837
|
},
|
|
18870
19838
|
onClear: () => {
|
|
18871
19839
|
if (flags.tui && !flags["no-tui"]) return;
|
|
@@ -19035,6 +20003,54 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
19035
20003
|
return autonomyMode;
|
|
19036
20004
|
},
|
|
19037
20005
|
getNextPredict: () => nextPredictEnabled,
|
|
20006
|
+
onSuggestionsParsed: (suggestions) => {
|
|
20007
|
+
currentSuggestions = suggestions ?? [];
|
|
20008
|
+
},
|
|
20009
|
+
getSuggestions: () => currentSuggestions,
|
|
20010
|
+
autoProceedDelayMs: config.autonomy?.autoProceedDelayMs ?? 45e3,
|
|
20011
|
+
autoProceedMaxIterations: config.autonomy?.autoProceedMaxIterations ?? 50,
|
|
20012
|
+
onValidateAutoProceed: async (suggestion, lastOutput) => {
|
|
20013
|
+
try {
|
|
20014
|
+
const resp = await context.provider.complete(
|
|
20015
|
+
{
|
|
20016
|
+
model: context.model,
|
|
20017
|
+
system: [
|
|
20018
|
+
{
|
|
20019
|
+
type: "text",
|
|
20020
|
+
text: "You are a safety validator for an autonomous coding agent. Your ONLY job is to decide whether the agent should auto-proceed with a suggested next step, or whether a human should review first. Reply with exactly one word: YES or NO."
|
|
20021
|
+
}
|
|
20022
|
+
],
|
|
20023
|
+
messages: [
|
|
20024
|
+
{
|
|
20025
|
+
role: "user",
|
|
20026
|
+
content: [
|
|
20027
|
+
{
|
|
20028
|
+
type: "text",
|
|
20029
|
+
text: `The autonomous agent just completed a turn and generated this top-ranked next-step suggestion:
|
|
20030
|
+
|
|
20031
|
+
"${suggestion}"
|
|
20032
|
+
|
|
20033
|
+
${lastOutput ? `Recent agent output:
|
|
20034
|
+
${lastOutput.slice(0, 500)}
|
|
20035
|
+
|
|
20036
|
+
` : ""}Should the agent auto-proceed with this suggestion, or should a human review first?
|
|
20037
|
+
|
|
20038
|
+
Reply YES to auto-proceed, NO to wait for human input.`
|
|
20039
|
+
}
|
|
20040
|
+
]
|
|
20041
|
+
}
|
|
20042
|
+
],
|
|
20043
|
+
maxTokens: 5,
|
|
20044
|
+
temperature: 0
|
|
20045
|
+
},
|
|
20046
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
20047
|
+
);
|
|
20048
|
+
const text = resp.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("").trim().toUpperCase();
|
|
20049
|
+
return text.startsWith("YES");
|
|
20050
|
+
} catch {
|
|
20051
|
+
return false;
|
|
20052
|
+
}
|
|
20053
|
+
},
|
|
19038
20054
|
getEternalEngine: () => eternalEngine,
|
|
19039
20055
|
getParallelEngine: () => parallelEngine,
|
|
19040
20056
|
subscribeEternalIteration: (fn) => {
|