@wrongstack/cli 0.260.0 → 0.265.1
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 +1818 -555
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fsp5 from 'fs/promises';
|
|
3
|
-
import * as
|
|
3
|
+
import * as path4 from 'path';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
-
import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, DefaultSecretScrubber, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning,
|
|
6
|
-
import {
|
|
5
|
+
import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, DefaultSecretScrubber, projectHash, wstackGlobalRoot, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, DEFAULT_CONTEXT_WINDOW_MODE_ID, recentTextTurns, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, DefaultLogger, DefaultModelsRegistry, isStdinTTY, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, AutonomousCoordinator, noOpVault, decryptConfigSecrets, encryptConfigSecrets, atomicWrite, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, FsError, ConfigError, InputBuilder, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, resolveMailboxIdentity, isSecretField as isSecretField$1 } from '@wrongstack/core';
|
|
6
|
+
import { decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, DefaultSecretVault, isSecretField } from '@wrongstack/core/security';
|
|
7
7
|
import * as crypto3 from 'crypto';
|
|
8
8
|
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
10
|
import * as os from 'os';
|
|
11
11
|
import os__default from 'os';
|
|
12
|
-
import { findFreePort, AutoPhaseWebSocketHandler, handleShellOpen, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
|
|
12
|
+
import { findFreePort, AutoPhaseWebSocketHandler, generateAuthToken, verifyClient, handleGitInfo, handleShellOpen, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
|
|
13
13
|
import { makeProviderFromConfig, capabilitiesFor, buildProviderFactoriesFromRegistry } from '@wrongstack/providers';
|
|
14
|
-
import {
|
|
14
|
+
import { toErrorMessage } from '@wrongstack/core/utils';
|
|
15
|
+
import { getProcessRegistry, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, TIER2_TOOLS, TIER3_TOOLS, TIER1_TOOLS, resetIndexCircuitBreaker } from '@wrongstack/tools';
|
|
15
16
|
import { DefaultSessionStore } from '@wrongstack/core/storage';
|
|
17
|
+
import { probeLocalLlm } from '@wrongstack/runtime/probe';
|
|
18
|
+
import * as fs2 from 'fs';
|
|
19
|
+
import { watch, writeFileSync, existsSync, readFileSync } from 'fs';
|
|
20
|
+
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
16
21
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
17
22
|
import { spawn, execFile, execFileSync } from 'child_process';
|
|
18
23
|
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
19
|
-
import * as fs2 from 'fs';
|
|
20
|
-
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
21
24
|
import { fileURLToPath } from 'url';
|
|
22
25
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
23
26
|
import * as readline from 'readline';
|
|
27
|
+
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, runEnsemble, renderEnsembleText, EnsembleRegistry, makeACPSubagentRunnerWithStop, findAgentDescriptor } from '@wrongstack/acp';
|
|
24
28
|
import { parseNextSteps } from '@wrongstack/tui';
|
|
25
|
-
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
26
29
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
27
|
-
import {
|
|
30
|
+
import { SubagentBudget } from '@wrongstack/core/coordination';
|
|
28
31
|
import { loadBenchConfig, reportHeaderLine, readSummary, renderMarkdownReport, createPolyglotSuite, createSwebenchSuite, runBenchmark, writeJsonArtifacts, collectCellPredictions, writePredictionsJsonl, gradePolyglot, gradeSwebench } from '@wrongstack/bench';
|
|
29
32
|
import { allServers } from '@wrongstack/core/infrastructure';
|
|
30
33
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
@@ -66,15 +69,57 @@ async function pathExists(file) {
|
|
|
66
69
|
return false;
|
|
67
70
|
}
|
|
68
71
|
}
|
|
72
|
+
async function hasRootFileWithSuffix(root, suffixes) {
|
|
73
|
+
let names;
|
|
74
|
+
try {
|
|
75
|
+
names = await fsp5.readdir(root);
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const lower = suffixes.map((s) => s.toLowerCase());
|
|
80
|
+
return names.some((n) => lower.some((s) => n.toLowerCase().endsWith(s)));
|
|
81
|
+
}
|
|
82
|
+
function parseCiRunCommands(yaml) {
|
|
83
|
+
const commands = [];
|
|
84
|
+
const lines = yaml.split(/\r?\n/);
|
|
85
|
+
for (let i = 0; i < lines.length; i++) {
|
|
86
|
+
const line = lines[i] ?? "";
|
|
87
|
+
const m = /^(\s*)(?:-\s+)?run:\s*(.*)$/.exec(line);
|
|
88
|
+
if (!m) continue;
|
|
89
|
+
const indent = (m[1] ?? "").length;
|
|
90
|
+
const inline = (m[2] ?? "").trim();
|
|
91
|
+
if (inline && inline !== "|" && inline !== ">" && !/^[|>][+-]?$/.test(inline)) {
|
|
92
|
+
commands.push(inline);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
96
|
+
const body = lines[j] ?? "";
|
|
97
|
+
if (body.trim() === "") continue;
|
|
98
|
+
const bodyIndent = body.length - body.trimStart().length;
|
|
99
|
+
if (bodyIndent <= indent) break;
|
|
100
|
+
commands.push(body.trim());
|
|
101
|
+
i = j;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return commands.filter((c) => c !== "" && !/^(#|cd\s|echo\s|export\s|set\s|if\s)/.test(c));
|
|
105
|
+
}
|
|
106
|
+
function applyCiCommands(facts, commands) {
|
|
107
|
+
const find = (re) => commands.find((c) => re.test(c));
|
|
108
|
+
facts.test ??= find(/\b(test|pytest|vitest|jest|go test|cargo test|mix test|rspec)\b/i);
|
|
109
|
+
facts.lint ??= find(/\b(lint|eslint|biome|clippy|ruff|flake8|golangci-lint|fmt --check)\b/i);
|
|
110
|
+
facts.build ??= find(
|
|
111
|
+
/\b(build|compile|tsc|cargo build|go build|mvn .*package|gradle .*build)\b/i
|
|
112
|
+
);
|
|
113
|
+
}
|
|
69
114
|
async function detectPackageManager2(root, declared) {
|
|
70
115
|
if (declared) {
|
|
71
116
|
const name = declared.split("@")[0];
|
|
72
117
|
if (name) return name;
|
|
73
118
|
}
|
|
74
|
-
if (await pathExists(
|
|
75
|
-
if (await pathExists(
|
|
76
|
-
if (await pathExists(
|
|
77
|
-
if (await pathExists(
|
|
119
|
+
if (await pathExists(path4.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
120
|
+
if (await pathExists(path4.join(root, "bun.lockb"))) return "bun";
|
|
121
|
+
if (await pathExists(path4.join(root, "bun.lock"))) return "bun";
|
|
122
|
+
if (await pathExists(path4.join(root, "yarn.lock"))) return "yarn";
|
|
78
123
|
return "npm";
|
|
79
124
|
}
|
|
80
125
|
function hasUsableScript(scripts, name) {
|
|
@@ -92,10 +137,50 @@ function parseMakeTargets(makefile) {
|
|
|
92
137
|
}
|
|
93
138
|
return targets;
|
|
94
139
|
}
|
|
140
|
+
async function scanSourceTree(root) {
|
|
141
|
+
const counts = /* @__PURE__ */ new Map();
|
|
142
|
+
const entryPoints = [];
|
|
143
|
+
const topDirs = [];
|
|
144
|
+
let fileCount = 0;
|
|
145
|
+
const MAX_FILES = 5e3;
|
|
146
|
+
const MAX_DEPTH = 6;
|
|
147
|
+
async function walk(dir, depth, rel) {
|
|
148
|
+
if (fileCount >= MAX_FILES || depth > MAX_DEPTH) return;
|
|
149
|
+
let entries;
|
|
150
|
+
try {
|
|
151
|
+
entries = await fsp5.readdir(dir, { withFileTypes: true });
|
|
152
|
+
} catch {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const e of entries) {
|
|
156
|
+
if (fileCount >= MAX_FILES) return;
|
|
157
|
+
const name = e.name;
|
|
158
|
+
if (e.isDirectory()) {
|
|
159
|
+
if (SCAN_IGNORE_DIRS.has(name) || name.startsWith(".")) continue;
|
|
160
|
+
if (depth === 0) topDirs.push(name);
|
|
161
|
+
await walk(path4.join(dir, name), depth + 1, rel ? `${rel}/${name}` : name);
|
|
162
|
+
} else if (e.isFile()) {
|
|
163
|
+
fileCount++;
|
|
164
|
+
const ext = path4.extname(name).toLowerCase();
|
|
165
|
+
const lang = EXT_LANG[ext];
|
|
166
|
+
if (!lang) continue;
|
|
167
|
+
counts.set(lang, (counts.get(lang) ?? 0) + 1);
|
|
168
|
+
const base = name.slice(0, name.length - ext.length).toLowerCase();
|
|
169
|
+
if (entryPoints.length < 10 && ENTRY_BASENAMES.has(base)) {
|
|
170
|
+
entryPoints.push(rel ? `${rel}/${name}` : name);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
await walk(root, 0, "");
|
|
176
|
+
if (counts.size === 0) return void 0;
|
|
177
|
+
const languages = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([lang, n]) => `${lang} (${n})`);
|
|
178
|
+
return { languages, entryPoints, topDirs: topDirs.sort() };
|
|
179
|
+
}
|
|
95
180
|
async function detectProjectFacts(root) {
|
|
96
181
|
const facts = { hints: [] };
|
|
97
182
|
try {
|
|
98
|
-
const pkg = JSON.parse(await fsp5.readFile(
|
|
183
|
+
const pkg = JSON.parse(await fsp5.readFile(path4.join(root, "package.json"), "utf8"));
|
|
99
184
|
const scripts = pkg.scripts ?? {};
|
|
100
185
|
const pm = await detectPackageManager2(root, pkg.packageManager);
|
|
101
186
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -109,14 +194,14 @@ async function detectProjectFacts(root) {
|
|
|
109
194
|
} catch {
|
|
110
195
|
}
|
|
111
196
|
try {
|
|
112
|
-
if (!await pathExists(
|
|
197
|
+
if (!await pathExists(path4.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
113
198
|
facts.test ??= "pytest";
|
|
114
199
|
facts.lint ??= "ruff check .";
|
|
115
200
|
facts.hints.push("pyproject.toml");
|
|
116
201
|
} catch {
|
|
117
202
|
}
|
|
118
203
|
try {
|
|
119
|
-
if (!await pathExists(
|
|
204
|
+
if (!await pathExists(path4.join(root, "go.mod"))) throw new Error("not go");
|
|
120
205
|
facts.build ??= "go build ./...";
|
|
121
206
|
facts.test ??= "go test ./...";
|
|
122
207
|
facts.run ??= "go run .";
|
|
@@ -124,7 +209,7 @@ async function detectProjectFacts(root) {
|
|
|
124
209
|
} catch {
|
|
125
210
|
}
|
|
126
211
|
try {
|
|
127
|
-
if (!await pathExists(
|
|
212
|
+
if (!await pathExists(path4.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
128
213
|
facts.build ??= "cargo build";
|
|
129
214
|
facts.test ??= "cargo test";
|
|
130
215
|
facts.lint ??= "cargo clippy";
|
|
@@ -133,7 +218,7 @@ async function detectProjectFacts(root) {
|
|
|
133
218
|
} catch {
|
|
134
219
|
}
|
|
135
220
|
try {
|
|
136
|
-
const makefile = await fsp5.readFile(
|
|
221
|
+
const makefile = await fsp5.readFile(path4.join(root, "Makefile"), "utf8");
|
|
137
222
|
const targets = parseMakeTargets(makefile);
|
|
138
223
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
139
224
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -143,6 +228,118 @@ async function detectProjectFacts(root) {
|
|
|
143
228
|
facts.hints.push("Makefile");
|
|
144
229
|
} catch {
|
|
145
230
|
}
|
|
231
|
+
try {
|
|
232
|
+
const composer = JSON.parse(await fsp5.readFile(path4.join(root, "composer.json"), "utf8"));
|
|
233
|
+
const scripts = composer.scripts ?? {};
|
|
234
|
+
if ("test" in scripts) facts.test ??= "composer test";
|
|
235
|
+
if ("lint" in scripts) facts.lint ??= "composer lint";
|
|
236
|
+
facts.hints.push("composer.json");
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
if (!await pathExists(path4.join(root, "pom.xml"))) throw new Error("not maven");
|
|
241
|
+
facts.build ??= "mvn package";
|
|
242
|
+
facts.test ??= "mvn test";
|
|
243
|
+
facts.hints.push("pom.xml");
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
const hasGradle = await pathExists(path4.join(root, "build.gradle")) || await pathExists(path4.join(root, "build.gradle.kts"));
|
|
248
|
+
if (!hasGradle) throw new Error("not gradle");
|
|
249
|
+
const g = await pathExists(path4.join(root, "gradlew")) ? "./gradlew" : "gradle";
|
|
250
|
+
facts.build ??= `${g} build`;
|
|
251
|
+
facts.test ??= `${g} test`;
|
|
252
|
+
facts.hints.push("Gradle");
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const hasDotnet = await pathExists(path4.join(root, "global.json")) || await hasRootFileWithSuffix(root, [".csproj", ".fsproj", ".sln"]);
|
|
257
|
+
if (!hasDotnet) throw new Error("not dotnet");
|
|
258
|
+
facts.build ??= "dotnet build";
|
|
259
|
+
facts.test ??= "dotnet test";
|
|
260
|
+
facts.run ??= "dotnet run";
|
|
261
|
+
facts.hints.push(".NET project");
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
if (!await pathExists(path4.join(root, "mix.exs"))) throw new Error("not elixir");
|
|
266
|
+
facts.build ??= "mix compile";
|
|
267
|
+
facts.test ??= "mix test";
|
|
268
|
+
facts.lint ??= "mix format --check-formatted";
|
|
269
|
+
facts.run ??= "mix run";
|
|
270
|
+
facts.hints.push("mix.exs");
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
if (!await pathExists(path4.join(root, "pubspec.yaml"))) throw new Error("not dart");
|
|
275
|
+
facts.test ??= "dart test";
|
|
276
|
+
facts.lint ??= "dart analyze";
|
|
277
|
+
facts.hints.push("pubspec.yaml");
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const hasDeno = await pathExists(path4.join(root, "deno.json")) || await pathExists(path4.join(root, "deno.jsonc"));
|
|
282
|
+
if (!hasDeno) throw new Error("not deno");
|
|
283
|
+
facts.test ??= "deno test";
|
|
284
|
+
facts.lint ??= "deno lint";
|
|
285
|
+
facts.hints.push("deno.json");
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
if (!await pathExists(path4.join(root, "Package.swift"))) throw new Error("not swift");
|
|
290
|
+
facts.build ??= "swift build";
|
|
291
|
+
facts.test ??= "swift test";
|
|
292
|
+
facts.run ??= "swift run";
|
|
293
|
+
facts.hints.push("Package.swift");
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
if (!await pathExists(path4.join(root, "Gemfile"))) throw new Error("not ruby");
|
|
298
|
+
if (await pathExists(path4.join(root, "Rakefile"))) facts.test ??= "bundle exec rake test";
|
|
299
|
+
facts.hints.push("Gemfile");
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
if (!await pathExists(path4.join(root, "CMakeLists.txt"))) throw new Error("not cmake");
|
|
304
|
+
facts.build ??= "cmake -B build && cmake --build build";
|
|
305
|
+
facts.test ??= "ctest --test-dir build";
|
|
306
|
+
facts.hints.push("CMakeLists.txt");
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const hasPip = await pathExists(path4.join(root, "requirements.txt")) || await pathExists(path4.join(root, "setup.py")) || await pathExists(path4.join(root, "setup.cfg"));
|
|
311
|
+
if (!hasPip) throw new Error("not pip");
|
|
312
|
+
facts.test ??= "pytest";
|
|
313
|
+
facts.hints.push("requirements.txt");
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
if (!facts.build || !facts.test || !facts.lint) {
|
|
317
|
+
try {
|
|
318
|
+
const wfDir = path4.join(root, ".github", "workflows");
|
|
319
|
+
const wfNames = (await fsp5.readdir(wfDir)).filter((n) => /\.ya?ml$/i.test(n));
|
|
320
|
+
const commands = [];
|
|
321
|
+
for (const n of wfNames) {
|
|
322
|
+
commands.push(...parseCiRunCommands(await fsp5.readFile(path4.join(wfDir, n), "utf8")));
|
|
323
|
+
}
|
|
324
|
+
if (commands.length > 0) {
|
|
325
|
+
const before = { build: facts.build, test: facts.test, lint: facts.lint };
|
|
326
|
+
applyCiCommands(facts, commands);
|
|
327
|
+
if (facts.build !== before.build || facts.test !== before.test || facts.lint !== before.lint) {
|
|
328
|
+
facts.hints.push(".github/workflows");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!facts.build && !facts.test && !facts.run && !facts.lint) {
|
|
335
|
+
const scan = await scanSourceTree(root);
|
|
336
|
+
if (scan) {
|
|
337
|
+
facts.languages = scan.languages;
|
|
338
|
+
if (scan.entryPoints.length > 0) facts.entryPoints = scan.entryPoints;
|
|
339
|
+
if (scan.topDirs.length > 0) facts.topDirs = scan.topDirs;
|
|
340
|
+
facts.hints.push(`source scan: ${scan.languages.join(", ")}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
146
343
|
return facts;
|
|
147
344
|
}
|
|
148
345
|
function renderAgentsTemplate(f) {
|
|
@@ -150,6 +347,16 @@ function renderAgentsTemplate(f) {
|
|
|
150
347
|
const hints = f.hints.length > 0 ? `
|
|
151
348
|
|
|
152
349
|
> Auto-detected: ${f.hints.join(", ")}` : "";
|
|
350
|
+
const runtime = f.languages?.length ? `_CLI, server, browser, worker, library, package?_ \u2014 detected: ${f.languages.join(", ")}` : "_CLI, server, browser, worker, library, package?_";
|
|
351
|
+
const keyFileRows = [];
|
|
352
|
+
for (const ep of f.entryPoints ?? [])
|
|
353
|
+
keyFileRows.push(`| \`${ep}\` | _Likely entry point (detected)_ |`);
|
|
354
|
+
for (const dir of f.topDirs ?? [])
|
|
355
|
+
keyFileRows.push(`| \`${dir}/\` | _Top-level directory (detected)_ |`);
|
|
356
|
+
const keyFiles = keyFileRows.length > 0 ? keyFileRows.join("\n") : `| _src/_ | _Main source entry point(s)_ |
|
|
357
|
+
| _tests/_ | _Test root or convention_ |
|
|
358
|
+
| _docs/_ | _Architecture, runbooks, design notes_ |
|
|
359
|
+
| _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |`;
|
|
153
360
|
return `# AGENTS.md
|
|
154
361
|
|
|
155
362
|
> **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
|
|
@@ -161,7 +368,7 @@ function renderAgentsTemplate(f) {
|
|
|
161
368
|
|
|
162
369
|
- **Purpose:** _What does this project do and why does it exist?_
|
|
163
370
|
- **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
|
|
164
|
-
- **Runtime / deployment:**
|
|
371
|
+
- **Runtime / deployment:** ${runtime}${hints}
|
|
165
372
|
|
|
166
373
|
## How to work safely
|
|
167
374
|
|
|
@@ -183,10 +390,7 @@ function renderAgentsTemplate(f) {
|
|
|
183
390
|
|
|
184
391
|
| File / directory | Role |
|
|
185
392
|
|---|---|
|
|
186
|
-
|
|
187
|
-
| _tests/_ | _Test root or convention_ |
|
|
188
|
-
| _docs/_ | _Architecture, runbooks, design notes_ |
|
|
189
|
-
| _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
|
|
393
|
+
${keyFiles}
|
|
190
394
|
|
|
191
395
|
## Architecture notes
|
|
192
396
|
|
|
@@ -245,8 +449,79 @@ function countToolResults(messages) {
|
|
|
245
449
|
function estimateTokens(messages) {
|
|
246
450
|
return estimateMessageTokens(messages);
|
|
247
451
|
}
|
|
452
|
+
var EXT_LANG, SCAN_IGNORE_DIRS, ENTRY_BASENAMES;
|
|
248
453
|
var init_helpers = __esm({
|
|
249
454
|
"src/slash-commands/helpers.ts"() {
|
|
455
|
+
EXT_LANG = {
|
|
456
|
+
".ts": "TypeScript",
|
|
457
|
+
".tsx": "TypeScript",
|
|
458
|
+
".mts": "TypeScript",
|
|
459
|
+
".cts": "TypeScript",
|
|
460
|
+
".js": "JavaScript",
|
|
461
|
+
".jsx": "JavaScript",
|
|
462
|
+
".mjs": "JavaScript",
|
|
463
|
+
".cjs": "JavaScript",
|
|
464
|
+
".py": "Python",
|
|
465
|
+
".rb": "Ruby",
|
|
466
|
+
".go": "Go",
|
|
467
|
+
".rs": "Rust",
|
|
468
|
+
".java": "Java",
|
|
469
|
+
".kt": "Kotlin",
|
|
470
|
+
".kts": "Kotlin",
|
|
471
|
+
".c": "C",
|
|
472
|
+
".h": "C",
|
|
473
|
+
".cpp": "C++",
|
|
474
|
+
".cc": "C++",
|
|
475
|
+
".cxx": "C++",
|
|
476
|
+
".hpp": "C++",
|
|
477
|
+
".cs": "C#",
|
|
478
|
+
".php": "PHP",
|
|
479
|
+
".swift": "Swift",
|
|
480
|
+
".scala": "Scala",
|
|
481
|
+
".clj": "Clojure",
|
|
482
|
+
".ex": "Elixir",
|
|
483
|
+
".exs": "Elixir",
|
|
484
|
+
".erl": "Erlang",
|
|
485
|
+
".hs": "Haskell",
|
|
486
|
+
".ml": "OCaml",
|
|
487
|
+
".dart": "Dart",
|
|
488
|
+
".lua": "Lua",
|
|
489
|
+
".jl": "Julia",
|
|
490
|
+
".sh": "Shell",
|
|
491
|
+
".bash": "Shell",
|
|
492
|
+
".ps1": "PowerShell",
|
|
493
|
+
".pl": "Perl",
|
|
494
|
+
".zig": "Zig",
|
|
495
|
+
".nim": "Nim",
|
|
496
|
+
".sql": "SQL",
|
|
497
|
+
".vue": "Vue",
|
|
498
|
+
".svelte": "Svelte"
|
|
499
|
+
};
|
|
500
|
+
SCAN_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
501
|
+
".git",
|
|
502
|
+
"node_modules",
|
|
503
|
+
"dist",
|
|
504
|
+
"build",
|
|
505
|
+
"out",
|
|
506
|
+
"target",
|
|
507
|
+
"vendor",
|
|
508
|
+
".venv",
|
|
509
|
+
"venv",
|
|
510
|
+
"env",
|
|
511
|
+
"__pycache__",
|
|
512
|
+
".next",
|
|
513
|
+
".nuxt",
|
|
514
|
+
".cache",
|
|
515
|
+
"coverage",
|
|
516
|
+
".wrongstack",
|
|
517
|
+
".idea",
|
|
518
|
+
".vscode",
|
|
519
|
+
"obj",
|
|
520
|
+
".gradle",
|
|
521
|
+
".dart_tool",
|
|
522
|
+
"Pods"
|
|
523
|
+
]);
|
|
524
|
+
ENTRY_BASENAMES = /* @__PURE__ */ new Set(["main", "index", "app", "cli", "server", "__main__"]);
|
|
250
525
|
}
|
|
251
526
|
});
|
|
252
527
|
function normalizeKeys(cfg) {
|
|
@@ -267,11 +542,18 @@ function writeKeysBack(cfg, keys) {
|
|
|
267
542
|
}
|
|
268
543
|
cfg.apiKeys = keys;
|
|
269
544
|
const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
|
|
270
|
-
cfg.apiKey
|
|
545
|
+
delete cfg.apiKey;
|
|
271
546
|
if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
|
|
272
547
|
cfg.activeKey = active.label;
|
|
273
548
|
}
|
|
274
549
|
}
|
|
550
|
+
function resolveActiveApiKey(cfg) {
|
|
551
|
+
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
552
|
+
const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
|
|
553
|
+
return (active ?? cfg.apiKeys[0])?.apiKey;
|
|
554
|
+
}
|
|
555
|
+
return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
|
|
556
|
+
}
|
|
275
557
|
function activeLabel(cfg, keys) {
|
|
276
558
|
if (cfg.activeKey && keys.some((k) => k.label === cfg.activeKey)) return cfg.activeKey;
|
|
277
559
|
return keys[0]?.label;
|
|
@@ -372,26 +654,26 @@ function fmtDuration(ms) {
|
|
|
372
654
|
const remMin = m - h * 60;
|
|
373
655
|
return `${h}h${remMin}m`;
|
|
374
656
|
}
|
|
375
|
-
function fmtTaskResultLine(r,
|
|
657
|
+
function fmtTaskResultLine(r, color74) {
|
|
376
658
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
377
659
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
378
660
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
379
661
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
380
|
-
const errKindChip = errKind ?
|
|
381
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
662
|
+
const errKindChip = errKind ? color74.dim(` [${errKind}]`) : "";
|
|
663
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color74.dim(errTail)}` : "";
|
|
382
664
|
switch (r.status) {
|
|
383
665
|
case "success":
|
|
384
|
-
return { mark:
|
|
666
|
+
return { mark: color74.green("\u2713"), stats, tail: "" };
|
|
385
667
|
case "timeout":
|
|
386
668
|
return {
|
|
387
|
-
mark:
|
|
388
|
-
stats: `${
|
|
669
|
+
mark: color74.yellow("\u23F1"),
|
|
670
|
+
stats: `${color74.yellow("timeout")} ${stats}`,
|
|
389
671
|
tail: errSnip
|
|
390
672
|
};
|
|
391
673
|
case "stopped":
|
|
392
|
-
return { mark:
|
|
674
|
+
return { mark: color74.dim("\u2298"), stats: `${color74.dim("stopped")} ${stats}`, tail: errSnip };
|
|
393
675
|
case "failed":
|
|
394
|
-
return { mark:
|
|
676
|
+
return { mark: color74.red("\u2717"), stats: `${color74.red("failed")} ${stats}`, tail: errSnip };
|
|
395
677
|
}
|
|
396
678
|
}
|
|
397
679
|
var init_utils = __esm({
|
|
@@ -676,7 +958,7 @@ async function findSpec(store, idOrTitle) {
|
|
|
676
958
|
async function gatherProjectContext2(projectRoot) {
|
|
677
959
|
const parts = [];
|
|
678
960
|
try {
|
|
679
|
-
const pkgPath =
|
|
961
|
+
const pkgPath = path4.join(projectRoot, "package.json");
|
|
680
962
|
const pkgRaw = await fsp5.readFile(pkgPath, "utf8");
|
|
681
963
|
const pkg = JSON.parse(pkgRaw);
|
|
682
964
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -694,13 +976,13 @@ async function gatherProjectContext2(projectRoot) {
|
|
|
694
976
|
} catch {
|
|
695
977
|
}
|
|
696
978
|
try {
|
|
697
|
-
const tsconfigPath =
|
|
979
|
+
const tsconfigPath = path4.join(projectRoot, "tsconfig.json");
|
|
698
980
|
await fsp5.access(tsconfigPath);
|
|
699
981
|
parts.push("Language: TypeScript");
|
|
700
982
|
} catch {
|
|
701
983
|
}
|
|
702
984
|
try {
|
|
703
|
-
const srcDir =
|
|
985
|
+
const srcDir = path4.join(projectRoot, "src");
|
|
704
986
|
const entries = await fsp5.readdir(srcDir, { withFileTypes: true });
|
|
705
987
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
706
988
|
if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -2125,12 +2407,12 @@ __export(project_utils_exports, {
|
|
|
2125
2407
|
touchProjectInManifest: () => touchProjectInManifest
|
|
2126
2408
|
});
|
|
2127
2409
|
function projectsJsonPath(globalConfigPath) {
|
|
2128
|
-
const base = globalConfigPath ?
|
|
2129
|
-
return
|
|
2410
|
+
const base = globalConfigPath ? path4.dirname(globalConfigPath) : wstackGlobalRoot();
|
|
2411
|
+
return path4.join(base, "projects.json");
|
|
2130
2412
|
}
|
|
2131
2413
|
function projectsDataDir(globalConfigPath) {
|
|
2132
|
-
const base = globalConfigPath ?
|
|
2133
|
-
return
|
|
2414
|
+
const base = globalConfigPath ? path4.dirname(globalConfigPath) : wstackGlobalRoot();
|
|
2415
|
+
return path4.join(base, "projects");
|
|
2134
2416
|
}
|
|
2135
2417
|
async function loadManifest(globalConfigPath) {
|
|
2136
2418
|
const file = projectsJsonPath(globalConfigPath);
|
|
@@ -2144,12 +2426,12 @@ async function loadManifest(globalConfigPath) {
|
|
|
2144
2426
|
}
|
|
2145
2427
|
async function saveManifest(manifest, globalConfigPath) {
|
|
2146
2428
|
const file = projectsJsonPath(globalConfigPath);
|
|
2147
|
-
await fsp5.mkdir(
|
|
2429
|
+
await fsp5.mkdir(path4.dirname(file), { recursive: true });
|
|
2148
2430
|
await fsp5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
2149
2431
|
}
|
|
2150
2432
|
function generateSlug(root) {
|
|
2151
|
-
const base =
|
|
2152
|
-
const hash = createHash("sha256").update(
|
|
2433
|
+
const base = path4.basename(root).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
2434
|
+
const hash = createHash("sha256").update(path4.resolve(root)).digest("hex").slice(0, 6);
|
|
2153
2435
|
return `${base}-${hash}`;
|
|
2154
2436
|
}
|
|
2155
2437
|
function findProject(manifest, query) {
|
|
@@ -2164,29 +2446,29 @@ function findProject(manifest, query) {
|
|
|
2164
2446
|
return found;
|
|
2165
2447
|
}
|
|
2166
2448
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
2167
|
-
const dir =
|
|
2449
|
+
const dir = path4.join(projectsDataDir(globalConfigPath), slug);
|
|
2168
2450
|
await fsp5.mkdir(dir, { recursive: true });
|
|
2169
2451
|
return dir;
|
|
2170
2452
|
}
|
|
2171
2453
|
async function touchProjectInManifest(opts) {
|
|
2172
|
-
const root =
|
|
2454
|
+
const root = path4.resolve(opts.projectRoot);
|
|
2173
2455
|
const file = projectsJsonPath(opts.globalConfigPath);
|
|
2174
2456
|
let entry;
|
|
2175
2457
|
await withFileLock(file, async () => {
|
|
2176
2458
|
const manifest = await loadManifest(opts.globalConfigPath);
|
|
2177
2459
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2178
|
-
entry = manifest.projects.find((p) =>
|
|
2460
|
+
entry = manifest.projects.find((p) => path4.resolve(p.root) === root);
|
|
2179
2461
|
if (entry) {
|
|
2180
2462
|
entry.lastSeen = now;
|
|
2181
|
-
if (opts.workingDir) entry.lastWorkingDir =
|
|
2463
|
+
if (opts.workingDir) entry.lastWorkingDir = path4.resolve(opts.workingDir);
|
|
2182
2464
|
} else {
|
|
2183
2465
|
entry = {
|
|
2184
|
-
name: opts.name ??
|
|
2466
|
+
name: opts.name ?? path4.basename(root),
|
|
2185
2467
|
root,
|
|
2186
2468
|
slug: generateSlug(root),
|
|
2187
2469
|
createdAt: now,
|
|
2188
2470
|
lastSeen: now,
|
|
2189
|
-
lastWorkingDir: opts.workingDir ?
|
|
2471
|
+
lastWorkingDir: opts.workingDir ? path4.resolve(opts.workingDir) : void 0
|
|
2190
2472
|
};
|
|
2191
2473
|
manifest.projects.push(entry);
|
|
2192
2474
|
}
|
|
@@ -2632,7 +2914,7 @@ __export(update_check_exports, {
|
|
|
2632
2914
|
getUpdateNotification: () => getUpdateNotification
|
|
2633
2915
|
});
|
|
2634
2916
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
2635
|
-
return
|
|
2917
|
+
return path4.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
2636
2918
|
}
|
|
2637
2919
|
function currentVersion() {
|
|
2638
2920
|
const req2 = createRequire(import.meta.url);
|
|
@@ -2669,7 +2951,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
2669
2951
|
}
|
|
2670
2952
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
2671
2953
|
try {
|
|
2672
|
-
const dir =
|
|
2954
|
+
const dir = path4.dirname(cachePath(homeFn));
|
|
2673
2955
|
await fsp5.mkdir(dir, { recursive: true });
|
|
2674
2956
|
await fsp5.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
2675
2957
|
} catch {
|
|
@@ -2755,7 +3037,7 @@ function registerWebuiInstance(p, deps = {}) {
|
|
|
2755
3037
|
wsPort: p.wsPort,
|
|
2756
3038
|
host: p.host,
|
|
2757
3039
|
projectRoot: p.projectRoot,
|
|
2758
|
-
projectName:
|
|
3040
|
+
projectName: path4.basename(p.projectRoot) || p.projectRoot,
|
|
2759
3041
|
startedAt: p.startedAt,
|
|
2760
3042
|
url: `http://${p.host}:${p.httpPort}`
|
|
2761
3043
|
},
|
|
@@ -2766,7 +3048,7 @@ function registerWebuiInstance(p, deps = {}) {
|
|
|
2766
3048
|
function announceWebuiReady(p) {
|
|
2767
3049
|
const log = p.log ?? ((m) => console.log(m));
|
|
2768
3050
|
const launch = p.openBrowserFn ?? openBrowser;
|
|
2769
|
-
const openUrl = `http://${p.host}:${p.httpPort}`;
|
|
3051
|
+
const openUrl = p.wsToken ? `http://${p.host}:${p.httpPort}?token=${encodeURIComponent(p.wsToken)}` : `http://${p.host}:${p.httpPort}`;
|
|
2770
3052
|
p.server.on("listening", () => {
|
|
2771
3053
|
log(
|
|
2772
3054
|
`
|
|
@@ -2819,7 +3101,7 @@ var init_lifecycle = __esm({
|
|
|
2819
3101
|
}
|
|
2820
3102
|
});
|
|
2821
3103
|
function getVault(globalConfigPath) {
|
|
2822
|
-
const keyFile =
|
|
3104
|
+
const keyFile = path4.join(path4.dirname(globalConfigPath ?? ""), ".key");
|
|
2823
3105
|
return new DefaultSecretVault({ keyFile });
|
|
2824
3106
|
}
|
|
2825
3107
|
async function loadSavedProviders(globalConfigPath) {
|
|
@@ -2853,7 +3135,7 @@ function resolveDistDir() {
|
|
|
2853
3135
|
try {
|
|
2854
3136
|
const requireFromHere = createRequire(import.meta.url);
|
|
2855
3137
|
const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
|
|
2856
|
-
return
|
|
3138
|
+
return path4.resolve(path4.dirname(serverEntry), "..");
|
|
2857
3139
|
} catch {
|
|
2858
3140
|
return null;
|
|
2859
3141
|
}
|
|
@@ -2867,7 +3149,9 @@ function startStaticServe(opts, deps = {}) {
|
|
|
2867
3149
|
host: opts.host,
|
|
2868
3150
|
distDir,
|
|
2869
3151
|
wsPort: opts.wsPort,
|
|
2870
|
-
globalRoot: opts.globalRoot
|
|
3152
|
+
globalRoot: opts.globalRoot,
|
|
3153
|
+
onFleetPing: opts.onFleetPing,
|
|
3154
|
+
apiToken: opts.apiToken
|
|
2871
3155
|
});
|
|
2872
3156
|
server.listen(opts.httpPort, opts.host);
|
|
2873
3157
|
return { server, port: opts.httpPort };
|
|
@@ -2908,7 +3192,7 @@ async function handleModesList(ctx, ws) {
|
|
|
2908
3192
|
payload: {
|
|
2909
3193
|
modes: [],
|
|
2910
3194
|
activeId: "default",
|
|
2911
|
-
error:
|
|
3195
|
+
error: toErrorMessage(err)
|
|
2912
3196
|
}
|
|
2913
3197
|
});
|
|
2914
3198
|
}
|
|
@@ -2931,7 +3215,7 @@ async function handleModeSwitch(ctx, ws, id) {
|
|
|
2931
3215
|
const payload = await ctx.buildSessionStart({ mode: id });
|
|
2932
3216
|
ctx.broadcast({ type: "session.start", payload });
|
|
2933
3217
|
} catch (err) {
|
|
2934
|
-
sendResult(ctx, ws, false,
|
|
3218
|
+
sendResult(ctx, ws, false, toErrorMessage(err));
|
|
2935
3219
|
}
|
|
2936
3220
|
}
|
|
2937
3221
|
async function handleModelSwitch(ctx, ws, payload) {
|
|
@@ -2950,7 +3234,7 @@ async function handleModelSwitch(ctx, ws, payload) {
|
|
|
2950
3234
|
ctx,
|
|
2951
3235
|
ws,
|
|
2952
3236
|
false,
|
|
2953
|
-
`Switch failed: ${
|
|
3237
|
+
`Switch failed: ${toErrorMessage(err)}`
|
|
2954
3238
|
);
|
|
2955
3239
|
}
|
|
2956
3240
|
}
|
|
@@ -2999,7 +3283,7 @@ async function handleModelRefine(ctx, ws, text) {
|
|
|
2999
3283
|
payload: {
|
|
3000
3284
|
refined: text,
|
|
3001
3285
|
english: text,
|
|
3002
|
-
error:
|
|
3286
|
+
error: toErrorMessage(err)
|
|
3003
3287
|
}
|
|
3004
3288
|
});
|
|
3005
3289
|
}
|
|
@@ -3009,8 +3293,6 @@ var init_agent_config = __esm({
|
|
|
3009
3293
|
init_provider_config();
|
|
3010
3294
|
}
|
|
3011
3295
|
});
|
|
3012
|
-
|
|
3013
|
-
// src/webui-server/ws-handlers/brain.ts
|
|
3014
3296
|
function sendResult2(ctx, ws, success, message) {
|
|
3015
3297
|
ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
3016
3298
|
}
|
|
@@ -3064,7 +3346,7 @@ async function handleBrainAsk(ctx, ws, question) {
|
|
|
3064
3346
|
ctx,
|
|
3065
3347
|
ws,
|
|
3066
3348
|
false,
|
|
3067
|
-
`Brain consultation failed: ${
|
|
3349
|
+
`Brain consultation failed: ${toErrorMessage(err)}`
|
|
3068
3350
|
);
|
|
3069
3351
|
}
|
|
3070
3352
|
}
|
|
@@ -3072,8 +3354,6 @@ var init_brain = __esm({
|
|
|
3072
3354
|
"src/webui-server/ws-handlers/brain.ts"() {
|
|
3073
3355
|
}
|
|
3074
3356
|
});
|
|
3075
|
-
|
|
3076
|
-
// src/webui-server/ws-handlers/connection.ts
|
|
3077
3357
|
async function handleUserMessage(ctx, ws, content) {
|
|
3078
3358
|
if (ctx.abortControllers.has(ws)) {
|
|
3079
3359
|
ctx.send(ws, {
|
|
@@ -3106,7 +3386,7 @@ async function handleUserMessage(ctx, ws, content) {
|
|
|
3106
3386
|
type: "error",
|
|
3107
3387
|
payload: {
|
|
3108
3388
|
phase: "agent.run",
|
|
3109
|
-
message:
|
|
3389
|
+
message: toErrorMessage(err)
|
|
3110
3390
|
}
|
|
3111
3391
|
});
|
|
3112
3392
|
} finally {
|
|
@@ -3355,8 +3635,6 @@ var init_cost_helpers = __esm({
|
|
|
3355
3635
|
"src/webui-server/cost-helpers.ts"() {
|
|
3356
3636
|
}
|
|
3357
3637
|
});
|
|
3358
|
-
|
|
3359
|
-
// src/webui-server/ws-handlers/introspection.ts
|
|
3360
3638
|
async function handleSkillsList(ctx, ws) {
|
|
3361
3639
|
if (!ctx.skillLoader) {
|
|
3362
3640
|
ctx.send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
|
|
@@ -3387,7 +3665,7 @@ async function handleSkillsList(ctx, ws) {
|
|
|
3387
3665
|
payload: {
|
|
3388
3666
|
skills: [],
|
|
3389
3667
|
enabled: true,
|
|
3390
|
-
error:
|
|
3668
|
+
error: toErrorMessage(err)
|
|
3391
3669
|
}
|
|
3392
3670
|
});
|
|
3393
3671
|
}
|
|
@@ -3544,8 +3822,8 @@ function sendResult6(ctx, ws, success, message) {
|
|
|
3544
3822
|
ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
3545
3823
|
}
|
|
3546
3824
|
async function handleProjectsList(ctx, ws) {
|
|
3547
|
-
const projectsBase = ctx.opts.globalConfigPath ?
|
|
3548
|
-
const manifestPath =
|
|
3825
|
+
const projectsBase = ctx.opts.globalConfigPath ? path4.resolve(path4.dirname(ctx.opts.globalConfigPath)) : wstackGlobalRoot();
|
|
3826
|
+
const manifestPath = path4.join(projectsBase, "projects.json");
|
|
3549
3827
|
try {
|
|
3550
3828
|
const raw = await fsp5.readFile(manifestPath, "utf8");
|
|
3551
3829
|
const manifest = JSON.parse(raw);
|
|
@@ -3558,22 +3836,22 @@ async function handleProjectsSelect(ctx, ws, payload) {
|
|
|
3558
3836
|
const { opts } = ctx;
|
|
3559
3837
|
const { root, name: projectName } = payload;
|
|
3560
3838
|
try {
|
|
3561
|
-
const resolved =
|
|
3839
|
+
const resolved = path4.resolve(root);
|
|
3562
3840
|
const stat7 = await fsp5.stat(resolved).catch(() => null);
|
|
3563
3841
|
if (!stat7?.isDirectory()) {
|
|
3564
3842
|
ctx.send(ws, {
|
|
3565
3843
|
type: "projects.selected",
|
|
3566
3844
|
payload: {
|
|
3567
3845
|
root,
|
|
3568
|
-
name: projectName ??
|
|
3846
|
+
name: projectName ?? path4.basename(root),
|
|
3569
3847
|
message: `Cannot switch: not a directory: ${resolved}`
|
|
3570
3848
|
}
|
|
3571
3849
|
});
|
|
3572
3850
|
return;
|
|
3573
3851
|
}
|
|
3574
3852
|
const manifest = await loadManifest(opts.globalConfigPath);
|
|
3575
|
-
const entry = manifest.projects.find((p) =>
|
|
3576
|
-
const displayName = projectName?.trim() || entry?.name ||
|
|
3853
|
+
const entry = manifest.projects.find((p) => path4.resolve(p.root) === resolved);
|
|
3854
|
+
const displayName = projectName?.trim() || entry?.name || path4.basename(resolved);
|
|
3577
3855
|
if (entry) {
|
|
3578
3856
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
3579
3857
|
} else {
|
|
@@ -3619,8 +3897,8 @@ async function handleProjectsSelect(ctx, ws, payload) {
|
|
|
3619
3897
|
});
|
|
3620
3898
|
} catch {
|
|
3621
3899
|
}
|
|
3622
|
-
const globalRoot = opts.globalConfigPath ?
|
|
3623
|
-
const newSessionsDir =
|
|
3900
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : wstackGlobalRoot();
|
|
3901
|
+
const newSessionsDir = path4.join(resolveProjectDir(resolved, globalRoot), "sessions");
|
|
3624
3902
|
await fsp5.mkdir(newSessionsDir, { recursive: true });
|
|
3625
3903
|
const newStore = new DefaultSessionStore({ dir: newSessionsDir });
|
|
3626
3904
|
opts.sessionStore = newStore;
|
|
@@ -3644,17 +3922,17 @@ async function handleProjectsSelect(ctx, ws, payload) {
|
|
|
3644
3922
|
const switchedP = await ctx.buildSessionStart({ reset: true, clearedSessionId: oldSessionId });
|
|
3645
3923
|
ctx.broadcast({ type: "session.start", payload: switchedP });
|
|
3646
3924
|
} catch (err) {
|
|
3647
|
-
sendResult6(ctx, ws, false,
|
|
3925
|
+
sendResult6(ctx, ws, false, toErrorMessage(err));
|
|
3648
3926
|
}
|
|
3649
3927
|
}
|
|
3650
3928
|
async function handleProjectsAdd(ctx, ws, payload) {
|
|
3651
3929
|
const { root: addRoot, name: addName } = payload;
|
|
3652
3930
|
try {
|
|
3653
|
-
const resolved =
|
|
3931
|
+
const resolved = path4.resolve(addRoot);
|
|
3654
3932
|
const stat7 = await fsp5.stat(resolved).catch(() => null);
|
|
3655
3933
|
if (!stat7?.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
3656
3934
|
const manifest = await loadManifest(ctx.opts.globalConfigPath);
|
|
3657
|
-
const existing = manifest.projects.find((p) =>
|
|
3935
|
+
const existing = manifest.projects.find((p) => path4.resolve(p.root) === resolved);
|
|
3658
3936
|
if (existing) {
|
|
3659
3937
|
ctx.send(ws, {
|
|
3660
3938
|
type: "projects.added",
|
|
@@ -3667,7 +3945,7 @@ async function handleProjectsAdd(ctx, ws, payload) {
|
|
|
3667
3945
|
});
|
|
3668
3946
|
return;
|
|
3669
3947
|
}
|
|
3670
|
-
const name = addName?.trim() ||
|
|
3948
|
+
const name = addName?.trim() || path4.basename(resolved);
|
|
3671
3949
|
const slug = projectSlug(resolved);
|
|
3672
3950
|
await ensureProjectDataDir(slug, ctx.opts.globalConfigPath);
|
|
3673
3951
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3681,10 +3959,10 @@ async function handleProjectsAdd(ctx, ws, payload) {
|
|
|
3681
3959
|
ctx.send(ws, {
|
|
3682
3960
|
type: "projects.added",
|
|
3683
3961
|
payload: {
|
|
3684
|
-
name:
|
|
3962
|
+
name: path4.basename(addRoot),
|
|
3685
3963
|
root: addRoot,
|
|
3686
3964
|
slug: "",
|
|
3687
|
-
message:
|
|
3965
|
+
message: toErrorMessage(err)
|
|
3688
3966
|
}
|
|
3689
3967
|
});
|
|
3690
3968
|
}
|
|
@@ -3692,8 +3970,8 @@ async function handleProjectsAdd(ctx, ws, payload) {
|
|
|
3692
3970
|
async function handleWorkingDirSet(ctx, ws, newPath) {
|
|
3693
3971
|
try {
|
|
3694
3972
|
const wdRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
3695
|
-
const resolved =
|
|
3696
|
-
if (!resolved.startsWith(wdRoot +
|
|
3973
|
+
const resolved = path4.resolve(wdRoot, newPath);
|
|
3974
|
+
if (!resolved.startsWith(wdRoot + path4.sep) && resolved !== wdRoot) {
|
|
3697
3975
|
sendResult6(ctx, ws, false, `Path must stay inside the project root: ${wdRoot}`);
|
|
3698
3976
|
return;
|
|
3699
3977
|
}
|
|
@@ -3706,7 +3984,7 @@ async function handleWorkingDirSet(ctx, ws, newPath) {
|
|
|
3706
3984
|
ctx.broadcast({ type: "working_dir.changed", payload: { cwd: resolved, projectRoot: wdRoot } });
|
|
3707
3985
|
sendResult6(ctx, ws, true, `Working directory set to ${resolved}`);
|
|
3708
3986
|
} catch (err) {
|
|
3709
|
-
sendResult6(ctx, ws, false,
|
|
3987
|
+
sendResult6(ctx, ws, false, toErrorMessage(err));
|
|
3710
3988
|
}
|
|
3711
3989
|
}
|
|
3712
3990
|
var init_projects = __esm({
|
|
@@ -3714,11 +3992,27 @@ var init_projects = __esm({
|
|
|
3714
3992
|
init_project_utils();
|
|
3715
3993
|
}
|
|
3716
3994
|
});
|
|
3717
|
-
|
|
3718
|
-
// src/webui-server/ws-handlers/providers.ts
|
|
3719
3995
|
function sendResult7(ctx, ws, success, message) {
|
|
3720
3996
|
ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
3721
3997
|
}
|
|
3998
|
+
function broadcastSaved(ctx, providers) {
|
|
3999
|
+
ctx.broadcast({
|
|
4000
|
+
type: "providers.saved",
|
|
4001
|
+
payload: {
|
|
4002
|
+
providers: Object.entries(providers).map(([id, cfg]) => ({
|
|
4003
|
+
id,
|
|
4004
|
+
family: cfg.family,
|
|
4005
|
+
baseUrl: cfg.baseUrl,
|
|
4006
|
+
apiKeys: normalizeKeys(cfg).map((k) => ({
|
|
4007
|
+
label: k.label,
|
|
4008
|
+
maskedKey: maskedKey(k.apiKey),
|
|
4009
|
+
isActive: k.label === cfg.activeKey,
|
|
4010
|
+
createdAt: k.createdAt
|
|
4011
|
+
}))
|
|
4012
|
+
}))
|
|
4013
|
+
}
|
|
4014
|
+
});
|
|
4015
|
+
}
|
|
3722
4016
|
async function handleProvidersList(ctx, ws) {
|
|
3723
4017
|
if (!ctx.modelsRegistry) {
|
|
3724
4018
|
sendResult7(ctx, ws, false, "Models registry not available");
|
|
@@ -3743,7 +4037,7 @@ async function handleProvidersList(ctx, ws) {
|
|
|
3743
4037
|
}
|
|
3744
4038
|
});
|
|
3745
4039
|
} catch (err) {
|
|
3746
|
-
sendResult7(ctx, ws, false,
|
|
4040
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
3747
4041
|
}
|
|
3748
4042
|
}
|
|
3749
4043
|
async function handleProviderModels(ctx, ws, providerId) {
|
|
@@ -3778,7 +4072,7 @@ async function handleProviderModels(ctx, ws, providerId) {
|
|
|
3778
4072
|
}
|
|
3779
4073
|
});
|
|
3780
4074
|
} catch (err) {
|
|
3781
|
-
sendResult7(ctx, ws, false,
|
|
4075
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
3782
4076
|
}
|
|
3783
4077
|
}
|
|
3784
4078
|
async function handleProvidersSaved(ctx, ws) {
|
|
@@ -3801,7 +4095,7 @@ async function handleProvidersSaved(ctx, ws) {
|
|
|
3801
4095
|
}
|
|
3802
4096
|
});
|
|
3803
4097
|
} catch (err) {
|
|
3804
|
-
sendResult7(ctx, ws, false,
|
|
4098
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
3805
4099
|
}
|
|
3806
4100
|
}
|
|
3807
4101
|
async function handleKeyUpsert(ctx, ws, providerId, label, apiKey) {
|
|
@@ -3821,7 +4115,7 @@ async function handleKeyUpsert(ctx, ws, providerId, label, apiKey) {
|
|
|
3821
4115
|
await ctx.providerStore.save(providers);
|
|
3822
4116
|
sendResult7(ctx, ws, true, `Key "${label}" saved for ${providerId}`);
|
|
3823
4117
|
} catch (err) {
|
|
3824
|
-
sendResult7(ctx, ws, false,
|
|
4118
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
3825
4119
|
}
|
|
3826
4120
|
}
|
|
3827
4121
|
async function handleKeyDelete(ctx, ws, providerId, label) {
|
|
@@ -3845,7 +4139,7 @@ async function handleKeyDelete(ctx, ws, providerId, label) {
|
|
|
3845
4139
|
await ctx.providerStore.save(providers);
|
|
3846
4140
|
sendResult7(ctx, ws, true, `Key "${label}" deleted from ${providerId}`);
|
|
3847
4141
|
} catch (err) {
|
|
3848
|
-
sendResult7(ctx, ws, false,
|
|
4142
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
3849
4143
|
}
|
|
3850
4144
|
}
|
|
3851
4145
|
async function handleKeySetActive(ctx, ws, providerId, label) {
|
|
@@ -3862,7 +4156,7 @@ async function handleKeySetActive(ctx, ws, providerId, label) {
|
|
|
3862
4156
|
await ctx.providerStore.save(providers);
|
|
3863
4157
|
sendResult7(ctx, ws, true, `Active key for ${providerId} set to "${label}"`);
|
|
3864
4158
|
} catch (err) {
|
|
3865
|
-
sendResult7(ctx, ws, false,
|
|
4159
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
3866
4160
|
}
|
|
3867
4161
|
}
|
|
3868
4162
|
async function handleProviderAdd(ctx, ws, payload) {
|
|
@@ -3907,7 +4201,7 @@ async function handleProviderAdd(ctx, ws, payload) {
|
|
|
3907
4201
|
}
|
|
3908
4202
|
});
|
|
3909
4203
|
} catch (err) {
|
|
3910
|
-
sendResult7(ctx, ws, false,
|
|
4204
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
3911
4205
|
}
|
|
3912
4206
|
}
|
|
3913
4207
|
async function handleProviderRemove(ctx, ws, providerId) {
|
|
@@ -3921,12 +4215,92 @@ async function handleProviderRemove(ctx, ws, providerId) {
|
|
|
3921
4215
|
await ctx.providerStore.save(providers);
|
|
3922
4216
|
sendResult7(ctx, ws, true, `Provider "${providerId}" removed`);
|
|
3923
4217
|
} catch (err) {
|
|
3924
|
-
sendResult7(ctx, ws, false,
|
|
4218
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
async function handleProviderClearModels(ctx, ws, providerId) {
|
|
4222
|
+
try {
|
|
4223
|
+
const providers = await ctx.providerStore.load();
|
|
4224
|
+
const cfg = providers[providerId];
|
|
4225
|
+
if (!cfg) {
|
|
4226
|
+
sendResult7(ctx, ws, false, `Unknown provider "${providerId}"`);
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
delete cfg.models;
|
|
4230
|
+
await ctx.providerStore.save(providers);
|
|
4231
|
+
sendResult7(ctx, ws, true, `Cleared model allowlist for ${providerId}`);
|
|
4232
|
+
broadcastSaved(ctx, providers);
|
|
4233
|
+
} catch (err) {
|
|
4234
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
async function handleProviderUndoClear(ctx, ws, providerId, previousModels) {
|
|
4238
|
+
try {
|
|
4239
|
+
const providers = await ctx.providerStore.load();
|
|
4240
|
+
const cfg = providers[providerId];
|
|
4241
|
+
if (!cfg) {
|
|
4242
|
+
sendResult7(ctx, ws, false, `Unknown provider "${providerId}"`);
|
|
4243
|
+
return;
|
|
4244
|
+
}
|
|
4245
|
+
cfg.models = [...previousModels];
|
|
4246
|
+
await ctx.providerStore.save(providers);
|
|
4247
|
+
sendResult7(ctx, ws, true, `Restored ${previousModels.length} model(s) for ${providerId}`);
|
|
4248
|
+
broadcastSaved(ctx, providers);
|
|
4249
|
+
} catch (err) {
|
|
4250
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
async function handleProviderUpdate(ctx, ws, payload) {
|
|
4254
|
+
try {
|
|
4255
|
+
const providers = await ctx.providerStore.load();
|
|
4256
|
+
const cfg = providers[payload.id];
|
|
4257
|
+
if (!cfg) {
|
|
4258
|
+
sendResult7(ctx, ws, false, `Unknown provider "${payload.id}"`);
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
if (payload.family !== void 0) cfg.family = payload.family;
|
|
4262
|
+
if (payload.baseUrl !== void 0) cfg.baseUrl = payload.baseUrl;
|
|
4263
|
+
if (payload.envVars !== void 0) cfg.envVars = payload.envVars;
|
|
4264
|
+
if (payload.models !== void 0) cfg.models = payload.models;
|
|
4265
|
+
await ctx.providerStore.save(providers);
|
|
4266
|
+
sendResult7(ctx, ws, true, `Updated ${payload.id}`);
|
|
4267
|
+
broadcastSaved(ctx, providers);
|
|
4268
|
+
} catch (err) {
|
|
4269
|
+
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
async function handleProviderProbe(ctx, ws, providerId, timeoutMs) {
|
|
4273
|
+
const reply = (payload) => ctx.send(ws, { type: "provider.probe", payload: { providerId, ...payload } });
|
|
4274
|
+
try {
|
|
4275
|
+
const providers = await ctx.providerStore.load();
|
|
4276
|
+
const cfg = providers[providerId];
|
|
4277
|
+
if (!cfg) {
|
|
4278
|
+
reply({ ok: false, status: "no_provider" });
|
|
4279
|
+
return;
|
|
4280
|
+
}
|
|
4281
|
+
if (!cfg.baseUrl) {
|
|
4282
|
+
reply({ ok: false, status: "no_base_url" });
|
|
4283
|
+
return;
|
|
4284
|
+
}
|
|
4285
|
+
const keys = normalizeKeys(cfg);
|
|
4286
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
|
|
4287
|
+
const result = await probeLocalLlm({
|
|
4288
|
+
baseUrl: cfg.baseUrl,
|
|
4289
|
+
apiKey: active?.apiKey,
|
|
4290
|
+
noAuth: false,
|
|
4291
|
+
scrubber: probeScrubber,
|
|
4292
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
4293
|
+
});
|
|
4294
|
+
reply(result);
|
|
4295
|
+
} catch (err) {
|
|
4296
|
+
reply({ ok: false, status: "unreachable", detail: toErrorMessage(err) });
|
|
3925
4297
|
}
|
|
3926
4298
|
}
|
|
4299
|
+
var probeScrubber;
|
|
3927
4300
|
var init_providers = __esm({
|
|
3928
4301
|
"src/webui-server/ws-handlers/providers.ts"() {
|
|
3929
4302
|
init_provider_config();
|
|
4303
|
+
probeScrubber = new DefaultSecretScrubber();
|
|
3930
4304
|
}
|
|
3931
4305
|
});
|
|
3932
4306
|
function sendResult8(ctx, ws, success, message) {
|
|
@@ -3934,13 +4308,13 @@ function sendResult8(ctx, ws, success, message) {
|
|
|
3934
4308
|
}
|
|
3935
4309
|
function storeFor(opts) {
|
|
3936
4310
|
return opts.sessionStore ?? new DefaultSessionStore({
|
|
3937
|
-
dir:
|
|
4311
|
+
dir: path4.join(opts.projectRoot ?? opts.agent.ctx.projectRoot, ".wrongstack", "sessions")
|
|
3938
4312
|
});
|
|
3939
4313
|
}
|
|
3940
4314
|
async function handleGoalGet(ctx, _ws) {
|
|
3941
4315
|
const projectRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
3942
4316
|
try {
|
|
3943
|
-
const goalPath =
|
|
4317
|
+
const goalPath = path4.join(projectRoot, ".wrongstack", "goal.json");
|
|
3944
4318
|
const raw = await fsp5.readFile(goalPath, "utf8");
|
|
3945
4319
|
ctx.broadcast({ type: "goal.updated", payload: JSON.parse(raw) });
|
|
3946
4320
|
} catch {
|
|
@@ -3968,7 +4342,7 @@ async function handleSessionsList(ctx, ws, limit) {
|
|
|
3968
4342
|
} catch (err) {
|
|
3969
4343
|
ctx.send(ws, {
|
|
3970
4344
|
type: "sessions.list",
|
|
3971
|
-
payload: { sessions: [], error:
|
|
4345
|
+
payload: { sessions: [], error: toErrorMessage(err) }
|
|
3972
4346
|
});
|
|
3973
4347
|
}
|
|
3974
4348
|
}
|
|
@@ -4000,7 +4374,7 @@ async function handleSessionNew(ctx, _ws) {
|
|
|
4000
4374
|
JSON.stringify({
|
|
4001
4375
|
level: "warn",
|
|
4002
4376
|
event: "webui.session_new_store_failed",
|
|
4003
|
-
message:
|
|
4377
|
+
message: toErrorMessage(err),
|
|
4004
4378
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4005
4379
|
})
|
|
4006
4380
|
);
|
|
@@ -4016,7 +4390,7 @@ async function handleSessionNew(ctx, _ws) {
|
|
|
4016
4390
|
function rewinderFor(opts) {
|
|
4017
4391
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
4018
4392
|
return new DefaultSessionRewinder(
|
|
4019
|
-
opts.sessionsDir ??
|
|
4393
|
+
opts.sessionsDir ?? path4.join(projectRoot, ".wrongstack", "sessions"),
|
|
4020
4394
|
projectRoot
|
|
4021
4395
|
);
|
|
4022
4396
|
}
|
|
@@ -4040,7 +4414,7 @@ async function handleSessionRewind(ctx, ws, checkpointIndex) {
|
|
|
4040
4414
|
const payload = await ctx.buildSessionStart({ reset: true });
|
|
4041
4415
|
ctx.broadcast({ type: "session.start", payload });
|
|
4042
4416
|
} catch (err) {
|
|
4043
|
-
sendResult8(ctx, ws, false,
|
|
4417
|
+
sendResult8(ctx, ws, false, toErrorMessage(err));
|
|
4044
4418
|
}
|
|
4045
4419
|
}
|
|
4046
4420
|
async function handleSessionDelete(ctx, ws, id) {
|
|
@@ -4052,7 +4426,7 @@ async function handleSessionDelete(ctx, ws, id) {
|
|
|
4052
4426
|
await storeFor(ctx.opts).delete(id);
|
|
4053
4427
|
sendResult8(ctx, ws, true, `Session ${id} deleted`);
|
|
4054
4428
|
} catch (err) {
|
|
4055
|
-
sendResult8(ctx, ws, false,
|
|
4429
|
+
sendResult8(ctx, ws, false, toErrorMessage(err));
|
|
4056
4430
|
}
|
|
4057
4431
|
}
|
|
4058
4432
|
function handleSessionSave(ctx, ws) {
|
|
@@ -4095,7 +4469,7 @@ async function handleSessionResume(ctx, ws, id) {
|
|
|
4095
4469
|
ctx.broadcast({ type: "session.start", payload });
|
|
4096
4470
|
sendResult8(ctx, ws, true, `Resumed session ${id}`);
|
|
4097
4471
|
} catch (err) {
|
|
4098
|
-
sendResult8(ctx, ws, false,
|
|
4472
|
+
sendResult8(ctx, ws, false, toErrorMessage(err));
|
|
4099
4473
|
}
|
|
4100
4474
|
}
|
|
4101
4475
|
var init_sessions = __esm({
|
|
@@ -4350,14 +4724,14 @@ async function runWebUI(opts) {
|
|
|
4350
4724
|
let customModeStoreP = null;
|
|
4351
4725
|
const getCustomModeStore = () => {
|
|
4352
4726
|
customModeStoreP ??= (async () => {
|
|
4353
|
-
const dir = opts.globalConfigPath ?
|
|
4727
|
+
const dir = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : wstackGlobalRoot();
|
|
4354
4728
|
const store = createCustomModeStore(dir);
|
|
4355
4729
|
await store.load();
|
|
4356
4730
|
return store;
|
|
4357
4731
|
})();
|
|
4358
4732
|
return customModeStoreP;
|
|
4359
4733
|
};
|
|
4360
|
-
const autoPhaseStoreDir = opts.projectRoot ?
|
|
4734
|
+
const autoPhaseStoreDir = opts.projectRoot ? path4.join(opts.projectRoot, ".wrongstack", "autophase") : path4.join(os.tmpdir(), ".wrongstack", "autophase");
|
|
4361
4735
|
const autoPhaseHandler = new AutoPhaseWebSocketHandler(
|
|
4362
4736
|
opts.agent,
|
|
4363
4737
|
opts.agent.ctx,
|
|
@@ -4388,7 +4762,12 @@ async function runWebUI(opts) {
|
|
|
4388
4762
|
"contextAutoCompact",
|
|
4389
4763
|
"contextStrategy",
|
|
4390
4764
|
"logLevel",
|
|
4391
|
-
"auditLevel"
|
|
4765
|
+
"auditLevel",
|
|
4766
|
+
// Telegram plugin notification settings (parity with the standalone server).
|
|
4767
|
+
"tgConfigured",
|
|
4768
|
+
"tgSessionEnd",
|
|
4769
|
+
"tgDelegate",
|
|
4770
|
+
"tgLongToolMs"
|
|
4392
4771
|
];
|
|
4393
4772
|
const prefSnapshot = () => {
|
|
4394
4773
|
const snapshot = {};
|
|
@@ -4427,6 +4806,12 @@ async function runWebUI(opts) {
|
|
|
4427
4806
|
meta["logLevel"] = cfg.log?.["level"] ?? "info";
|
|
4428
4807
|
meta["auditLevel"] = cfg.session?.["auditLevel"] ?? "standard";
|
|
4429
4808
|
meta["maxIterations"] = cfg.tools?.["maxIterations"] ?? 500;
|
|
4809
|
+
const tgExt = cfg.extensions?.["telegram"];
|
|
4810
|
+
meta["tgConfigured"] = typeof tgExt?.["botToken"] === "string" && tgExt["botToken"].length > 0;
|
|
4811
|
+
meta["tgSessionEnd"] = tgExt?.["notifyOnSessionEnd"] === true;
|
|
4812
|
+
meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
|
|
4813
|
+
const tgMs = tgExt?.["longToolThresholdMs"];
|
|
4814
|
+
meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
|
|
4430
4815
|
} catch {
|
|
4431
4816
|
}
|
|
4432
4817
|
}
|
|
@@ -4448,7 +4833,7 @@ async function runWebUI(opts) {
|
|
|
4448
4833
|
return;
|
|
4449
4834
|
}
|
|
4450
4835
|
const vault = new DefaultSecretVault({
|
|
4451
|
-
keyFile:
|
|
4836
|
+
keyFile: path4.join(path4.dirname(configPath2), ".key")
|
|
4452
4837
|
});
|
|
4453
4838
|
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
4454
4839
|
const autonomyCfg = decrypted.autonomy ?? {};
|
|
@@ -4521,6 +4906,16 @@ async function runWebUI(opts) {
|
|
|
4521
4906
|
toolsCfg.maxIterations = payload["maxIterations"];
|
|
4522
4907
|
decrypted.tools = toolsCfg;
|
|
4523
4908
|
}
|
|
4909
|
+
const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
|
|
4910
|
+
if (tgTouched) {
|
|
4911
|
+
const ext = decrypted.extensions ?? {};
|
|
4912
|
+
const tg = ext["telegram"] ?? {};
|
|
4913
|
+
if (typeof payload["tgSessionEnd"] === "boolean") tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
|
|
4914
|
+
if (typeof payload["tgDelegate"] === "boolean") tg["notifyOnDelegate"] = payload["tgDelegate"];
|
|
4915
|
+
if (typeof payload["tgLongToolMs"] === "number") tg["longToolThresholdMs"] = payload["tgLongToolMs"];
|
|
4916
|
+
ext["telegram"] = tg;
|
|
4917
|
+
decrypted.extensions = ext;
|
|
4918
|
+
}
|
|
4524
4919
|
const encrypted = encryptConfigSecrets$1(decrypted, vault);
|
|
4525
4920
|
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4526
4921
|
};
|
|
@@ -4542,7 +4937,6 @@ async function runWebUI(opts) {
|
|
|
4542
4937
|
);
|
|
4543
4938
|
}
|
|
4544
4939
|
};
|
|
4545
|
-
const authToken = crypto3.randomBytes(16).toString("hex");
|
|
4546
4940
|
const sessionStartedAt = Date.now();
|
|
4547
4941
|
async function buildSessionStartPayload(overrides, needsSetup = false) {
|
|
4548
4942
|
let maxContext = 0;
|
|
@@ -4571,7 +4965,7 @@ async function runWebUI(opts) {
|
|
|
4571
4965
|
model: opts.agent.ctx.model,
|
|
4572
4966
|
provider: opts.agent.ctx.provider.id,
|
|
4573
4967
|
mode: opts.modeId ?? "default",
|
|
4574
|
-
projectName: opts.projectRoot ?
|
|
4968
|
+
projectName: opts.projectRoot ? path4.basename(opts.projectRoot) : void 0,
|
|
4575
4969
|
// Frontend reads `projectRoot` from session.start (ws-handlers setEnv) —
|
|
4576
4970
|
// omitting it left the store's projectRoot empty after a project switch.
|
|
4577
4971
|
projectRoot: opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "",
|
|
@@ -4597,7 +4991,7 @@ async function runWebUI(opts) {
|
|
|
4597
4991
|
const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot());
|
|
4598
4992
|
const mailbox = new GlobalMailbox(projectDir, opts.events);
|
|
4599
4993
|
webuiClientId = `webui@${crypto3.randomUUID().slice(0, 8)}`;
|
|
4600
|
-
const projectName = opts.projectRoot ?
|
|
4994
|
+
const projectName = opts.projectRoot ? path4.basename(opts.projectRoot) : "unknown";
|
|
4601
4995
|
await mailbox.registerClient({
|
|
4602
4996
|
clientId: webuiClientId,
|
|
4603
4997
|
sessionId: opts.projectRoot,
|
|
@@ -4622,13 +5016,19 @@ async function runWebUI(opts) {
|
|
|
4622
5016
|
}
|
|
4623
5017
|
};
|
|
4624
5018
|
registerWebuiClient();
|
|
5019
|
+
const wsToken = generateAuthToken();
|
|
4625
5020
|
const wss = new WebSocketServer({ port, host, maxPayload: 1 * 1024 * 1024 });
|
|
4626
5021
|
console.log(`[WebUI] WebSocket server starting on ws://${host}:${port}`);
|
|
5022
|
+
let fleetBroadcastCli = null;
|
|
4627
5023
|
const httpServer = startStaticServe({
|
|
4628
5024
|
host,
|
|
4629
5025
|
httpPort,
|
|
4630
5026
|
wsPort,
|
|
4631
|
-
globalRoot:
|
|
5027
|
+
globalRoot: path4.dirname(opts.globalConfigPath ?? ""),
|
|
5028
|
+
onFleetPing: () => {
|
|
5029
|
+
void fleetBroadcastCli?.();
|
|
5030
|
+
},
|
|
5031
|
+
apiToken: wsToken
|
|
4632
5032
|
});
|
|
4633
5033
|
if (httpServer) {
|
|
4634
5034
|
announceWebuiReady({
|
|
@@ -4636,14 +5036,15 @@ async function runWebUI(opts) {
|
|
|
4636
5036
|
host,
|
|
4637
5037
|
httpPort,
|
|
4638
5038
|
wsPort,
|
|
4639
|
-
open: !!opts.open
|
|
5039
|
+
open: !!opts.open,
|
|
5040
|
+
wsToken
|
|
4640
5041
|
});
|
|
4641
5042
|
} else {
|
|
4642
5043
|
console.warn(
|
|
4643
5044
|
`[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`). WS bridge still active on ws://${host}:${wsPort}.`
|
|
4644
5045
|
);
|
|
4645
5046
|
}
|
|
4646
|
-
const registryBaseDir = opts.globalConfigPath ?
|
|
5047
|
+
const registryBaseDir = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : void 0;
|
|
4647
5048
|
if (opts.projectRoot) {
|
|
4648
5049
|
registerWebuiInstance({
|
|
4649
5050
|
pid: process.pid,
|
|
@@ -4678,9 +5079,13 @@ async function runWebUI(opts) {
|
|
|
4678
5079
|
emitConcurrency();
|
|
4679
5080
|
eventUnsubscribers.push(
|
|
4680
5081
|
opts.events.on("iteration.started", (e) => {
|
|
5082
|
+
const maxIt = opts.agent.ctx.meta["maxIterations"];
|
|
4681
5083
|
broadcast({
|
|
4682
5084
|
type: "iteration.started",
|
|
4683
|
-
payload: {
|
|
5085
|
+
payload: {
|
|
5086
|
+
index: e.index,
|
|
5087
|
+
...typeof maxIt === "number" ? { maxIterations: maxIt } : {}
|
|
5088
|
+
}
|
|
4684
5089
|
});
|
|
4685
5090
|
})
|
|
4686
5091
|
);
|
|
@@ -4941,6 +5346,18 @@ async function runWebUI(opts) {
|
|
|
4941
5346
|
broadcast,
|
|
4942
5347
|
log: (m) => console.log(m)
|
|
4943
5348
|
};
|
|
5349
|
+
const skillsProjectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5350
|
+
const skillsCtx = {
|
|
5351
|
+
skillLoader: opts.skillLoader,
|
|
5352
|
+
skillInstaller: opts.skillLoader ? new SkillInstaller({
|
|
5353
|
+
manifestPath: path4.join(wstackGlobalRoot(), "installed-skills.json"),
|
|
5354
|
+
projectSkillsDir: path4.join(skillsProjectRoot, ".wrongstack", "skills"),
|
|
5355
|
+
globalSkillsDir: path4.join(wstackGlobalRoot(), "skills"),
|
|
5356
|
+
projectHash: skillsProjectRoot ? projectHash(skillsProjectRoot) : "",
|
|
5357
|
+
skillLoader: opts.skillLoader
|
|
5358
|
+
}) : void 0,
|
|
5359
|
+
projectRoot: skillsProjectRoot
|
|
5360
|
+
};
|
|
4944
5361
|
const worklistCtx = {
|
|
4945
5362
|
agent: opts.agent,
|
|
4946
5363
|
sessionId: opts.session.id,
|
|
@@ -5005,20 +5422,22 @@ async function runWebUI(opts) {
|
|
|
5005
5422
|
console.log(`[WebUI] WebSocket server running on ws://${host}:${port}`);
|
|
5006
5423
|
setupEvents();
|
|
5007
5424
|
opts.onListening?.({ httpPort, wsPort, host });
|
|
5008
|
-
const globalRoot = opts.globalConfigPath ?
|
|
5425
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : void 0;
|
|
5009
5426
|
if (globalRoot) {
|
|
5010
|
-
const
|
|
5427
|
+
const broadcastSessions = async () => {
|
|
5011
5428
|
try {
|
|
5012
5429
|
const { SessionRegistry } = await import('@wrongstack/core');
|
|
5013
5430
|
const registry = new SessionRegistry(globalRoot);
|
|
5014
5431
|
const sessions = await registry.list();
|
|
5015
|
-
const
|
|
5432
|
+
const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
|
|
5433
|
+
const live = sessions.filter((s) => s.status !== "stale").filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
|
|
5016
5434
|
sessionId: s.sessionId,
|
|
5017
5435
|
projectName: s.projectName,
|
|
5018
5436
|
projectSlug: s.projectSlug,
|
|
5019
5437
|
projectRoot: s.projectRoot,
|
|
5020
5438
|
workingDir: s.workingDir,
|
|
5021
5439
|
gitBranch: s.gitBranch,
|
|
5440
|
+
clientType: s.clientType,
|
|
5022
5441
|
status: s.status,
|
|
5023
5442
|
pid: s.pid,
|
|
5024
5443
|
startedAt: s.startedAt,
|
|
@@ -5030,60 +5449,52 @@ async function runWebUI(opts) {
|
|
|
5030
5449
|
currentTool: a.currentTool,
|
|
5031
5450
|
iterations: a.iterations,
|
|
5032
5451
|
toolCalls: a.toolCalls,
|
|
5452
|
+
costUsd: a.costUsd,
|
|
5453
|
+
tokensIn: a.tokensIn,
|
|
5454
|
+
tokensOut: a.tokensOut,
|
|
5455
|
+
ctxPct: a.ctxPct,
|
|
5456
|
+
model: a.model,
|
|
5457
|
+
partialText: a.partialText,
|
|
5033
5458
|
lastActivityAt: a.lastActivityAt
|
|
5034
5459
|
}))
|
|
5035
5460
|
}));
|
|
5036
5461
|
broadcast({ type: "sessions.status_update", payload: { sessions: live } });
|
|
5037
5462
|
} catch {
|
|
5038
5463
|
}
|
|
5039
|
-
}
|
|
5464
|
+
};
|
|
5465
|
+
fleetBroadcastCli = broadcastSessions;
|
|
5466
|
+
const statusInterval = setInterval(() => void broadcastSessions(), 5e3);
|
|
5040
5467
|
if (statusInterval.unref) statusInterval.unref();
|
|
5041
5468
|
eventUnsubscribers.push(() => clearInterval(statusInterval));
|
|
5042
|
-
|
|
5043
|
-
});
|
|
5044
|
-
wss.on("connection", async (ws, req2) => {
|
|
5045
|
-
const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
5046
|
-
const tokenMatches = (provided) => {
|
|
5047
|
-
if (!provided) return false;
|
|
5048
|
-
const a = Buffer.from(provided);
|
|
5049
|
-
const b = Buffer.from(authToken);
|
|
5050
|
-
return a.length === b.length && crypto3.timingSafeEqual(a, b);
|
|
5051
|
-
};
|
|
5052
|
-
try {
|
|
5053
|
-
const url = new URL(req2.url ?? "/", `http://localhost:${port}`);
|
|
5054
|
-
const token = url.searchParams.get("token");
|
|
5055
|
-
const tokenOk = tokenMatches(token);
|
|
5056
|
-
const hostHeader = (req2.headers.host ?? "").trim();
|
|
5057
|
-
let hostOk = false;
|
|
5469
|
+
let regDebounce;
|
|
5058
5470
|
try {
|
|
5059
|
-
|
|
5471
|
+
const regWatcher = watch(globalRoot, { persistent: false }, (_event, filename) => {
|
|
5472
|
+
const name = filename ? String(filename) : "";
|
|
5473
|
+
if (!name.startsWith("session-registry.json") || name.endsWith(".lock")) return;
|
|
5474
|
+
if (regDebounce) clearTimeout(regDebounce);
|
|
5475
|
+
regDebounce = setTimeout(() => void broadcastSessions(), 150);
|
|
5476
|
+
});
|
|
5477
|
+
eventUnsubscribers.push(() => {
|
|
5478
|
+
if (regDebounce) clearTimeout(regDebounce);
|
|
5479
|
+
regWatcher.close();
|
|
5480
|
+
});
|
|
5060
5481
|
} catch {
|
|
5061
|
-
hostOk = false;
|
|
5062
5482
|
}
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
}
|
|
5079
|
-
} else {
|
|
5080
|
-
if (!tokenOk) {
|
|
5081
|
-
ws.close(4003, "Forbidden: auth token required for non-browser clients");
|
|
5082
|
-
return;
|
|
5083
|
-
}
|
|
5084
|
-
}
|
|
5085
|
-
} catch {
|
|
5086
|
-
ws.close(4001, "Unauthorized: malformed request");
|
|
5483
|
+
void broadcastSessions();
|
|
5484
|
+
}
|
|
5485
|
+
});
|
|
5486
|
+
wss.on("connection", async (ws, req2) => {
|
|
5487
|
+
const ok = verifyClient({
|
|
5488
|
+
origin: req2.headers.origin,
|
|
5489
|
+
url: req2.url ?? "/",
|
|
5490
|
+
hostHeader: req2.headers.host,
|
|
5491
|
+
remoteAddress: req2.socket.remoteAddress,
|
|
5492
|
+
cookieHeader: req2.headers.cookie,
|
|
5493
|
+
wsHost: host,
|
|
5494
|
+
expectedToken: wsToken
|
|
5495
|
+
});
|
|
5496
|
+
if (!ok) {
|
|
5497
|
+
ws.close(4003, "Forbidden");
|
|
5087
5498
|
return;
|
|
5088
5499
|
}
|
|
5089
5500
|
const client = { ws, sessionId: opts.session.id };
|
|
@@ -5227,6 +5638,31 @@ async function runWebUI(opts) {
|
|
|
5227
5638
|
await handleProviderRemove(wsHandlerCtx, ws, m.payload.providerId);
|
|
5228
5639
|
break;
|
|
5229
5640
|
}
|
|
5641
|
+
case "provider.clear_models": {
|
|
5642
|
+
const m = msg;
|
|
5643
|
+
await handleProviderClearModels(wsHandlerCtx, ws, m.payload.providerId);
|
|
5644
|
+
break;
|
|
5645
|
+
}
|
|
5646
|
+
case "provider.undo_clear": {
|
|
5647
|
+
const m = msg;
|
|
5648
|
+
await handleProviderUndoClear(
|
|
5649
|
+
wsHandlerCtx,
|
|
5650
|
+
ws,
|
|
5651
|
+
m.payload.providerId,
|
|
5652
|
+
m.payload.previousModels
|
|
5653
|
+
);
|
|
5654
|
+
break;
|
|
5655
|
+
}
|
|
5656
|
+
case "provider.update": {
|
|
5657
|
+
const m = msg;
|
|
5658
|
+
await handleProviderUpdate(wsHandlerCtx, ws, m.payload);
|
|
5659
|
+
break;
|
|
5660
|
+
}
|
|
5661
|
+
case "provider.probe": {
|
|
5662
|
+
const m = msg;
|
|
5663
|
+
await handleProviderProbe(wsHandlerCtx, ws, m.payload.providerId, m.payload.timeoutMs);
|
|
5664
|
+
break;
|
|
5665
|
+
}
|
|
5230
5666
|
case "todos.get": {
|
|
5231
5667
|
handleTodosGet(worklistCtx, ws);
|
|
5232
5668
|
break;
|
|
@@ -5383,10 +5819,84 @@ async function runWebUI(opts) {
|
|
|
5383
5819
|
}
|
|
5384
5820
|
return handleMemoryForget(ws, msg, opts.memoryStore);
|
|
5385
5821
|
}
|
|
5822
|
+
// ── MCP operations — MCP servers are read directly from config file ──
|
|
5823
|
+
case "mcp.list": {
|
|
5824
|
+
const servers = [];
|
|
5825
|
+
if (opts.globalConfigPath) {
|
|
5826
|
+
try {
|
|
5827
|
+
const raw = await fsp5.readFile(opts.globalConfigPath, "utf8");
|
|
5828
|
+
const cfg = JSON.parse(raw);
|
|
5829
|
+
const mcpServers = cfg.mcpServers;
|
|
5830
|
+
if (mcpServers) {
|
|
5831
|
+
for (const [name, serverCfg] of Object.entries(mcpServers)) {
|
|
5832
|
+
servers.push({
|
|
5833
|
+
name,
|
|
5834
|
+
transport: serverCfg.transport ?? "stdio",
|
|
5835
|
+
status: "stopped",
|
|
5836
|
+
enabled: serverCfg.enabled ?? true,
|
|
5837
|
+
// Conditional spreads keep these absent (not explicitly
|
|
5838
|
+
// `undefined`) to satisfy exactOptionalPropertyTypes.
|
|
5839
|
+
...serverCfg.description !== void 0 && { description: serverCfg.description },
|
|
5840
|
+
...serverCfg.allowedTools !== void 0 && { tools: serverCfg.allowedTools }
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
} catch {
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
send(ws, { type: "mcp.list", payload: { servers } });
|
|
5848
|
+
break;
|
|
5849
|
+
}
|
|
5850
|
+
case "mcp.add":
|
|
5851
|
+
case "mcp.remove":
|
|
5852
|
+
case "mcp.update":
|
|
5853
|
+
case "mcp.wake":
|
|
5854
|
+
case "mcp.sleep":
|
|
5855
|
+
case "mcp.discover":
|
|
5856
|
+
case "mcp.enable":
|
|
5857
|
+
case "mcp.disable":
|
|
5858
|
+
case "mcp.restart": {
|
|
5859
|
+
send(ws, {
|
|
5860
|
+
type: "mcp.operation_result",
|
|
5861
|
+
payload: {
|
|
5862
|
+
success: false,
|
|
5863
|
+
message: 'MCP management operations require the standalone WebUI server. Please run "wrongstack webui" instead of "wrongstack --webui".'
|
|
5864
|
+
}
|
|
5865
|
+
});
|
|
5866
|
+
break;
|
|
5867
|
+
}
|
|
5386
5868
|
case "skills.list": {
|
|
5387
5869
|
await handleSkillsList(introspectionCtx, ws);
|
|
5388
5870
|
break;
|
|
5389
5871
|
}
|
|
5872
|
+
case "skills.content": {
|
|
5873
|
+
await handleSkillsContent(ws, skillsCtx, msg);
|
|
5874
|
+
break;
|
|
5875
|
+
}
|
|
5876
|
+
case "skills.install": {
|
|
5877
|
+
await handleSkillsInstall(ws, skillsCtx, msg);
|
|
5878
|
+
break;
|
|
5879
|
+
}
|
|
5880
|
+
case "skills.uninstall": {
|
|
5881
|
+
await handleSkillsUninstall(ws, skillsCtx, msg);
|
|
5882
|
+
break;
|
|
5883
|
+
}
|
|
5884
|
+
case "skills.update": {
|
|
5885
|
+
await handleSkillsUpdate(ws, skillsCtx, msg);
|
|
5886
|
+
break;
|
|
5887
|
+
}
|
|
5888
|
+
case "skills.create": {
|
|
5889
|
+
await handleSkillsCreate(ws, skillsCtx, msg);
|
|
5890
|
+
break;
|
|
5891
|
+
}
|
|
5892
|
+
case "skills.edit": {
|
|
5893
|
+
await handleSkillsEdit(ws, skillsCtx, msg);
|
|
5894
|
+
break;
|
|
5895
|
+
}
|
|
5896
|
+
case "skills.export": {
|
|
5897
|
+
await handleSkillsExport(ws, skillsCtx);
|
|
5898
|
+
break;
|
|
5899
|
+
}
|
|
5390
5900
|
case "modes.list": {
|
|
5391
5901
|
await handleModesList(agentConfigCtx, ws);
|
|
5392
5902
|
break;
|
|
@@ -5501,11 +6011,16 @@ async function runWebUI(opts) {
|
|
|
5501
6011
|
break;
|
|
5502
6012
|
}
|
|
5503
6013
|
// Collaboration messages — the CLI webui-server doesn't run a
|
|
5504
|
-
// full collab hub; silently acknowledge and ignore.
|
|
6014
|
+
// full collab hub; silently acknowledge and ignore. request_pause /
|
|
6015
|
+
// resume are included so the CollabPanel's pause/resume buttons don't
|
|
6016
|
+
// trip the "Unhandled message type" warning (the standalone webui
|
|
6017
|
+
// server is the one that wires the real CollaborationWebSocketHandler).
|
|
5505
6018
|
case "collab.join":
|
|
5506
6019
|
case "collab.leave":
|
|
5507
6020
|
case "collab.annotate":
|
|
5508
6021
|
case "collab.resolve":
|
|
6022
|
+
case "collab.request_pause":
|
|
6023
|
+
case "collab.resume":
|
|
5509
6024
|
break;
|
|
5510
6025
|
case "projects.list": {
|
|
5511
6026
|
await handleProjectsList(projectsCtx, ws);
|
|
@@ -5554,7 +6069,7 @@ async function runWebUI(opts) {
|
|
|
5554
6069
|
// ── Mailbox operations — project-level inter-agent messaging ────
|
|
5555
6070
|
case "mailbox.messages": {
|
|
5556
6071
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5557
|
-
const globalRoot = opts.globalConfigPath ?
|
|
6072
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
5558
6073
|
if (!projectRoot || !globalRoot) {
|
|
5559
6074
|
send(ws, {
|
|
5560
6075
|
type: "mailbox.messages",
|
|
@@ -5603,7 +6118,7 @@ async function runWebUI(opts) {
|
|
|
5603
6118
|
}
|
|
5604
6119
|
case "mailbox.agents": {
|
|
5605
6120
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5606
|
-
const globalRoot = opts.globalConfigPath ?
|
|
6121
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
5607
6122
|
if (!projectRoot || !globalRoot) {
|
|
5608
6123
|
send(ws, {
|
|
5609
6124
|
type: "mailbox.agents",
|
|
@@ -5646,7 +6161,7 @@ async function runWebUI(opts) {
|
|
|
5646
6161
|
}
|
|
5647
6162
|
case "mailbox.clear": {
|
|
5648
6163
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5649
|
-
const globalRoot = opts.globalConfigPath ?
|
|
6164
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
5650
6165
|
if (!projectRoot || !globalRoot) {
|
|
5651
6166
|
send(ws, { type: "mailbox.cleared", payload: { error: "No project root available" } });
|
|
5652
6167
|
break;
|
|
@@ -5664,6 +6179,32 @@ async function runWebUI(opts) {
|
|
|
5664
6179
|
}
|
|
5665
6180
|
break;
|
|
5666
6181
|
}
|
|
6182
|
+
case "mailbox.purge": {
|
|
6183
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
6184
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
6185
|
+
if (!projectRoot || !globalRoot) {
|
|
6186
|
+
send(ws, { type: "mailbox.purged", payload: { error: "No project root available" } });
|
|
6187
|
+
break;
|
|
6188
|
+
}
|
|
6189
|
+
try {
|
|
6190
|
+
const mbDir = resolveProjectDir(projectRoot, globalRoot);
|
|
6191
|
+
const mb = new GlobalMailbox(mbDir);
|
|
6192
|
+
const payload = msg;
|
|
6193
|
+
const result = await mb.purgeStale(payload.payload);
|
|
6194
|
+
send(ws, { type: "mailbox.purged", payload: result });
|
|
6195
|
+
} catch (err) {
|
|
6196
|
+
send(ws, {
|
|
6197
|
+
type: "mailbox.purged",
|
|
6198
|
+
payload: { error: err instanceof Error ? err.message : String(err) }
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6201
|
+
break;
|
|
6202
|
+
}
|
|
6203
|
+
case "git.info": {
|
|
6204
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
6205
|
+
await handleGitInfo(ws, projectRoot);
|
|
6206
|
+
break;
|
|
6207
|
+
}
|
|
5667
6208
|
default: {
|
|
5668
6209
|
const msgType = msg.type;
|
|
5669
6210
|
if (msgType.startsWith("autophase.")) {
|
|
@@ -6276,7 +6817,7 @@ var ReadlineInputReader = class {
|
|
|
6276
6817
|
history = [];
|
|
6277
6818
|
pending = false;
|
|
6278
6819
|
constructor(opts = {}) {
|
|
6279
|
-
this.historyFile = opts.historyFile ??
|
|
6820
|
+
this.historyFile = opts.historyFile ?? path4.join(wstackGlobalRoot(), "history");
|
|
6280
6821
|
}
|
|
6281
6822
|
async loadHistory() {
|
|
6282
6823
|
try {
|
|
@@ -6288,7 +6829,7 @@ var ReadlineInputReader = class {
|
|
|
6288
6829
|
}
|
|
6289
6830
|
async saveHistory() {
|
|
6290
6831
|
try {
|
|
6291
|
-
await fsp5.mkdir(
|
|
6832
|
+
await fsp5.mkdir(path4.dirname(this.historyFile), { recursive: true });
|
|
6292
6833
|
await fsp5.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
6293
6834
|
} catch {
|
|
6294
6835
|
}
|
|
@@ -6620,7 +7161,7 @@ function pickGroupIndex(opts) {
|
|
|
6620
7161
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
6621
7162
|
} catch {
|
|
6622
7163
|
}
|
|
6623
|
-
fs2.mkdirSync(
|
|
7164
|
+
fs2.mkdirSync(path4.dirname(opts.cursorFile), { recursive: true });
|
|
6624
7165
|
fs2.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
6625
7166
|
return current;
|
|
6626
7167
|
} catch {
|
|
@@ -6673,11 +7214,11 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
6673
7214
|
throw new FsError({
|
|
6674
7215
|
message: `Refusing to delete protected file: ${filename}`,
|
|
6675
7216
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
6676
|
-
path:
|
|
7217
|
+
path: path4.join(parentDir, filename),
|
|
6677
7218
|
context: { reason: "protected_basename" }
|
|
6678
7219
|
});
|
|
6679
7220
|
}
|
|
6680
|
-
if (filename !==
|
|
7221
|
+
if (filename !== path4.basename(filename)) {
|
|
6681
7222
|
throw new FsError({
|
|
6682
7223
|
message: `Refusing to delete path with traversal: ${filename}`,
|
|
6683
7224
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
@@ -6689,11 +7230,11 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
6689
7230
|
throw new FsError({
|
|
6690
7231
|
message: `Refusing to delete unknown file: ${filename}`,
|
|
6691
7232
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
6692
|
-
path:
|
|
7233
|
+
path: path4.join(parentDir, filename),
|
|
6693
7234
|
context: { reason: "unknown_file_pattern" }
|
|
6694
7235
|
});
|
|
6695
7236
|
}
|
|
6696
|
-
const resolvedParent =
|
|
7237
|
+
const resolvedParent = path4.resolve(parentDir);
|
|
6697
7238
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
6698
7239
|
throw new FsError({
|
|
6699
7240
|
message: `Unexpected parent directory for bak prune: ${resolvedParent}`,
|
|
@@ -6704,8 +7245,8 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
6704
7245
|
}
|
|
6705
7246
|
}
|
|
6706
7247
|
async function safeDelete(filePath) {
|
|
6707
|
-
const dir =
|
|
6708
|
-
const filename =
|
|
7248
|
+
const dir = path4.dirname(filePath);
|
|
7249
|
+
const filename = path4.basename(filePath);
|
|
6709
7250
|
try {
|
|
6710
7251
|
assertSafeToDelete(filename, dir);
|
|
6711
7252
|
await fsp5.unlink(filePath);
|
|
@@ -6750,16 +7291,16 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
6750
7291
|
}
|
|
6751
7292
|
var defaultHomeDir = () => os__default.homedir();
|
|
6752
7293
|
function historyDir(homeFn = defaultHomeDir) {
|
|
6753
|
-
return
|
|
7294
|
+
return path4.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
6754
7295
|
}
|
|
6755
7296
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
6756
|
-
return
|
|
7297
|
+
return path4.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
6757
7298
|
}
|
|
6758
7299
|
function configPath(homeFn = defaultHomeDir) {
|
|
6759
|
-
return
|
|
7300
|
+
return path4.join(homeFn(), ".wrongstack", "config.json");
|
|
6760
7301
|
}
|
|
6761
7302
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
6762
|
-
return
|
|
7303
|
+
return path4.join(homeFn(), ".wrongstack", "config.json.last");
|
|
6763
7304
|
}
|
|
6764
7305
|
function entryId(ts) {
|
|
6765
7306
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -6769,7 +7310,7 @@ async function ensureHistoryDir(homeFn = defaultHomeDir) {
|
|
|
6769
7310
|
await fsp5.mkdir(historyDir(homeFn), { recursive: true });
|
|
6770
7311
|
} catch (err) {
|
|
6771
7312
|
throw new FsError({
|
|
6772
|
-
message:
|
|
7313
|
+
message: toErrorMessage(err),
|
|
6773
7314
|
code: ERROR_CODES.FS_MKDIR_FAILED,
|
|
6774
7315
|
path: historyDir(homeFn),
|
|
6775
7316
|
cause: err
|
|
@@ -6790,7 +7331,7 @@ async function writeIndex(idx, homeFn = defaultHomeDir) {
|
|
|
6790
7331
|
await atomicWrite(historyIndexPath(homeFn), JSON.stringify(idx, null, 2));
|
|
6791
7332
|
} catch (err) {
|
|
6792
7333
|
throw new FsError({
|
|
6793
|
-
message:
|
|
7334
|
+
message: toErrorMessage(err),
|
|
6794
7335
|
code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|
|
6795
7336
|
path: historyIndexPath(homeFn),
|
|
6796
7337
|
cause: err
|
|
@@ -6811,30 +7352,30 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
6811
7352
|
await atomicWrite(last, content);
|
|
6812
7353
|
} catch (err) {
|
|
6813
7354
|
writeErr(
|
|
6814
|
-
`[config-history] .last backup failed: ${
|
|
7355
|
+
`[config-history] .last backup failed: ${toErrorMessage(err)}`
|
|
6815
7356
|
);
|
|
6816
7357
|
}
|
|
6817
7358
|
}
|
|
6818
7359
|
if (content !== void 0) {
|
|
6819
7360
|
try {
|
|
6820
|
-
const bakPath =
|
|
7361
|
+
const bakPath = path4.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
6821
7362
|
await atomicWrite(bakPath, content);
|
|
6822
7363
|
} catch (err) {
|
|
6823
7364
|
writeErr(
|
|
6824
|
-
`[config-history] timestamped backup failed: ${
|
|
7365
|
+
`[config-history] timestamped backup failed: ${toErrorMessage(err)}`
|
|
6825
7366
|
);
|
|
6826
7367
|
}
|
|
6827
7368
|
}
|
|
6828
7369
|
try {
|
|
6829
|
-
const dir =
|
|
7370
|
+
const dir = path4.join(homeFn(), ".wrongstack");
|
|
6830
7371
|
const files = await fsp5.readdir(dir);
|
|
6831
7372
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
6832
7373
|
for (const f of baks.slice(10)) {
|
|
6833
|
-
await safeDelete(
|
|
7374
|
+
await safeDelete(path4.join(dir, f));
|
|
6834
7375
|
}
|
|
6835
7376
|
} catch (err) {
|
|
6836
7377
|
writeErr(
|
|
6837
|
-
`[config-history] backup prune failed: ${
|
|
7378
|
+
`[config-history] backup prune failed: ${toErrorMessage(err)}`
|
|
6838
7379
|
);
|
|
6839
7380
|
}
|
|
6840
7381
|
}
|
|
@@ -6851,15 +7392,15 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
6851
7392
|
};
|
|
6852
7393
|
try {
|
|
6853
7394
|
await fsp5.writeFile(
|
|
6854
|
-
|
|
7395
|
+
path4.join(historyDir(homeFn), `${id}.json`),
|
|
6855
7396
|
JSON.stringify(entry, null, 2),
|
|
6856
7397
|
"utf8"
|
|
6857
7398
|
);
|
|
6858
7399
|
} catch (err) {
|
|
6859
7400
|
throw new FsError({
|
|
6860
|
-
message:
|
|
7401
|
+
message: toErrorMessage(err),
|
|
6861
7402
|
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
6862
|
-
path:
|
|
7403
|
+
path: path4.join(historyDir(homeFn), `${id}.json`),
|
|
6863
7404
|
cause: err
|
|
6864
7405
|
});
|
|
6865
7406
|
}
|
|
@@ -6874,7 +7415,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
6874
7415
|
}
|
|
6875
7416
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
6876
7417
|
try {
|
|
6877
|
-
const raw = await fsp5.readFile(
|
|
7418
|
+
const raw = await fsp5.readFile(path4.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
6878
7419
|
return JSON.parse(raw);
|
|
6879
7420
|
} catch {
|
|
6880
7421
|
return null;
|
|
@@ -6985,16 +7526,14 @@ async function buildPickableProviders(modelsRegistry, config) {
|
|
|
6985
7526
|
}
|
|
6986
7527
|
return out;
|
|
6987
7528
|
}
|
|
6988
|
-
|
|
6989
|
-
// src/picker.ts
|
|
6990
7529
|
var theme = { primary: color.amber };
|
|
6991
7530
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os__default.homedir()) {
|
|
6992
7531
|
try {
|
|
6993
|
-
const { atomicWrite:
|
|
6994
|
-
const
|
|
7532
|
+
const { atomicWrite: atomicWrite16 } = await import('@wrongstack/core');
|
|
7533
|
+
const fs38 = await import('fs/promises');
|
|
6995
7534
|
let existing = {};
|
|
6996
7535
|
try {
|
|
6997
|
-
const raw = await
|
|
7536
|
+
const raw = await fs38.readFile(configPath2, "utf8");
|
|
6998
7537
|
existing = JSON.parse(raw);
|
|
6999
7538
|
} catch {
|
|
7000
7539
|
}
|
|
@@ -7008,12 +7547,12 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
|
|
|
7008
7547
|
JSON.stringify({
|
|
7009
7548
|
level: "warn",
|
|
7010
7549
|
event: "picker.backup_failed",
|
|
7011
|
-
message:
|
|
7550
|
+
message: toErrorMessage(err),
|
|
7012
7551
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7013
7552
|
})
|
|
7014
7553
|
);
|
|
7015
7554
|
}
|
|
7016
|
-
await
|
|
7555
|
+
await atomicWrite16(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
7017
7556
|
try {
|
|
7018
7557
|
await appendHistory(
|
|
7019
7558
|
oldCfg,
|
|
@@ -7029,7 +7568,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
|
|
|
7029
7568
|
JSON.stringify({
|
|
7030
7569
|
level: "warn",
|
|
7031
7570
|
event: "picker.save_failed",
|
|
7032
|
-
message:
|
|
7571
|
+
message: toErrorMessage(err),
|
|
7033
7572
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7034
7573
|
})
|
|
7035
7574
|
);
|
|
@@ -7335,7 +7874,8 @@ function buildAuthCommand(opts) {
|
|
|
7335
7874
|
{
|
|
7336
7875
|
encrypt: (v) => v,
|
|
7337
7876
|
decrypt: (v) => v,
|
|
7338
|
-
isEncrypted: () => false
|
|
7877
|
+
isEncrypted: () => false,
|
|
7878
|
+
keyVersion: 1
|
|
7339
7879
|
}
|
|
7340
7880
|
);
|
|
7341
7881
|
} catch {
|
|
@@ -7676,7 +8216,7 @@ function formatPhaseList(graph) {
|
|
|
7676
8216
|
}
|
|
7677
8217
|
async function gatherProjectContext(projectRoot) {
|
|
7678
8218
|
try {
|
|
7679
|
-
const raw = await fsp5.readFile(
|
|
8219
|
+
const raw = await fsp5.readFile(path4.join(projectRoot, "package.json"), "utf8");
|
|
7680
8220
|
const pkg = JSON.parse(raw);
|
|
7681
8221
|
const parts = [
|
|
7682
8222
|
`Project: ${String(pkg.name ?? "unknown")}`,
|
|
@@ -8027,7 +8567,7 @@ function buildCodebaseReindexCommand(opts) {
|
|
|
8027
8567
|
${color.yellow(` ${r.errors.length} file(s) had errors`)}` : "");
|
|
8028
8568
|
return { message: summary };
|
|
8029
8569
|
} catch (err) {
|
|
8030
|
-
const msg = `${color.red("Codebase reindex failed:")} ${
|
|
8570
|
+
const msg = `${color.red("Codebase reindex failed:")} ${toErrorMessage(err)}`;
|
|
8031
8571
|
return { message: msg };
|
|
8032
8572
|
}
|
|
8033
8573
|
}
|
|
@@ -8114,7 +8654,7 @@ async function historyCommand(opts, sessionId, args) {
|
|
|
8114
8654
|
} catch (err) {
|
|
8115
8655
|
return {
|
|
8116
8656
|
message: color.yellow(
|
|
8117
|
-
`Failed to read session: ${
|
|
8657
|
+
`Failed to read session: ${toErrorMessage(err)}`
|
|
8118
8658
|
)
|
|
8119
8659
|
};
|
|
8120
8660
|
}
|
|
@@ -8616,7 +9156,7 @@ async function spawnAgent(opts, role, _task, _name, header) {
|
|
|
8616
9156
|
opts.renderer.write(msg);
|
|
8617
9157
|
return { message: msg };
|
|
8618
9158
|
} catch (err) {
|
|
8619
|
-
const msg = `${color.red("\u2717 Spawn failed")}: ${
|
|
9159
|
+
const msg = `${color.red("\u2717 Spawn failed")}: ${toErrorMessage(err)}`;
|
|
8620
9160
|
opts.renderer.writeWarning(msg);
|
|
8621
9161
|
return { message: msg };
|
|
8622
9162
|
}
|
|
@@ -9102,17 +9642,17 @@ function diagnoseConfig(cfg, plugins = []) {
|
|
|
9102
9642
|
function scanPlaintextSecrets(node, prefix, findings) {
|
|
9103
9643
|
if (!isPlainObject(node)) return;
|
|
9104
9644
|
for (const [key, value] of Object.entries(node)) {
|
|
9105
|
-
const
|
|
9645
|
+
const path39 = prefix ? `${prefix}.${key}` : key;
|
|
9106
9646
|
if (typeof value === "string") {
|
|
9107
9647
|
if (value.length > 0 && isSecretField$1(key) && !value.startsWith(ENC_PREFIX)) {
|
|
9108
9648
|
findings.push({
|
|
9109
|
-
path:
|
|
9649
|
+
path: path39,
|
|
9110
9650
|
problem: "looks like a plaintext secret (not vault-encrypted) \u2014 it will be encrypted on next boot",
|
|
9111
9651
|
severity: "warning"
|
|
9112
9652
|
});
|
|
9113
9653
|
}
|
|
9114
9654
|
} else if (isPlainObject(value)) {
|
|
9115
|
-
scanPlaintextSecrets(value,
|
|
9655
|
+
scanPlaintextSecrets(value, path39, findings);
|
|
9116
9656
|
}
|
|
9117
9657
|
}
|
|
9118
9658
|
}
|
|
@@ -9133,7 +9673,7 @@ function resolvePersistPath(deps) {
|
|
|
9133
9673
|
return deps.globalConfigPath;
|
|
9134
9674
|
}
|
|
9135
9675
|
async function ensureProjectDir(filePath) {
|
|
9136
|
-
const dir =
|
|
9676
|
+
const dir = path4.dirname(filePath);
|
|
9137
9677
|
try {
|
|
9138
9678
|
await fsp5.mkdir(dir, { recursive: true });
|
|
9139
9679
|
} catch {
|
|
@@ -9157,7 +9697,8 @@ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
|
|
|
9157
9697
|
"session",
|
|
9158
9698
|
"indexing",
|
|
9159
9699
|
"tools",
|
|
9160
|
-
"launch"
|
|
9700
|
+
"launch",
|
|
9701
|
+
"circuitBreaker"
|
|
9161
9702
|
]);
|
|
9162
9703
|
function filterSafeForProject(cfg) {
|
|
9163
9704
|
const out = {};
|
|
@@ -9337,8 +9878,8 @@ function buildDoctorCommand(opts) {
|
|
|
9337
9878
|
return ` ${icon} ${color.cyan(f.path)} \u2014 ${f.problem}${fix}`;
|
|
9338
9879
|
}
|
|
9339
9880
|
async function findParsableBackup(file) {
|
|
9340
|
-
const dir =
|
|
9341
|
-
const base =
|
|
9881
|
+
const dir = path4.dirname(file);
|
|
9882
|
+
const base = path4.basename(file);
|
|
9342
9883
|
const candidates = [`${base}.last`];
|
|
9343
9884
|
try {
|
|
9344
9885
|
const siblings = await fsp5.readdir(dir);
|
|
@@ -9349,7 +9890,7 @@ function buildDoctorCommand(opts) {
|
|
|
9349
9890
|
}
|
|
9350
9891
|
for (const name of candidates) {
|
|
9351
9892
|
try {
|
|
9352
|
-
const raw = await fsp5.readFile(
|
|
9893
|
+
const raw = await fsp5.readFile(path4.join(dir, name), "utf8");
|
|
9353
9894
|
JSON.parse(raw);
|
|
9354
9895
|
return { name, raw };
|
|
9355
9896
|
} catch {
|
|
@@ -9415,7 +9956,7 @@ function buildDoctorCommand(opts) {
|
|
|
9415
9956
|
parsed = JSON.parse(raw);
|
|
9416
9957
|
} catch (err) {
|
|
9417
9958
|
errorCount++;
|
|
9418
|
-
const msg =
|
|
9959
|
+
const msg = toErrorMessage(err);
|
|
9419
9960
|
lines.push(` ${color.red("\u2717")} invalid JSON \u2014 ${msg}`);
|
|
9420
9961
|
if (!applyFixes) {
|
|
9421
9962
|
fixableCount++;
|
|
@@ -9466,7 +10007,7 @@ function buildDoctorCommand(opts) {
|
|
|
9466
10007
|
await atomicWrite(target.file, JSON.stringify(report.fixed, null, 2));
|
|
9467
10008
|
if (!target.isProject) {
|
|
9468
10009
|
try {
|
|
9469
|
-
const homeFn = () =>
|
|
10010
|
+
const homeFn = () => path4.dirname(path4.dirname(target.file));
|
|
9470
10011
|
await appendHistory(parsed, report.fixed, "config doctor auto-fix", homeFn);
|
|
9471
10012
|
} catch {
|
|
9472
10013
|
}
|
|
@@ -9554,6 +10095,65 @@ function buildEnhanceCommand(opts) {
|
|
|
9554
10095
|
}
|
|
9555
10096
|
};
|
|
9556
10097
|
}
|
|
10098
|
+
function buildEnsembleCommand(_opts) {
|
|
10099
|
+
return {
|
|
10100
|
+
name: "ensemble",
|
|
10101
|
+
category: "Agent",
|
|
10102
|
+
description: "Fan a task out to multiple ACP agents in parallel (claude-code, gemini-cli, codex-cli, etc.).",
|
|
10103
|
+
argsHint: "<agent-ids-csv> <task description>",
|
|
10104
|
+
help: [
|
|
10105
|
+
"Fan a single task out to multiple ACP-supporting agents in parallel.",
|
|
10106
|
+
"",
|
|
10107
|
+
"Usage:",
|
|
10108
|
+
" /ensemble <agent-ids-csv> <task description>",
|
|
10109
|
+
"",
|
|
10110
|
+
"Examples:",
|
|
10111
|
+
' /ensemble claude-code,gemini-cli "review this diff"',
|
|
10112
|
+
' /ensemble claude-code,codex-cli "refactor auth/session.ts"',
|
|
10113
|
+
' /ensemble claude-code,gemini-cli,codex-cli "explain the v1 protocol"',
|
|
10114
|
+
"",
|
|
10115
|
+
"Each agent runs in its own process. Agents not installed on the host",
|
|
10116
|
+
"are skipped with a warning. The command waits for all agents to finish",
|
|
10117
|
+
"and reports per-agent outcomes (success / failed / skipped / cancelled).",
|
|
10118
|
+
"",
|
|
10119
|
+
"Use /acp list (or wstack acp list) to see which agents are detected."
|
|
10120
|
+
].join("\n"),
|
|
10121
|
+
async run(args) {
|
|
10122
|
+
const trimmed = args.trim();
|
|
10123
|
+
if (!trimmed) {
|
|
10124
|
+
return {
|
|
10125
|
+
message: 'Usage: /ensemble <agent-ids-csv> <task description>\n\nExamples:\n /ensemble claude-code,gemini-cli "review this diff"\n /ensemble claude-code,codex-cli "refactor auth/session.ts"\n\nRun `wstack acp list` to see which agents are detected on this host.'
|
|
10126
|
+
};
|
|
10127
|
+
}
|
|
10128
|
+
const spaceIdx = trimmed.search(/\s/);
|
|
10129
|
+
if (spaceIdx === -1) {
|
|
10130
|
+
return {
|
|
10131
|
+
message: 'Task description is required.\n\nUsage: /ensemble <agent-ids-csv> <task description>\nExample: /ensemble claude-code,gemini-cli "explain this code"'
|
|
10132
|
+
};
|
|
10133
|
+
}
|
|
10134
|
+
const agentIds = trimmed.slice(0, spaceIdx);
|
|
10135
|
+
const task = stripSurroundingQuotes(trimmed.slice(spaceIdx + 1).trim());
|
|
10136
|
+
if (!task) {
|
|
10137
|
+
return { message: "Task description is required." };
|
|
10138
|
+
}
|
|
10139
|
+
try {
|
|
10140
|
+
const result = await runEnsemble({ agentIds, task });
|
|
10141
|
+
return { message: renderEnsembleText(result) };
|
|
10142
|
+
} catch (err) {
|
|
10143
|
+
return { message: `Ensemble failed: ${toErrorMessage(err)}` };
|
|
10144
|
+
}
|
|
10145
|
+
}
|
|
10146
|
+
};
|
|
10147
|
+
}
|
|
10148
|
+
function stripSurroundingQuotes(s) {
|
|
10149
|
+
if (s.length < 2) return s;
|
|
10150
|
+
const first = s[0];
|
|
10151
|
+
const last = s[s.length - 1];
|
|
10152
|
+
if ((first === '"' || first === "'") && first === last) {
|
|
10153
|
+
return s.slice(1, -1);
|
|
10154
|
+
}
|
|
10155
|
+
return s;
|
|
10156
|
+
}
|
|
9557
10157
|
function parseModelRef(ref) {
|
|
9558
10158
|
const trimmed = ref.trim();
|
|
9559
10159
|
const slash = trimmed.indexOf("/");
|
|
@@ -9854,7 +10454,7 @@ function buildFallbackCommand(opts) {
|
|
|
9854
10454
|
};
|
|
9855
10455
|
} catch (err) {
|
|
9856
10456
|
return {
|
|
9857
|
-
message: `${color.red("fallback error")}: ${
|
|
10457
|
+
message: `${color.red("fallback error")}: ${toErrorMessage(err)}`
|
|
9858
10458
|
};
|
|
9859
10459
|
}
|
|
9860
10460
|
}
|
|
@@ -11071,7 +11671,7 @@ async function handleSpawn(opts, subargs) {
|
|
|
11071
11671
|
const id = await opts.onFleetSpawn(role);
|
|
11072
11672
|
spawned.push(id);
|
|
11073
11673
|
} catch (err) {
|
|
11074
|
-
const warnMsg = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${
|
|
11674
|
+
const warnMsg = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${toErrorMessage(err)}`;
|
|
11075
11675
|
opts.renderer.writeWarning(warnMsg);
|
|
11076
11676
|
}
|
|
11077
11677
|
}
|
|
@@ -11125,7 +11725,7 @@ async function handleDispatch(opts, subargs) {
|
|
|
11125
11725
|
lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
|
|
11126
11726
|
} catch (err) {
|
|
11127
11727
|
lines.push(
|
|
11128
|
-
` ${color.amber("\u26A0 spawn failed:")} ${
|
|
11728
|
+
` ${color.amber("\u26A0 spawn failed:")} ${toErrorMessage(err)}`
|
|
11129
11729
|
);
|
|
11130
11730
|
}
|
|
11131
11731
|
} else {
|
|
@@ -11587,8 +12187,8 @@ function buildInitCommand(opts) {
|
|
|
11587
12187
|
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
11588
12188
|
async run(_args, ctx) {
|
|
11589
12189
|
const root = ctx?.projectRoot ?? opts.projectRoot ?? process.cwd();
|
|
11590
|
-
const dir =
|
|
11591
|
-
const file =
|
|
12190
|
+
const dir = path4.join(root, ".wrongstack");
|
|
12191
|
+
const file = path4.join(dir, "AGENTS.md");
|
|
11592
12192
|
const isFirstInit = !await fileExists(file);
|
|
11593
12193
|
const detected = await detectProjectFacts(root);
|
|
11594
12194
|
const body = renderAgentsTemplate(detected);
|
|
@@ -11596,7 +12196,7 @@ function buildInitCommand(opts) {
|
|
|
11596
12196
|
await fsp5.writeFile(file, body, "utf8");
|
|
11597
12197
|
let nodePkg = false;
|
|
11598
12198
|
try {
|
|
11599
|
-
await fsp5.access(
|
|
12199
|
+
await fsp5.access(path4.join(root, "package.json"));
|
|
11600
12200
|
nodePkg = true;
|
|
11601
12201
|
} catch {
|
|
11602
12202
|
}
|
|
@@ -11758,6 +12358,7 @@ function buildMailboxCommand(opts) {
|
|
|
11758
12358
|
" /mailbox broadcast <message> Message every agent on the project.",
|
|
11759
12359
|
" /mailbox history [n] Last n messages on the project (default 20).",
|
|
11760
12360
|
" /mailbox clear Delete all messages from the mailbox.",
|
|
12361
|
+
" /mailbox purge Remove stale/orphaned messages (completed >1d, incomplete >7d).",
|
|
11761
12362
|
"",
|
|
11762
12363
|
"Examples:",
|
|
11763
12364
|
" /mailbox broadcast pausing deploys, hold off on main",
|
|
@@ -11866,8 +12467,17 @@ function buildMailboxCommand(opts) {
|
|
|
11866
12467
|
await mb.clearAll();
|
|
11867
12468
|
return { message: color.green("\u2713 All messages deleted from the mailbox.") };
|
|
11868
12469
|
}
|
|
12470
|
+
if (sub === "purge") {
|
|
12471
|
+
const result = await mb.purgeStale();
|
|
12472
|
+
if (result.totalPurged === 0) {
|
|
12473
|
+
return { message: color.green("\u2713 No stale messages found. Mailbox is clean.") };
|
|
12474
|
+
}
|
|
12475
|
+
return {
|
|
12476
|
+
message: `\u2713 Purged ${result.totalPurged} message(s): ${result.completedPurged} completed, ${result.incompletePurged} incomplete. ${result.remaining} message(s) remain.`
|
|
12477
|
+
};
|
|
12478
|
+
}
|
|
11869
12479
|
return {
|
|
11870
|
-
message: `Unknown subcommand "${sub}". Use: /mailbox [agents|online|send|broadcast|history|clear]`
|
|
12480
|
+
message: `Unknown subcommand "${sub}". Use: /mailbox [agents|online|send|broadcast|history|clear purge]`
|
|
11871
12481
|
};
|
|
11872
12482
|
}
|
|
11873
12483
|
};
|
|
@@ -12280,9 +12890,9 @@ function stateBadge(state) {
|
|
|
12280
12890
|
return color.dim(state);
|
|
12281
12891
|
}
|
|
12282
12892
|
}
|
|
12283
|
-
async function readConfig(
|
|
12893
|
+
async function readConfig(path39) {
|
|
12284
12894
|
try {
|
|
12285
|
-
return JSON.parse(await fsp5.readFile(
|
|
12895
|
+
return JSON.parse(await fsp5.readFile(path39, "utf8"));
|
|
12286
12896
|
} catch {
|
|
12287
12897
|
return {};
|
|
12288
12898
|
}
|
|
@@ -12290,11 +12900,11 @@ async function readConfig(path40) {
|
|
|
12290
12900
|
function isMcpServerRecord(value) {
|
|
12291
12901
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
12292
12902
|
}
|
|
12293
|
-
async function writeConfig(
|
|
12903
|
+
async function writeConfig(path39, cfg) {
|
|
12294
12904
|
const raw = JSON.stringify(cfg, null, 2);
|
|
12295
|
-
const tmp =
|
|
12905
|
+
const tmp = path39 + ".tmp";
|
|
12296
12906
|
await fsp5.writeFile(tmp, raw, "utf8");
|
|
12297
|
-
await fsp5.rename(tmp,
|
|
12907
|
+
await fsp5.rename(tmp, path39);
|
|
12298
12908
|
}
|
|
12299
12909
|
|
|
12300
12910
|
// src/slash-commands/mcp.ts
|
|
@@ -12484,7 +13094,7 @@ async function runCompact(opts) {
|
|
|
12484
13094
|
responseText = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
12485
13095
|
} catch (err) {
|
|
12486
13096
|
return {
|
|
12487
|
-
message: `LLM call failed: ${
|
|
13097
|
+
message: `LLM call failed: ${toErrorMessage(err)}`
|
|
12488
13098
|
};
|
|
12489
13099
|
}
|
|
12490
13100
|
if (!responseText) {
|
|
@@ -12500,7 +13110,7 @@ ${responseText.slice(0, 500)}` };
|
|
|
12500
13110
|
parsed = JSON.parse(jsonMatch[0]);
|
|
12501
13111
|
} catch (err) {
|
|
12502
13112
|
return {
|
|
12503
|
-
message: `Failed to parse LLM response: ${
|
|
13113
|
+
message: `Failed to parse LLM response: ${toErrorMessage(err)}
|
|
12504
13114
|
|
|
12505
13115
|
Raw response:
|
|
12506
13116
|
${responseText.slice(0, 500)}`
|
|
@@ -12558,7 +13168,7 @@ ${responseText.slice(0, 500)}`
|
|
|
12558
13168
|
}
|
|
12559
13169
|
} catch (err) {
|
|
12560
13170
|
errors.push(
|
|
12561
|
-
`${op.action} failed for ${op.targets.join(", ")}: ${
|
|
13171
|
+
`${op.action} failed for ${op.targets.join(", ")}: ${toErrorMessage(err)}`
|
|
12562
13172
|
);
|
|
12563
13173
|
}
|
|
12564
13174
|
}
|
|
@@ -13195,7 +13805,7 @@ function buildModelsCommand(opts) {
|
|
|
13195
13805
|
};
|
|
13196
13806
|
} catch (err) {
|
|
13197
13807
|
return {
|
|
13198
|
-
message: `${color.red("models error")}: ${
|
|
13808
|
+
message: `${color.red("models error")}: ${toErrorMessage(err)}`
|
|
13199
13809
|
};
|
|
13200
13810
|
}
|
|
13201
13811
|
}
|
|
@@ -13879,7 +14489,7 @@ async function killSession(sessionId) {
|
|
|
13879
14489
|
} catch (err) {
|
|
13880
14490
|
return {
|
|
13881
14491
|
message: color.red(
|
|
13882
|
-
`Failed to kill session: ${
|
|
14492
|
+
`Failed to kill session: ${toErrorMessage(err)}`
|
|
13883
14493
|
)
|
|
13884
14494
|
};
|
|
13885
14495
|
}
|
|
@@ -14250,7 +14860,7 @@ function buildSetModelCommand(opts) {
|
|
|
14250
14860
|
};
|
|
14251
14861
|
} catch (err) {
|
|
14252
14862
|
return {
|
|
14253
|
-
message: `${color.red("setmodel error")}: ${
|
|
14863
|
+
message: `${color.red("setmodel error")}: ${toErrorMessage(err)}`
|
|
14254
14864
|
};
|
|
14255
14865
|
}
|
|
14256
14866
|
}
|
|
@@ -14353,7 +14963,7 @@ function buildSuggestCommand(opts) {
|
|
|
14353
14963
|
opts.onSuggestions?.(suggestions);
|
|
14354
14964
|
return { message: formatSuggestions(suggestions) };
|
|
14355
14965
|
} catch (err) {
|
|
14356
|
-
const msg = `Suggestion generation failed: ${
|
|
14966
|
+
const msg = `Suggestion generation failed: ${toErrorMessage(err)}`;
|
|
14357
14967
|
opts.renderer.writeWarning(msg);
|
|
14358
14968
|
return { message: msg };
|
|
14359
14969
|
}
|
|
@@ -14413,6 +15023,73 @@ function formatSuggestions(suggestions) {
|
|
|
14413
15023
|
return lines.join("\n");
|
|
14414
15024
|
}
|
|
14415
15025
|
|
|
15026
|
+
// src/slash-commands/coordinator.ts
|
|
15027
|
+
function buildCoordinatorCommand(opts) {
|
|
15028
|
+
return {
|
|
15029
|
+
name: "coordinator",
|
|
15030
|
+
category: "Agent",
|
|
15031
|
+
description: "Start, stop, or inspect the AutonomousCoordinator \u2014 the fleet brain that auctions tasks and consults Brain for risky decisions.",
|
|
15032
|
+
help: [
|
|
15033
|
+
"Usage:",
|
|
15034
|
+
" /coordinator start <goal> Start the coordinator with a goal",
|
|
15035
|
+
" /coordinator stop Stop the running coordinator",
|
|
15036
|
+
" /coordinator status Show current coordinator status",
|
|
15037
|
+
"",
|
|
15038
|
+
"The AutonomousCoordinator runs alongside the agent loop and:",
|
|
15039
|
+
" \u2022 Maintains a shared knowledge graph of facts and decisions",
|
|
15040
|
+
" \u2022 Breaks work into a task DAG and auctions tasks to subagents",
|
|
15041
|
+
" \u2022 Consults the Brain for risky decisions",
|
|
15042
|
+
" \u2022 Uses ConsensusProtocol to vote on multi-agent changes",
|
|
15043
|
+
"",
|
|
15044
|
+
"It is separate from /autonomy eternal \u2014 both can run concurrently."
|
|
15045
|
+
].join("\n"),
|
|
15046
|
+
async run(args) {
|
|
15047
|
+
const trimmed = args.trim();
|
|
15048
|
+
const [verbRaw, ...rest] = trimmed.split(/\s+/);
|
|
15049
|
+
const verb = (verbRaw ?? "").toLowerCase();
|
|
15050
|
+
if (verb === "start") {
|
|
15051
|
+
const goal = rest.join(" ").trim();
|
|
15052
|
+
if (!goal) {
|
|
15053
|
+
return { message: "Usage: /coordinator start <goal>\nA goal is required to start the coordinator." };
|
|
15054
|
+
}
|
|
15055
|
+
opts.onCoordinatorStart?.(goal);
|
|
15056
|
+
return {
|
|
15057
|
+
message: `AutonomousCoordinator started with goal: "${goal}"
|
|
15058
|
+
Use /coordinator status to monitor progress.`
|
|
15059
|
+
};
|
|
15060
|
+
}
|
|
15061
|
+
if (verb === "stop") {
|
|
15062
|
+
opts.onCoordinatorStop?.();
|
|
15063
|
+
return { message: "AutonomousCoordinator stop signal sent." };
|
|
15064
|
+
}
|
|
15065
|
+
if (verb === "status") {
|
|
15066
|
+
const canStart = opts.onCoordinatorStart != null;
|
|
15067
|
+
const canStop = opts.onCoordinatorStop != null;
|
|
15068
|
+
return {
|
|
15069
|
+
message: [
|
|
15070
|
+
`Coordinator wired: start=${canStart ? "yes" : "no"}, stop=${canStop ? "yes" : "no"}`,
|
|
15071
|
+
"Use F9 in the TUI for the full coordinator panel."
|
|
15072
|
+
].join("\n")
|
|
15073
|
+
};
|
|
15074
|
+
}
|
|
15075
|
+
return {
|
|
15076
|
+
message: [
|
|
15077
|
+
"Usage:",
|
|
15078
|
+
" /coordinator start <goal> Start with a goal",
|
|
15079
|
+
" /coordinator stop Stop the coordinator",
|
|
15080
|
+
" /coordinator status Show status",
|
|
15081
|
+
"",
|
|
15082
|
+
"The coordinator is a fleet brain that:",
|
|
15083
|
+
" \u2022 Auctions tasks to subagents via TaskAuctioneer",
|
|
15084
|
+
" \u2022 Maintains a shared KnowledgeGraph",
|
|
15085
|
+
" \u2022 Consults Brain for risky decisions",
|
|
15086
|
+
" \u2022 Uses ConsensusProtocol for multi-agent votes"
|
|
15087
|
+
].join("\n")
|
|
15088
|
+
};
|
|
15089
|
+
}
|
|
15090
|
+
};
|
|
15091
|
+
}
|
|
15092
|
+
|
|
14416
15093
|
// src/slash-commands/mouse.ts
|
|
14417
15094
|
function buildMouseCommand(_opts) {
|
|
14418
15095
|
return {
|
|
@@ -14593,7 +15270,7 @@ async function listProjectsCommand(opts, ctx) {
|
|
|
14593
15270
|
return { message: lines.join("\n") };
|
|
14594
15271
|
}
|
|
14595
15272
|
async function addProjectCommand(opts, ctx, targetPath, displayName) {
|
|
14596
|
-
const resolved =
|
|
15273
|
+
const resolved = path4.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), targetPath);
|
|
14597
15274
|
try {
|
|
14598
15275
|
await fsp5.access(resolved);
|
|
14599
15276
|
} catch {
|
|
@@ -14610,7 +15287,7 @@ async function addProjectCommand(opts, ctx, targetPath, displayName) {
|
|
|
14610
15287
|
message: color.yellow(`Project already registered: "${existing.name}" (${existing.slug})`)
|
|
14611
15288
|
};
|
|
14612
15289
|
}
|
|
14613
|
-
const name = displayName?.trim() ||
|
|
15290
|
+
const name = displayName?.trim() || path4.basename(resolved);
|
|
14614
15291
|
const slug = generateSlug(resolved);
|
|
14615
15292
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14616
15293
|
await ensureProjectDataDir(slug, opts.paths?.globalConfig);
|
|
@@ -14663,7 +15340,7 @@ async function removeProjectCommand(opts, _ctx, slugOrName) {
|
|
|
14663
15340
|
};
|
|
14664
15341
|
}
|
|
14665
15342
|
async function switchProjectCommand(opts, ctx, target, displayName) {
|
|
14666
|
-
const resolved =
|
|
15343
|
+
const resolved = path4.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), target);
|
|
14667
15344
|
try {
|
|
14668
15345
|
await fsp5.access(resolved);
|
|
14669
15346
|
} catch {
|
|
@@ -14677,8 +15354,8 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
|
|
|
14677
15354
|
try {
|
|
14678
15355
|
const req2 = createRequire(import.meta.url);
|
|
14679
15356
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
14680
|
-
const pkgDir =
|
|
14681
|
-
cliPath =
|
|
15357
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
15358
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
14682
15359
|
await fsp5.access(cliPath);
|
|
14683
15360
|
} catch {
|
|
14684
15361
|
cliPath = process.argv[1] ?? "";
|
|
@@ -14695,13 +15372,13 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
|
|
|
14695
15372
|
if (existing) {
|
|
14696
15373
|
existing.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
14697
15374
|
} else {
|
|
14698
|
-
const name = displayName?.trim() ||
|
|
15375
|
+
const name = displayName?.trim() || path4.basename(resolved);
|
|
14699
15376
|
const slug = generateSlug(resolved);
|
|
14700
15377
|
manifest.projects.push({ name, root: resolved, slug, lastSeen: (/* @__PURE__ */ new Date()).toISOString() });
|
|
14701
15378
|
await ensureProjectDataDir(slug, opts.paths?.globalConfig);
|
|
14702
15379
|
}
|
|
14703
15380
|
await saveManifest(manifest, opts.paths?.globalConfig);
|
|
14704
|
-
const targetName = displayName?.trim() ||
|
|
15381
|
+
const targetName = displayName?.trim() || path4.basename(resolved);
|
|
14705
15382
|
const canSwitch = await confirmProjectSwitch(opts, targetName);
|
|
14706
15383
|
if (!canSwitch) return { message: "" };
|
|
14707
15384
|
const nodeExe = process.execPath;
|
|
@@ -14817,8 +15494,8 @@ async function spawnInProject(opts, _ctx, root, projectName) {
|
|
|
14817
15494
|
try {
|
|
14818
15495
|
const req2 = createRequire(import.meta.url);
|
|
14819
15496
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
14820
|
-
const pkgDir =
|
|
14821
|
-
cliPath =
|
|
15497
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
15498
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
14822
15499
|
await fsp5.access(cliPath);
|
|
14823
15500
|
} catch {
|
|
14824
15501
|
cliPath = process.argv[1] ?? "";
|
|
@@ -14835,7 +15512,7 @@ async function spawnInProject(opts, _ctx, root, projectName) {
|
|
|
14835
15512
|
if (existing) {
|
|
14836
15513
|
existing.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
14837
15514
|
} else {
|
|
14838
|
-
const name = projectName ||
|
|
15515
|
+
const name = projectName || path4.basename(root);
|
|
14839
15516
|
const slug = generateSlug(root);
|
|
14840
15517
|
manifest.projects.push({ name, root, slug, lastSeen: (/* @__PURE__ */ new Date()).toISOString() });
|
|
14841
15518
|
await ensureProjectDataDir(slug, opts.paths?.globalConfig);
|
|
@@ -14867,8 +15544,8 @@ async function handleNewSession(_opts, _ctx) {
|
|
|
14867
15544
|
try {
|
|
14868
15545
|
const req2 = createRequire(import.meta.url);
|
|
14869
15546
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
14870
|
-
const pkgDir =
|
|
14871
|
-
cliPath =
|
|
15547
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
15548
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
14872
15549
|
await fsp5.access(cliPath);
|
|
14873
15550
|
} catch {
|
|
14874
15551
|
cliPath = process.argv[1] ?? "";
|
|
@@ -14979,7 +15656,7 @@ function buildReviewCommand(opts) {
|
|
|
14979
15656
|
for (const f of allChanged) {
|
|
14980
15657
|
if (f.path.startsWith(".wrongstack/")) continue;
|
|
14981
15658
|
try {
|
|
14982
|
-
await fsp5.access(
|
|
15659
|
+
await fsp5.access(path4.join(cwd, f.path));
|
|
14983
15660
|
existing.push(f);
|
|
14984
15661
|
} catch {
|
|
14985
15662
|
}
|
|
@@ -14990,7 +15667,7 @@ function buildReviewCommand(opts) {
|
|
|
14990
15667
|
const filesWithContent = [];
|
|
14991
15668
|
for (const f of existing.slice(0, 30)) {
|
|
14992
15669
|
try {
|
|
14993
|
-
const content = await fsp5.readFile(
|
|
15670
|
+
const content = await fsp5.readFile(path4.join(cwd, f.path), "utf8");
|
|
14994
15671
|
filesWithContent.push({ ...f, content });
|
|
14995
15672
|
} catch {
|
|
14996
15673
|
}
|
|
@@ -15024,10 +15701,13 @@ function buildSettingsCommand(opts) {
|
|
|
15024
15701
|
" /settings hints on|off Show or suppress rotating launch hints",
|
|
15025
15702
|
" /settings debug-stream on|off Raw SSE hex-dump to stderr for debugging",
|
|
15026
15703
|
" /settings config-scope global|project Save settings globally or per-project",
|
|
15704
|
+
" /settings fs-access unrestricted|project File-tool access scope (project = confine to project root)",
|
|
15027
15705
|
" /settings refine on|off Enable/disable prompt refinement",
|
|
15028
15706
|
" /settings refine-delay <seconds> Countdown duration for refine preview",
|
|
15029
15707
|
" /settings refine-language original|english Default language for refinement",
|
|
15030
15708
|
" /settings semver-part patch|minor|major|auto Default part for /semver and the semver_bump tool",
|
|
15709
|
+
" /settings breaker on|off Enable/disable the process circuit breaker (gates bash/exec)",
|
|
15710
|
+
" /settings breaker-timeout <seconds> Auto kill/reset delay when the breaker trips (0 = manual)",
|
|
15031
15711
|
" /settings defaults Show built-in default values",
|
|
15032
15712
|
"",
|
|
15033
15713
|
"Settings are persisted to ~/.wrongstack/config.json."
|
|
@@ -15039,10 +15719,14 @@ function buildSettingsCommand(opts) {
|
|
|
15039
15719
|
const hints = opts.configStore.get().hints !== false;
|
|
15040
15720
|
const debugStream = opts.configStore.get().debugStream === true;
|
|
15041
15721
|
const configScope = opts.configStore.get().configScope ?? "global";
|
|
15722
|
+
const fsAccess = opts.configStore.get().tools?.restrictToProjectRoot === true ? "project" : "unrestricted";
|
|
15042
15723
|
const enhanceEnabled = autonomy?.enhance ?? true;
|
|
15043
15724
|
const enhanceDelay = autonomy?.enhanceDelayMs ?? 6e4;
|
|
15044
15725
|
const enhanceLanguage = autonomy?.enhanceLanguage ?? "original";
|
|
15045
15726
|
const semverPart = opts.configStore.get().extensions?.["semver-bump"]?.["defaultPart"] ?? "patch";
|
|
15727
|
+
const cb = opts.configStore.get().circuitBreaker;
|
|
15728
|
+
const breakerEnabled = cb?.enabled === true;
|
|
15729
|
+
const breakerTimeout = cb?.autoKillResetMs ?? 6e4;
|
|
15046
15730
|
return [
|
|
15047
15731
|
`${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
|
|
15048
15732
|
"",
|
|
@@ -15051,10 +15735,12 @@ function buildSettingsCommand(opts) {
|
|
|
15051
15735
|
` launch hints: ${hints ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings hints on|off")}`,
|
|
15052
15736
|
` debug stream: ${debugStream ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings debug-stream on|off")}`,
|
|
15053
15737
|
` config scope: ${color.cyan(configScope)} ${color.dim("change: /settings config-scope global|project")}`,
|
|
15738
|
+
` filesystem access: ${color.cyan(fsAccess)} ${color.dim("change: /settings fs-access unrestricted|project")}`,
|
|
15054
15739
|
` refine: ${enhanceEnabled ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings refine on|off")}`,
|
|
15055
15740
|
` refine-delay: ${color.cyan(formatDelay(enhanceDelay))} ${color.dim("change: /settings refine-delay <seconds>")}`,
|
|
15056
15741
|
` refine-language: ${color.cyan(enhanceLanguage)} ${color.dim("change: /settings refine-language original|english")}`,
|
|
15057
15742
|
` semver default part: ${color.cyan(semverPart)} ${color.dim("change: /settings semver-part patch|minor|major|auto")}`,
|
|
15743
|
+
` circuit breaker: ${breakerEnabled ? color.cyan("on") : color.dim("off")} (kill/reset ${breakerTimeout > 0 ? formatDelay(breakerTimeout) : color.dim("manual")}) ${color.dim("change: /settings breaker on|off")}`,
|
|
15058
15744
|
"",
|
|
15059
15745
|
color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
|
|
15060
15746
|
].join("\n");
|
|
@@ -15167,6 +15853,25 @@ function buildSettingsCommand(opts) {
|
|
|
15167
15853
|
const label = raw === "project" ? `${color.cyan("project")} \u2014 settings saved to <project>/.wrongstack/config.json` : `${color.cyan("global")} \u2014 settings saved to ~/.wrongstack/config.json`;
|
|
15168
15854
|
return { message: `${color.green("\u2713")} config scope \u2192 ${label}` };
|
|
15169
15855
|
}
|
|
15856
|
+
if (sub === "fs-access") {
|
|
15857
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
15858
|
+
if (!["unrestricted", "project"].includes(raw)) {
|
|
15859
|
+
return { message: `${color.amber("Usage:")} /settings fs-access unrestricted|project` };
|
|
15860
|
+
}
|
|
15861
|
+
const restrict = raw === "project";
|
|
15862
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
15863
|
+
const tools = cfg.tools ?? {};
|
|
15864
|
+
tools.restrictToProjectRoot = restrict;
|
|
15865
|
+
cfg.tools = tools;
|
|
15866
|
+
const features = cfg.features ?? {};
|
|
15867
|
+
features.allowOutsideProjectRoot = !restrict;
|
|
15868
|
+
cfg.features = features;
|
|
15869
|
+
});
|
|
15870
|
+
const label = restrict ? `${color.cyan("project")} \u2014 file tools confined to the project root` : `${color.cyan("unrestricted")} \u2014 file tools may access paths outside the project root`;
|
|
15871
|
+
return {
|
|
15872
|
+
message: `${color.green("\u2713")} filesystem access \u2192 ${label} ${color.dim("(restart or re-open the session to apply)")}`
|
|
15873
|
+
};
|
|
15874
|
+
}
|
|
15170
15875
|
if (sub === "refine") {
|
|
15171
15876
|
const raw = (rest[0] ?? "").toLowerCase();
|
|
15172
15877
|
if (!["on", "off"].includes(raw)) {
|
|
@@ -15227,19 +15932,55 @@ function buildSettingsCommand(opts) {
|
|
|
15227
15932
|
message: `${color.green("\u2713")} semver default part \u2192 ${color.bold(raw)} ${color.dim("saved to global config; used when /semver or semver_bump gets no explicit part")}`
|
|
15228
15933
|
};
|
|
15229
15934
|
}
|
|
15935
|
+
if (sub === "breaker") {
|
|
15936
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
15937
|
+
if (!["on", "off"].includes(raw)) {
|
|
15938
|
+
return { message: `${color.amber("Usage:")} /settings breaker on|off` };
|
|
15939
|
+
}
|
|
15940
|
+
const on = raw === "on";
|
|
15941
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
15942
|
+
const cb = cfg.circuitBreaker;
|
|
15943
|
+
cfg.circuitBreaker = { ...cb ?? {}, enabled: on };
|
|
15944
|
+
});
|
|
15945
|
+
getProcessRegistry().setBreakerConfig({ enabled: on });
|
|
15946
|
+
return {
|
|
15947
|
+
message: `${color.green("\u2713")} circuit breaker \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim(on ? "bash/exec gated on repeated failures; trips arm the kill/reset countdown" : "bash/exec always proceed")}`
|
|
15948
|
+
};
|
|
15949
|
+
}
|
|
15950
|
+
if (sub === "breaker-timeout") {
|
|
15951
|
+
const raw = rest[0];
|
|
15952
|
+
if (raw === void 0) {
|
|
15953
|
+
return {
|
|
15954
|
+
message: `${color.amber("Usage:")} /settings breaker-timeout <seconds> ${color.dim("(0 = manual recovery only)")}`
|
|
15955
|
+
};
|
|
15956
|
+
}
|
|
15957
|
+
const seconds = Number.parseFloat(raw);
|
|
15958
|
+
if (Number.isNaN(seconds) || seconds < 0) {
|
|
15959
|
+
return {
|
|
15960
|
+
message: `${color.red("Invalid number")}: "${raw}". Enter seconds, e.g. /settings breaker-timeout 60`
|
|
15961
|
+
};
|
|
15962
|
+
}
|
|
15963
|
+
const ms = Math.round(seconds * 1e3);
|
|
15964
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
15965
|
+
const cb = cfg.circuitBreaker;
|
|
15966
|
+
cfg.circuitBreaker = { ...cb ?? {}, autoKillResetMs: ms };
|
|
15967
|
+
});
|
|
15968
|
+
getProcessRegistry().setBreakerConfig({ autoKillResetMs: ms });
|
|
15969
|
+
return {
|
|
15970
|
+
message: `${color.green("\u2713")} breaker kill/reset timeout \u2192 ${ms > 0 ? formatDelay(ms) : color.dim("manual")} ${color.dim(ms > 0 ? "statusline shows a countdown when the breaker trips" : "breaker trips require /kill reset")}`
|
|
15971
|
+
};
|
|
15972
|
+
}
|
|
15230
15973
|
return {
|
|
15231
|
-
message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "refine", "refine-delay", "refine-language", "semver-part", "defaults"], "settings")}`
|
|
15974
|
+
message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "fs-access", "refine", "refine-delay", "refine-language", "semver-part", "breaker", "breaker-timeout", "defaults"], "settings")}`
|
|
15232
15975
|
};
|
|
15233
15976
|
} catch (err) {
|
|
15234
15977
|
return {
|
|
15235
|
-
message: `${color.red("Settings error")}: ${
|
|
15978
|
+
message: `${color.red("Settings error")}: ${toErrorMessage(err)}`
|
|
15236
15979
|
};
|
|
15237
15980
|
}
|
|
15238
15981
|
}
|
|
15239
15982
|
};
|
|
15240
15983
|
}
|
|
15241
|
-
|
|
15242
|
-
// src/slash-commands/spawn-agents.ts
|
|
15243
15984
|
function buildSpawnCommand(opts) {
|
|
15244
15985
|
return {
|
|
15245
15986
|
name: "spawn",
|
|
@@ -15276,7 +16017,7 @@ function buildSpawnCommand(opts) {
|
|
|
15276
16017
|
const summary = Object.keys(parsed).length > 0 ? await opts.onSpawn(description, parsed) : await opts.onSpawn(description);
|
|
15277
16018
|
return { message: summary };
|
|
15278
16019
|
} catch (err) {
|
|
15279
|
-
return { message: `Spawn failed: ${
|
|
16020
|
+
return { message: `Spawn failed: ${toErrorMessage(err)}` };
|
|
15280
16021
|
}
|
|
15281
16022
|
}
|
|
15282
16023
|
};
|
|
@@ -15363,7 +16104,7 @@ var DEFAULTS = {
|
|
|
15363
16104
|
working_dir: true
|
|
15364
16105
|
};
|
|
15365
16106
|
function resolveConfigPath() {
|
|
15366
|
-
return process.env[CONFIG_ENV] ??
|
|
16107
|
+
return process.env[CONFIG_ENV] ?? path4.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
15367
16108
|
}
|
|
15368
16109
|
async function loadStatuslineConfig() {
|
|
15369
16110
|
const p = resolveConfigPath();
|
|
@@ -15377,11 +16118,11 @@ async function loadStatuslineConfig() {
|
|
|
15377
16118
|
async function saveStatuslineConfig(cfg) {
|
|
15378
16119
|
const p = resolveConfigPath();
|
|
15379
16120
|
try {
|
|
15380
|
-
await fsp5.mkdir(
|
|
16121
|
+
await fsp5.mkdir(path4.dirname(p), { recursive: true });
|
|
15381
16122
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
15382
16123
|
} catch (err) {
|
|
15383
16124
|
throw new FsError({
|
|
15384
|
-
message:
|
|
16125
|
+
message: toErrorMessage(err),
|
|
15385
16126
|
code: err instanceof Error && err.message.includes("mkdir") ? ERROR_CODES.FS_MKDIR_FAILED : ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|
|
15386
16127
|
path: p,
|
|
15387
16128
|
cause: err
|
|
@@ -15542,7 +16283,7 @@ function buildTasksCommand(_opts) {
|
|
|
15542
16283
|
return {
|
|
15543
16284
|
name: "tasks",
|
|
15544
16285
|
category: "Inspect",
|
|
15545
|
-
description:
|
|
16286
|
+
description: 'Manage structured tasks with dependencies, types, and priorities: /tasks [show|add <title>|start|done|fail|status <id> <status>|promote <id>|planify <id>|clear]. Tasks are session-persistent (survive resume, isolated to this session). Use `scope: "project"` to share across sessions. For simpler per-turn todos use /todos. For multi-phase roadmaps use /plan.',
|
|
15546
16287
|
help: [
|
|
15547
16288
|
"Usage:",
|
|
15548
16289
|
" /tasks Show task progress + list",
|
|
@@ -15559,7 +16300,8 @@ function buildTasksCommand(_opts) {
|
|
|
15559
16300
|
" /tasks clear Remove all tasks",
|
|
15560
16301
|
"",
|
|
15561
16302
|
"Types: feature, bugfix, refactor, docs, test, chore",
|
|
15562
|
-
"Priorities: critical, high, medium, low"
|
|
16303
|
+
"Priorities: critical, high, medium, low",
|
|
16304
|
+
'Scope: tasks are session-isolated by default; use scope:"project" in the task tool to share across sessions'
|
|
15563
16305
|
].join("\n"),
|
|
15564
16306
|
async run(args, ctx) {
|
|
15565
16307
|
if (!ctx) {
|
|
@@ -15611,12 +16353,26 @@ ${formatPlan(updated)}`;
|
|
|
15611
16353
|
return file;
|
|
15612
16354
|
}
|
|
15613
16355
|
const parts = restJoined.split(/\s+/);
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
16356
|
+
let type = "feature";
|
|
16357
|
+
let priority = "medium";
|
|
16358
|
+
if (parts.length > 1) {
|
|
16359
|
+
const p = validatePriority(parts[parts.length - 1] ?? "");
|
|
16360
|
+
if (p) {
|
|
16361
|
+
priority = p;
|
|
16362
|
+
parts.pop();
|
|
16363
|
+
if (parts.length > 1) {
|
|
16364
|
+
const t = validateType(parts[parts.length - 1] ?? "");
|
|
16365
|
+
if (t) {
|
|
16366
|
+
type = t;
|
|
16367
|
+
parts.pop();
|
|
16368
|
+
}
|
|
16369
|
+
}
|
|
16370
|
+
}
|
|
16371
|
+
}
|
|
16372
|
+
const title = parts.join(" ");
|
|
15617
16373
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15618
16374
|
file.tasks.push({
|
|
15619
|
-
id: `task_${
|
|
16375
|
+
id: `task_${randomUUID()}`,
|
|
15620
16376
|
title,
|
|
15621
16377
|
type,
|
|
15622
16378
|
priority,
|
|
@@ -15739,7 +16495,7 @@ ${formatTaskProgress(file.tasks)}`;
|
|
|
15739
16495
|
];
|
|
15740
16496
|
if (found.item.description) {
|
|
15741
16497
|
todos.push({
|
|
15742
|
-
id: `todo_${
|
|
16498
|
+
id: `todo_${randomUUID()}`,
|
|
15743
16499
|
content: found.item.description.slice(0, 200),
|
|
15744
16500
|
status: "pending",
|
|
15745
16501
|
promotedFromTask: found.item.id
|
|
@@ -15778,7 +16534,7 @@ ${formatTaskProgress(file.tasks)}`;
|
|
|
15778
16534
|
"clear"
|
|
15779
16535
|
],
|
|
15780
16536
|
"tasks"
|
|
15781
|
-
);
|
|
16537
|
+
) + "\n\nRelated: /plan (session-persistent roadmap) | /todos (per-turn list)";
|
|
15782
16538
|
return file;
|
|
15783
16539
|
}
|
|
15784
16540
|
return file;
|
|
@@ -15789,13 +16545,13 @@ ${formatTaskProgress(file.tasks)}`;
|
|
|
15789
16545
|
}
|
|
15790
16546
|
async function discoverPackageFiles(projectRoot) {
|
|
15791
16547
|
const files = [];
|
|
15792
|
-
const rootPkg =
|
|
16548
|
+
const rootPkg = path4.join(projectRoot, "package.json");
|
|
15793
16549
|
try {
|
|
15794
16550
|
await fsp5.access(rootPkg);
|
|
15795
16551
|
files.push(rootPkg);
|
|
15796
16552
|
} catch {
|
|
15797
16553
|
}
|
|
15798
|
-
const workspaceFile =
|
|
16554
|
+
const workspaceFile = path4.join(projectRoot, "pnpm-workspace.yaml");
|
|
15799
16555
|
try {
|
|
15800
16556
|
await fsp5.access(workspaceFile);
|
|
15801
16557
|
const content = await fsp5.readFile(workspaceFile, "utf8");
|
|
@@ -15805,12 +16561,12 @@ async function discoverPackageFiles(projectRoot) {
|
|
|
15805
16561
|
const globs = rawGlobs.split(/[\s,]+/).filter(Boolean).map((g) => g.replace(/['"]/g, ""));
|
|
15806
16562
|
for (const g of globs) {
|
|
15807
16563
|
const dirPrefix = g.replace(/\/?\*$/, "").replace(/\/\*$/, "");
|
|
15808
|
-
const dir =
|
|
16564
|
+
const dir = path4.join(projectRoot, dirPrefix);
|
|
15809
16565
|
try {
|
|
15810
16566
|
const entries = await fsp5.readdir(dir, { withFileTypes: true });
|
|
15811
16567
|
for (const e of entries) {
|
|
15812
16568
|
if (!e.isDirectory()) continue;
|
|
15813
|
-
const subPkg =
|
|
16569
|
+
const subPkg = path4.join(dir, e.name, "package.json");
|
|
15814
16570
|
try {
|
|
15815
16571
|
await fsp5.access(subPkg);
|
|
15816
16572
|
files.push(subPkg);
|
|
@@ -15825,7 +16581,7 @@ async function discoverPackageFiles(projectRoot) {
|
|
|
15825
16581
|
return files;
|
|
15826
16582
|
}
|
|
15827
16583
|
function buildTechStackTask(opts) {
|
|
15828
|
-
const pkgList = opts.packageFiles.map((f) => ` - ${
|
|
16584
|
+
const pkgList = opts.packageFiles.map((f) => ` - ${path4.relative(opts.projectRoot, f)}`).join("\n");
|
|
15829
16585
|
const header = opts.isInit ? [
|
|
15830
16586
|
"## Tech Stack Audit \u2014 First-Time Project Init",
|
|
15831
16587
|
"",
|
|
@@ -15947,7 +16703,7 @@ function buildTechStackCommand(opts) {
|
|
|
15947
16703
|
}
|
|
15948
16704
|
} catch (err) {
|
|
15949
16705
|
discoveryNote = color.red(
|
|
15950
|
-
`Could not scan for package files: ${
|
|
16706
|
+
`Could not scan for package files: ${toErrorMessage(err)}`
|
|
15951
16707
|
);
|
|
15952
16708
|
}
|
|
15953
16709
|
const task = buildTechStackTask({
|
|
@@ -15975,7 +16731,7 @@ function buildTechStackCommand(opts) {
|
|
|
15975
16731
|
const summary = await opts.onSpawnAndWait(task, { name });
|
|
15976
16732
|
return { message: summary };
|
|
15977
16733
|
} catch (err) {
|
|
15978
|
-
const msg = `Tech stack scan failed: ${
|
|
16734
|
+
const msg = `Tech stack scan failed: ${toErrorMessage(err)}`;
|
|
15979
16735
|
opts.renderer.writeWarning(msg);
|
|
15980
16736
|
return { message: msg };
|
|
15981
16737
|
}
|
|
@@ -16088,39 +16844,196 @@ function buildTelegramSetupCommand(opts) {
|
|
|
16088
16844
|
};
|
|
16089
16845
|
}
|
|
16090
16846
|
try {
|
|
16091
|
-
await persistTelegramConfig(persistDeps, (telegram) => {
|
|
16092
|
-
telegram.botToken = botToken;
|
|
16093
|
-
if (chatId) {
|
|
16094
|
-
telegram.notifyChatId = /^\d+$/.test(chatId) ? Number(chatId) : chatId;
|
|
16847
|
+
await persistTelegramConfig(persistDeps, (telegram) => {
|
|
16848
|
+
telegram.botToken = botToken;
|
|
16849
|
+
if (chatId) {
|
|
16850
|
+
telegram.notifyChatId = /^\d+$/.test(chatId) ? Number(chatId) : chatId;
|
|
16851
|
+
}
|
|
16852
|
+
if (telegram.notifyOnSessionEnd === void 0) {
|
|
16853
|
+
telegram.notifyOnSessionEnd = true;
|
|
16854
|
+
}
|
|
16855
|
+
});
|
|
16856
|
+
const chatLine = chatId ? `
|
|
16857
|
+
Default chat: ${color.green(chatId)}` : `
|
|
16858
|
+
${color.dim("No default chat set. You can add it later: /telegram-setup <token> <chatId>")}`;
|
|
16859
|
+
return {
|
|
16860
|
+
message: [
|
|
16861
|
+
`${color.green("\u2713")} Telegram configured successfully!`,
|
|
16862
|
+
"",
|
|
16863
|
+
`Bot: ${color.bold(`@${bot.username ?? bot.first_name}`)} ${color.dim(`(id=${bot.id})`)}`,
|
|
16864
|
+
`Name: ${bot.first_name}`,
|
|
16865
|
+
chatLine,
|
|
16866
|
+
"",
|
|
16867
|
+
`${color.amber("\u26A0")} Restart WrongStack for the plugin to pick up the new config.`,
|
|
16868
|
+
"",
|
|
16869
|
+
"After restart, try:",
|
|
16870
|
+
` ${color.cyan("/telegram:status")} \u2014 check bot connection`,
|
|
16871
|
+
` ${color.cyan("/telegram:send")} \u2014 send a test message`
|
|
16872
|
+
].join("\n")
|
|
16873
|
+
};
|
|
16874
|
+
} catch (err) {
|
|
16875
|
+
return {
|
|
16876
|
+
message: [
|
|
16877
|
+
`${color.red("\u2717")} Failed to save config.`,
|
|
16878
|
+
`Error: ${err.message}`
|
|
16879
|
+
].join("\n")
|
|
16880
|
+
};
|
|
16881
|
+
}
|
|
16882
|
+
}
|
|
16883
|
+
};
|
|
16884
|
+
}
|
|
16885
|
+
init_helpers();
|
|
16886
|
+
var HELP2 = [
|
|
16887
|
+
"Usage:",
|
|
16888
|
+
" /telegram-settings Show current Telegram notification settings",
|
|
16889
|
+
" /telegram-settings session-end on|off Notify on session end",
|
|
16890
|
+
" /telegram-settings delegate on|off Notify when a delegated subagent finishes",
|
|
16891
|
+
" /telegram-settings long-tool <ms|off> Notify for tools slower than <ms> (0/off = disabled)",
|
|
16892
|
+
" /telegram-settings poll <seconds> Bot polling interval (1\u201360)",
|
|
16893
|
+
" /telegram-settings chat <chatId> Default chat for notifications",
|
|
16894
|
+
"",
|
|
16895
|
+
"Aliases: /tg-settings",
|
|
16896
|
+
"",
|
|
16897
|
+
"Settings apply immediately \u2014 no restart needed."
|
|
16898
|
+
].join("\n");
|
|
16899
|
+
function buildTelegramSettingsCommand(opts) {
|
|
16900
|
+
function currentView() {
|
|
16901
|
+
const config = opts.configStore.get();
|
|
16902
|
+
const tg = config.extensions?.telegram ?? {};
|
|
16903
|
+
const sessionEnd = tg.notifyOnSessionEnd === true;
|
|
16904
|
+
const delegate = tg.notifyOnDelegate !== false;
|
|
16905
|
+
const longToolMs = typeof tg.longToolThresholdMs === "number" ? tg.longToolThresholdMs : 3e4;
|
|
16906
|
+
const longTool = longToolMs > 0 ? `${longToolMs}ms` : "off";
|
|
16907
|
+
const poll = typeof tg.pollIntervalSec === "number" ? `${tg.pollIntervalSec}s` : "2s";
|
|
16908
|
+
const chat = tg.notifyChatId !== void 0 && tg.notifyChatId !== null ? String(tg.notifyChatId) : "not set";
|
|
16909
|
+
const hasToken = typeof tg.botToken === "string" && tg.botToken.length > 0;
|
|
16910
|
+
return [
|
|
16911
|
+
`${color.bold("Telegram")} ${color.dim("\u2014 Notification Settings")}`,
|
|
16912
|
+
"",
|
|
16913
|
+
` session end: ${sessionEnd ? color.cyan("on") : color.dim("off")} ${color.dim("change: /telegram-settings session-end on|off")}`,
|
|
16914
|
+
` delegate done: ${delegate ? color.cyan("on") : color.dim("off")} ${color.dim("change: /telegram-settings delegate on|off")}`,
|
|
16915
|
+
` long tool: ${color.cyan(longTool)} ${color.dim("change: /telegram-settings long-tool <ms|off>")}`,
|
|
16916
|
+
` poll interval: ${color.cyan(poll)} ${color.dim("change: /telegram-settings poll <seconds>")}`,
|
|
16917
|
+
` notify chat: ${color.cyan(chat)} ${color.dim("change: /telegram-settings chat <chatId>")}`,
|
|
16918
|
+
"",
|
|
16919
|
+
hasToken ? color.dim(" Bot token configured. Changes apply immediately.") : `${color.amber("\u26A0")} No bot token configured. Run: /telegram-setup <botToken> [chatId]`
|
|
16920
|
+
].join("\n");
|
|
16921
|
+
}
|
|
16922
|
+
return {
|
|
16923
|
+
name: "telegram-settings",
|
|
16924
|
+
category: "Config",
|
|
16925
|
+
aliases: ["tg-settings"],
|
|
16926
|
+
description: "Toggle which agent events are reported to Telegram.",
|
|
16927
|
+
argsHint: "[session-end|delegate|long-tool|poll|chat <value>]",
|
|
16928
|
+
help: HELP2,
|
|
16929
|
+
async run(args) {
|
|
16930
|
+
const { cmd: sub, rest } = parseSubcommand(args);
|
|
16931
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
16932
|
+
return { message: HELP2 };
|
|
16933
|
+
}
|
|
16934
|
+
if (!opts.configStore || !opts.paths?.globalConfig) {
|
|
16935
|
+
return { message: `${color.red("Error")} config store not available.` };
|
|
16936
|
+
}
|
|
16937
|
+
if (!sub) {
|
|
16938
|
+
return { message: currentView() };
|
|
16939
|
+
}
|
|
16940
|
+
const persistDeps = {
|
|
16941
|
+
configStore: opts.configStore,
|
|
16942
|
+
globalConfigPath: opts.paths.globalConfig,
|
|
16943
|
+
vault: noOpVault
|
|
16944
|
+
};
|
|
16945
|
+
try {
|
|
16946
|
+
if (sub === "session-end") {
|
|
16947
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
16948
|
+
if (!["on", "off"].includes(raw)) {
|
|
16949
|
+
return { message: `${color.amber("Usage:")} /telegram-settings session-end on|off` };
|
|
16095
16950
|
}
|
|
16096
|
-
|
|
16097
|
-
|
|
16951
|
+
const on = raw === "on";
|
|
16952
|
+
await persistTelegramConfig(persistDeps, (tg) => {
|
|
16953
|
+
tg.notifyOnSessionEnd = on;
|
|
16954
|
+
});
|
|
16955
|
+
return {
|
|
16956
|
+
message: `${color.green("\u2713")} session-end \u2192 ${on ? color.cyan("on") : color.dim("off")}`
|
|
16957
|
+
};
|
|
16958
|
+
}
|
|
16959
|
+
if (sub === "delegate") {
|
|
16960
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
16961
|
+
if (!["on", "off"].includes(raw)) {
|
|
16962
|
+
return { message: `${color.amber("Usage:")} /telegram-settings delegate on|off` };
|
|
16098
16963
|
}
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16964
|
+
const on = raw === "on";
|
|
16965
|
+
await persistTelegramConfig(persistDeps, (tg) => {
|
|
16966
|
+
tg.notifyOnDelegate = on;
|
|
16967
|
+
});
|
|
16968
|
+
return {
|
|
16969
|
+
message: `${color.green("\u2713")} delegate \u2192 ${on ? color.cyan("on") : color.dim("off")}`
|
|
16970
|
+
};
|
|
16971
|
+
}
|
|
16972
|
+
if (sub === "long-tool") {
|
|
16973
|
+
const raw = rest[0];
|
|
16974
|
+
if (raw === void 0) {
|
|
16975
|
+
return {
|
|
16976
|
+
message: `${color.amber("Usage:")} /telegram-settings long-tool <ms|off> ${color.dim("(0 or off disables)")}`
|
|
16977
|
+
};
|
|
16978
|
+
}
|
|
16979
|
+
if (raw === "off") {
|
|
16980
|
+
await persistTelegramConfig(persistDeps, (tg) => {
|
|
16981
|
+
tg.longToolThresholdMs = 0;
|
|
16982
|
+
});
|
|
16983
|
+
return {
|
|
16984
|
+
message: `${color.green("\u2713")} long-tool \u2192 ${color.dim("off")}`
|
|
16985
|
+
};
|
|
16986
|
+
}
|
|
16987
|
+
const ms = Number.parseInt(raw, 10);
|
|
16988
|
+
if (Number.isNaN(ms) || ms < 0) {
|
|
16989
|
+
return {
|
|
16990
|
+
message: `${color.red("Invalid number")}: "${raw}". Enter milliseconds, e.g. /telegram-settings long-tool 15000`
|
|
16991
|
+
};
|
|
16992
|
+
}
|
|
16993
|
+
await persistTelegramConfig(persistDeps, (tg) => {
|
|
16994
|
+
tg.longToolThresholdMs = ms;
|
|
16995
|
+
});
|
|
16996
|
+
return {
|
|
16997
|
+
message: `${color.green("\u2713")} long-tool \u2192 ${color.cyan(`${ms}ms`)}`
|
|
16998
|
+
};
|
|
16999
|
+
}
|
|
17000
|
+
if (sub === "poll") {
|
|
17001
|
+
const raw = rest[0];
|
|
17002
|
+
if (raw === void 0) {
|
|
17003
|
+
return { message: `${color.amber("Usage:")} /telegram-settings poll <seconds> ${color.dim("(1\u201360)")}` };
|
|
17004
|
+
}
|
|
17005
|
+
const sec = Number.parseInt(raw, 10);
|
|
17006
|
+
if (Number.isNaN(sec) || sec < 1 || sec > 60) {
|
|
17007
|
+
return {
|
|
17008
|
+
message: `${color.red("Invalid value")}: "${raw}". Enter seconds between 1 and 60.`
|
|
17009
|
+
};
|
|
17010
|
+
}
|
|
17011
|
+
await persistTelegramConfig(persistDeps, (tg) => {
|
|
17012
|
+
tg.pollIntervalSec = sec;
|
|
17013
|
+
});
|
|
17014
|
+
return {
|
|
17015
|
+
message: `${color.green("\u2713")} poll \u2192 ${color.cyan(`${sec}s`)}`
|
|
17016
|
+
};
|
|
17017
|
+
}
|
|
17018
|
+
if (sub === "chat") {
|
|
17019
|
+
const raw = rest[0];
|
|
17020
|
+
if (!raw) {
|
|
17021
|
+
return { message: `${color.amber("Usage:")} /telegram-settings chat <chatId>` };
|
|
17022
|
+
}
|
|
17023
|
+
const chatId = /^\d+$/.test(raw) ? Number(raw) : raw;
|
|
17024
|
+
await persistTelegramConfig(persistDeps, (tg) => {
|
|
17025
|
+
tg.notifyChatId = chatId;
|
|
17026
|
+
});
|
|
17027
|
+
return {
|
|
17028
|
+
message: `${color.green("\u2713")} notify chat \u2192 ${color.cyan(raw)}`
|
|
17029
|
+
};
|
|
17030
|
+
}
|
|
16103
17031
|
return {
|
|
16104
|
-
message: [
|
|
16105
|
-
`${color.green("\u2713")} Telegram configured successfully!`,
|
|
16106
|
-
"",
|
|
16107
|
-
`Bot: ${color.bold(`@${bot.username ?? bot.first_name}`)} ${color.dim(`(id=${bot.id})`)}`,
|
|
16108
|
-
`Name: ${bot.first_name}`,
|
|
16109
|
-
chatLine,
|
|
16110
|
-
"",
|
|
16111
|
-
`${color.amber("\u26A0")} Restart WrongStack for the plugin to pick up the new config.`,
|
|
16112
|
-
"",
|
|
16113
|
-
"After restart, try:",
|
|
16114
|
-
` ${color.cyan("/telegram:status")} \u2014 check bot connection`,
|
|
16115
|
-
` ${color.cyan("/telegram:send")} \u2014 send a test message`
|
|
16116
|
-
].join("\n")
|
|
17032
|
+
message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["session-end", "delegate", "long-tool", "poll", "chat"], "telegram-settings")}`
|
|
16117
17033
|
};
|
|
16118
17034
|
} catch (err) {
|
|
16119
17035
|
return {
|
|
16120
|
-
message:
|
|
16121
|
-
`${color.red("\u2717")} Failed to save config.`,
|
|
16122
|
-
`Error: ${err.message}`
|
|
16123
|
-
].join("\n")
|
|
17036
|
+
message: `${color.red("Settings error")}: ${toErrorMessage(err)}`
|
|
16124
17037
|
};
|
|
16125
17038
|
}
|
|
16126
17039
|
}
|
|
@@ -16153,7 +17066,7 @@ function buildTodosCommand(opts) {
|
|
|
16153
17066
|
return {
|
|
16154
17067
|
name: "todos",
|
|
16155
17068
|
category: "Inspect",
|
|
16156
|
-
description: "Inspect or edit the live todo list: /todos [show|clear|add|done|remove|rm <id|index>]",
|
|
17069
|
+
description: "Inspect or edit the live todo list: /todos [show|clear|add|done|remove|rm <id|index>]. For multi-phase work use /plan (session-persistent roadmap). For structured typed work with priorities and dependencies use /tasks (session-persistent).",
|
|
16157
17070
|
async run(args) {
|
|
16158
17071
|
const ctx = opts.context;
|
|
16159
17072
|
if (!ctx) return { message: "No active context." };
|
|
@@ -16207,7 +17120,7 @@ function buildTodosCommand(opts) {
|
|
|
16207
17120
|
}
|
|
16208
17121
|
default:
|
|
16209
17122
|
return {
|
|
16210
|
-
message: unknownSubcommand(cmd, ["show", "clear", "add", "done", "remove"], "todos")
|
|
17123
|
+
message: unknownSubcommand(cmd, ["show", "clear", "add", "done", "remove"], "todos") + "\n\nRelated: /plan (session-persistent roadmap) | /tasks (structured tasks with priorities)"
|
|
16211
17124
|
};
|
|
16212
17125
|
}
|
|
16213
17126
|
}
|
|
@@ -16253,7 +17166,7 @@ function buildWorkingDirCommand(_opts) {
|
|
|
16253
17166
|
}
|
|
16254
17167
|
const trimmed = args.trim();
|
|
16255
17168
|
if (!trimmed) {
|
|
16256
|
-
const rel2 =
|
|
17169
|
+
const rel2 = path4.relative(ctx.projectRoot, ctx.workingDir) || ".";
|
|
16257
17170
|
return {
|
|
16258
17171
|
message: [
|
|
16259
17172
|
`Working directory: ${color.bold(ctx.workingDir)}`,
|
|
@@ -16262,10 +17175,10 @@ function buildWorkingDirCommand(_opts) {
|
|
|
16262
17175
|
].join("\n")
|
|
16263
17176
|
};
|
|
16264
17177
|
}
|
|
16265
|
-
const resolved =
|
|
16266
|
-
const root =
|
|
16267
|
-
const rel =
|
|
16268
|
-
if (rel.startsWith("..") ||
|
|
17178
|
+
const resolved = path4.isAbsolute(trimmed) ? path4.resolve(trimmed) : path4.resolve(ctx.projectRoot, trimmed);
|
|
17179
|
+
const root = path4.resolve(ctx.projectRoot);
|
|
17180
|
+
const rel = path4.relative(root, resolved);
|
|
17181
|
+
if (rel.startsWith("..") || path4.isAbsolute(rel)) {
|
|
16269
17182
|
return {
|
|
16270
17183
|
message: color.red(
|
|
16271
17184
|
`Directory "${trimmed}" is outside the project root.
|
|
@@ -16287,11 +17200,11 @@ function buildWorkingDirCommand(_opts) {
|
|
|
16287
17200
|
ctx.setWorkingDir(resolved);
|
|
16288
17201
|
} catch (err) {
|
|
16289
17202
|
return {
|
|
16290
|
-
message: color.red(
|
|
17203
|
+
message: color.red(toErrorMessage(err))
|
|
16291
17204
|
};
|
|
16292
17205
|
}
|
|
16293
|
-
const prevRel =
|
|
16294
|
-
const newRel =
|
|
17206
|
+
const prevRel = path4.relative(ctx.projectRoot, previous) || ".";
|
|
17207
|
+
const newRel = path4.relative(ctx.projectRoot, resolved) || ".";
|
|
16295
17208
|
return {
|
|
16296
17209
|
message: [
|
|
16297
17210
|
color.green(` \u2713 ${prevRel} \u2192 ${color.bold(newRel)}`),
|
|
@@ -16434,6 +17347,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
16434
17347
|
buildDirectorCommand(opts),
|
|
16435
17348
|
buildFleetCommand(opts),
|
|
16436
17349
|
buildEnhanceCommand(opts),
|
|
17350
|
+
buildEnsembleCommand(),
|
|
16437
17351
|
buildMemoryCommand(opts),
|
|
16438
17352
|
buildTodosCommand(opts),
|
|
16439
17353
|
buildTasksCommand(),
|
|
@@ -16444,6 +17358,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
16444
17358
|
buildMouseCommand(),
|
|
16445
17359
|
buildAutonomyCommand(opts),
|
|
16446
17360
|
buildGoalCommand(opts),
|
|
17361
|
+
buildCoordinatorCommand(opts),
|
|
16447
17362
|
buildBrainCommand(opts),
|
|
16448
17363
|
buildBtwCommand(opts),
|
|
16449
17364
|
buildNextCommand(opts),
|
|
@@ -16456,6 +17371,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
16456
17371
|
buildWorktreeCommand(opts),
|
|
16457
17372
|
buildSettingsCommand(opts),
|
|
16458
17373
|
buildTelegramSetupCommand(opts),
|
|
17374
|
+
buildTelegramSettingsCommand(opts),
|
|
16459
17375
|
buildSetModelCommand(opts),
|
|
16460
17376
|
buildFallbackCommand(opts),
|
|
16461
17377
|
buildModelCapsCommand(opts),
|
|
@@ -16475,8 +17391,6 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
16475
17391
|
})
|
|
16476
17392
|
];
|
|
16477
17393
|
}
|
|
16478
|
-
|
|
16479
|
-
// src/pre-launch.ts
|
|
16480
17394
|
var MANIFESTS = [
|
|
16481
17395
|
"package.json",
|
|
16482
17396
|
"pyproject.toml",
|
|
@@ -16491,13 +17405,13 @@ var MANIFESTS = [
|
|
|
16491
17405
|
];
|
|
16492
17406
|
async function detectProjectKind(projectRoot) {
|
|
16493
17407
|
try {
|
|
16494
|
-
await fsp5.access(
|
|
17408
|
+
await fsp5.access(path4.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
16495
17409
|
return "initialized";
|
|
16496
17410
|
} catch {
|
|
16497
17411
|
}
|
|
16498
17412
|
for (const m of MANIFESTS) {
|
|
16499
17413
|
try {
|
|
16500
|
-
await fsp5.access(
|
|
17414
|
+
await fsp5.access(path4.join(projectRoot, m));
|
|
16501
17415
|
return "project";
|
|
16502
17416
|
} catch {
|
|
16503
17417
|
}
|
|
@@ -16505,8 +17419,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
16505
17419
|
return "empty";
|
|
16506
17420
|
}
|
|
16507
17421
|
async function scaffoldAgentsMd(projectRoot) {
|
|
16508
|
-
const dir =
|
|
16509
|
-
const file =
|
|
17422
|
+
const dir = path4.join(projectRoot, ".wrongstack");
|
|
17423
|
+
const file = path4.join(dir, "AGENTS.md");
|
|
16510
17424
|
const facts = await detectProjectFacts(projectRoot);
|
|
16511
17425
|
const body = renderAgentsTemplate(facts);
|
|
16512
17426
|
await fsp5.mkdir(dir, { recursive: true });
|
|
@@ -16519,7 +17433,7 @@ async function runProjectCheck(opts) {
|
|
|
16519
17433
|
if (kind === "initialized") {
|
|
16520
17434
|
renderer.write(
|
|
16521
17435
|
`
|
|
16522
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
17436
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path4.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
16523
17437
|
`
|
|
16524
17438
|
);
|
|
16525
17439
|
return true;
|
|
@@ -16544,13 +17458,13 @@ async function runProjectCheck(opts) {
|
|
|
16544
17458
|
`);
|
|
16545
17459
|
} catch (err) {
|
|
16546
17460
|
renderer.writeError(
|
|
16547
|
-
`Failed to scaffold AGENTS.md: ${
|
|
17461
|
+
`Failed to scaffold AGENTS.md: ${toErrorMessage(err)}`
|
|
16548
17462
|
);
|
|
16549
17463
|
}
|
|
16550
17464
|
}
|
|
16551
17465
|
return true;
|
|
16552
17466
|
}
|
|
16553
|
-
const gitDir =
|
|
17467
|
+
const gitDir = path4.join(projectRoot, ".git");
|
|
16554
17468
|
let hasGit = false;
|
|
16555
17469
|
try {
|
|
16556
17470
|
await fsp5.access(gitDir);
|
|
@@ -16589,7 +17503,7 @@ async function runProjectCheck(opts) {
|
|
|
16589
17503
|
`);
|
|
16590
17504
|
} catch (err) {
|
|
16591
17505
|
renderer.writeError(
|
|
16592
|
-
`git init failed: ${
|
|
17506
|
+
`git init failed: ${toErrorMessage(err)}
|
|
16593
17507
|
`
|
|
16594
17508
|
);
|
|
16595
17509
|
}
|
|
@@ -16785,11 +17699,11 @@ async function countProjectFiles(projectRoot, threshold) {
|
|
|
16785
17699
|
for (const e of entries) {
|
|
16786
17700
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
16787
17701
|
if (count >= threshold) return;
|
|
16788
|
-
const full =
|
|
17702
|
+
const full = path4.join(dir, e.name);
|
|
16789
17703
|
if (e.isDirectory()) {
|
|
16790
17704
|
await walk(full);
|
|
16791
17705
|
} else if (e.isFile()) {
|
|
16792
|
-
if (INDEXABLE_EXTS.has(
|
|
17706
|
+
if (INDEXABLE_EXTS.has(path4.extname(e.name))) {
|
|
16793
17707
|
count++;
|
|
16794
17708
|
}
|
|
16795
17709
|
}
|
|
@@ -17130,14 +18044,14 @@ function summarize(value, name) {
|
|
|
17130
18044
|
if (typeof v === "object" && v !== null) {
|
|
17131
18045
|
const o = v;
|
|
17132
18046
|
if (name === "edit") {
|
|
17133
|
-
const
|
|
18047
|
+
const path39 = typeof o["path"] === "string" ? o["path"] : "";
|
|
17134
18048
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
17135
|
-
return `${
|
|
18049
|
+
return `${path39} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
17136
18050
|
}
|
|
17137
18051
|
if (name === "write") {
|
|
17138
|
-
const
|
|
18052
|
+
const path39 = typeof o["path"] === "string" ? o["path"] : "";
|
|
17139
18053
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
17140
|
-
return bytes !== void 0 ? `${
|
|
18054
|
+
return bytes !== void 0 ? `${path39} ${bytes}B` : path39;
|
|
17141
18055
|
}
|
|
17142
18056
|
if (typeof o["count"] === "number") {
|
|
17143
18057
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -17148,6 +18062,17 @@ function summarize(value, name) {
|
|
|
17148
18062
|
|
|
17149
18063
|
// src/boot.ts
|
|
17150
18064
|
init_project_utils();
|
|
18065
|
+
function resolveCmdFromCatalog(subagentId) {
|
|
18066
|
+
const desc = findAgentDescriptor(subagentId);
|
|
18067
|
+
if (!desc) return null;
|
|
18068
|
+
const out = {
|
|
18069
|
+
command: desc.acp.command,
|
|
18070
|
+
args: [...desc.acp.args ?? []],
|
|
18071
|
+
role: subagentId
|
|
18072
|
+
};
|
|
18073
|
+
if (desc.acp.env) out.env = desc.acp.env;
|
|
18074
|
+
return out;
|
|
18075
|
+
}
|
|
17151
18076
|
var acpCmd = async (args, deps) => {
|
|
17152
18077
|
const sub = args[0];
|
|
17153
18078
|
if (!sub || sub === "server" || sub === "serve") {
|
|
@@ -17162,6 +18087,9 @@ Usage:
|
|
|
17162
18087
|
wstack acp list List available ACP agents
|
|
17163
18088
|
wstack acp spawn <id> <task>
|
|
17164
18089
|
Spawn an ACP agent as a subagent and wait for result
|
|
18090
|
+
wstack acp parallel <agent-id-csv> <task>
|
|
18091
|
+
Fan a task out to multiple ACP agents in parallel
|
|
18092
|
+
and aggregate the results
|
|
17165
18093
|
wstack acp help Show this help
|
|
17166
18094
|
|
|
17167
18095
|
ACP Mode:
|
|
@@ -17171,9 +18099,17 @@ ACP Mode:
|
|
|
17171
18099
|
Press Ctrl+C to stop.
|
|
17172
18100
|
|
|
17173
18101
|
spawn:
|
|
17174
|
-
Spawns a named ACP agent (
|
|
17175
|
-
|
|
18102
|
+
Spawns a named ACP agent (claude-code, gemini-cli, codex-cli, copilot,
|
|
18103
|
+
cline, goose, openhands, qwen-code, kiro-cli, opencode, mistral-vibe,
|
|
18104
|
+
cursor) with the given task and waits for its result.
|
|
17176
18105
|
Example: wstack acp spawn cline "fix the login bug"
|
|
18106
|
+
|
|
18107
|
+
parallel:
|
|
18108
|
+
Runs the same task on a comma-separated list of ACP agents concurrently.
|
|
18109
|
+
Example: wstack acp parallel claude-code,gemini-cli,codex-cli "review this diff"
|
|
18110
|
+
Each agent's result is rendered under a clearly-marked header. Returns 0
|
|
18111
|
+
if at least one agent succeeds, 1 if all fail. Agents that aren't
|
|
18112
|
+
installed are skipped with a warning.
|
|
17177
18113
|
`);
|
|
17178
18114
|
return 0;
|
|
17179
18115
|
}
|
|
@@ -17183,20 +18119,20 @@ spawn:
|
|
|
17183
18119
|
if (sub === "spawn") {
|
|
17184
18120
|
return spawnACPAgent(args.slice(1), deps);
|
|
17185
18121
|
}
|
|
18122
|
+
if (sub === "parallel") {
|
|
18123
|
+
return parallelACPAgents(args.slice(1), deps);
|
|
18124
|
+
}
|
|
17186
18125
|
deps.renderer.writeError(`Unknown acp subcommand: ${sub}
|
|
17187
18126
|
`);
|
|
17188
18127
|
deps.renderer.write("Run `wstack acp help` for usage.\n");
|
|
17189
18128
|
return 1;
|
|
17190
18129
|
};
|
|
17191
18130
|
async function runACPServer(deps) {
|
|
17192
|
-
const toolRegistry = deps.toolRegistry;
|
|
17193
|
-
const tools = toolRegistry?.list() ?? [];
|
|
17194
18131
|
deps.renderer.writeInfo("Starting WrongStack ACP server...\n");
|
|
17195
|
-
deps.renderer.writeInfo(`Exposing ${tools.length} tool(s) via ACP protocol.
|
|
17196
|
-
`);
|
|
17197
18132
|
deps.renderer.writeInfo("Waiting for ACP client connection on stdin/stdout...\n");
|
|
18133
|
+
deps.renderer.writeInfo("(default runTurn is a no-op echo \u2014 wire makeACPServerAgentTurn for a real agent)\n");
|
|
17198
18134
|
deps.renderer.writeInfo("Press Ctrl+C to stop.\n");
|
|
17199
|
-
const server = new WrongStackACPServer({
|
|
18135
|
+
const server = new WrongStackACPServer({});
|
|
17200
18136
|
const shutdown = () => {
|
|
17201
18137
|
deps.renderer.writeWarning("\nShutting down ACP server...");
|
|
17202
18138
|
server.stop();
|
|
@@ -17215,16 +18151,25 @@ async function runACPServer(deps) {
|
|
|
17215
18151
|
}
|
|
17216
18152
|
return 0;
|
|
17217
18153
|
}
|
|
17218
|
-
function listACPAgents(deps) {
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
18154
|
+
async function listACPAgents(deps) {
|
|
18155
|
+
const registry = new EnsembleRegistry();
|
|
18156
|
+
const detected = await registry.list();
|
|
18157
|
+
deps.renderer.write("Detected ACP agents:\n\n");
|
|
18158
|
+
const installed = detected.filter((a) => a.installed);
|
|
18159
|
+
const missing = detected.filter((a) => !a.installed);
|
|
18160
|
+
for (const a of installed) {
|
|
18161
|
+
const ver = a.version ? ` (${a.version.split("\n")[0]})` : "";
|
|
18162
|
+
deps.renderer.write(` \u2713 ${a.id.padEnd(16)} ${a.displayName}${ver}
|
|
18163
|
+
`);
|
|
18164
|
+
}
|
|
18165
|
+
for (const a of missing) {
|
|
18166
|
+
deps.renderer.write(` \u2717 ${a.id.padEnd(16)} ${a.displayName} (${a.reason ?? "not installed"})
|
|
17225
18167
|
`);
|
|
17226
18168
|
}
|
|
17227
|
-
deps.renderer.write(
|
|
18169
|
+
deps.renderer.write(`
|
|
18170
|
+
${installed.length} of ${detected.length} agents available.
|
|
18171
|
+
`);
|
|
18172
|
+
deps.renderer.write("Use `wstack acp spawn <agent-id> <task>` to delegate a task.\n");
|
|
17228
18173
|
return 0;
|
|
17229
18174
|
}
|
|
17230
18175
|
async function spawnACPAgent(args, deps) {
|
|
@@ -17240,7 +18185,7 @@ async function spawnACPAgent(args, deps) {
|
|
|
17240
18185
|
deps.renderer.write("Task description is required.\n");
|
|
17241
18186
|
return 1;
|
|
17242
18187
|
}
|
|
17243
|
-
const cmd = ACP_AGENT_COMMANDS[subagentId];
|
|
18188
|
+
const cmd = ACP_AGENT_COMMANDS[subagentId] ?? resolveCmdFromCatalog(subagentId);
|
|
17244
18189
|
if (!cmd) {
|
|
17245
18190
|
deps.renderer.writeError(`Unknown ACP agent: ${subagentId}
|
|
17246
18191
|
`);
|
|
@@ -17294,10 +18239,11 @@ async function spawnACPAgent(args, deps) {
|
|
|
17294
18239
|
);
|
|
17295
18240
|
return 0;
|
|
17296
18241
|
} catch (err) {
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
18242
|
+
const e = err;
|
|
18243
|
+
const detail = e.kind ? `[${e.kind}] ` : "";
|
|
18244
|
+
const message = e.message ?? (err instanceof Error ? err.message : String(err));
|
|
18245
|
+
deps.renderer.writeError(`ACP agent error: ${detail}${message}
|
|
18246
|
+
`);
|
|
17301
18247
|
return 1;
|
|
17302
18248
|
} finally {
|
|
17303
18249
|
cleanup();
|
|
@@ -17305,6 +18251,94 @@ async function spawnACPAgent(args, deps) {
|
|
|
17305
18251
|
process.off("SIGTERM", cleanup);
|
|
17306
18252
|
}
|
|
17307
18253
|
}
|
|
18254
|
+
async function parallelACPAgents(args, deps) {
|
|
18255
|
+
const [csv, ...taskParts] = args;
|
|
18256
|
+
if (!csv) {
|
|
18257
|
+
deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
|
|
18258
|
+
deps.renderer.write('Example: wstack acp parallel claude-code,gemini-cli "review this diff"\n');
|
|
18259
|
+
return 1;
|
|
18260
|
+
}
|
|
18261
|
+
const task = taskParts.join(" ");
|
|
18262
|
+
if (!task) {
|
|
18263
|
+
deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
|
|
18264
|
+
deps.renderer.writeError("Task description is required.\n");
|
|
18265
|
+
return 1;
|
|
18266
|
+
}
|
|
18267
|
+
const ac = new AbortController();
|
|
18268
|
+
const onSignal = () => ac.abort();
|
|
18269
|
+
process.on("SIGINT", onSignal);
|
|
18270
|
+
process.on("SIGTERM", onSignal);
|
|
18271
|
+
try {
|
|
18272
|
+
const result = await runEnsemble({
|
|
18273
|
+
agentIds: csv,
|
|
18274
|
+
task,
|
|
18275
|
+
resolveCmd: resolveCmdFromCatalog,
|
|
18276
|
+
signal: ac.signal
|
|
18277
|
+
});
|
|
18278
|
+
const skipped = result.results.filter((r) => r.status === "skipped");
|
|
18279
|
+
if (skipped.length > 0) {
|
|
18280
|
+
deps.renderer.writeWarning(
|
|
18281
|
+
`Skipping ${skipped.length} agent(s) not installed: ${skipped.map((s) => `${s.agentId} (${s.reason ?? "not installed"})`).join(", ")}
|
|
18282
|
+
`
|
|
18283
|
+
);
|
|
18284
|
+
}
|
|
18285
|
+
if (result.summary.succeeded + result.summary.failed + result.summary.cancelled === 0) {
|
|
18286
|
+
deps.renderer.writeError("No installed agents to run.\n");
|
|
18287
|
+
deps.renderer.write("Run `wstack acp list` to see what is available.\n");
|
|
18288
|
+
return 1;
|
|
18289
|
+
}
|
|
18290
|
+
const fannedOut = result.results.filter((r) => r.status !== "skipped").map((r) => r.agentId).join(", ");
|
|
18291
|
+
deps.renderer.writeInfo(
|
|
18292
|
+
`Fanning out to ${result.summary.succeeded + result.summary.failed + result.summary.cancelled} agent(s): ${fannedOut}
|
|
18293
|
+
`
|
|
18294
|
+
);
|
|
18295
|
+
deps.renderer.writeInfo(`Task: ${result.task}
|
|
18296
|
+
|
|
18297
|
+
`);
|
|
18298
|
+
for (const r of result.results) {
|
|
18299
|
+
if (r.status === "skipped") continue;
|
|
18300
|
+
deps.renderer.write(`
|
|
18301
|
+
=== ${r.agentId} ===
|
|
18302
|
+
`);
|
|
18303
|
+
if (r.status === "success") {
|
|
18304
|
+
deps.renderer.write(r.result && r.result.length > 0 ? r.result : "(no result)");
|
|
18305
|
+
deps.renderer.write(
|
|
18306
|
+
`
|
|
18307
|
+
[${r.agentId}] success ${r.durationMs}ms iterations=${r.iterations} toolCalls=${r.toolCalls}
|
|
18308
|
+
`
|
|
18309
|
+
);
|
|
18310
|
+
} else if (r.status === "failed") {
|
|
18311
|
+
deps.renderer.writeError(
|
|
18312
|
+
`[${r.error?.kind ?? "unknown"}] ${r.error?.message ?? "failed"}
|
|
18313
|
+
`
|
|
18314
|
+
);
|
|
18315
|
+
deps.renderer.write(
|
|
18316
|
+
`[${r.agentId}] failed ${r.durationMs}ms
|
|
18317
|
+
`
|
|
18318
|
+
);
|
|
18319
|
+
} else {
|
|
18320
|
+
deps.renderer.writeError(
|
|
18321
|
+
`[${r.error?.kind ?? "aborted"}] ${r.error?.message ?? "cancelled"}
|
|
18322
|
+
`
|
|
18323
|
+
);
|
|
18324
|
+
deps.renderer.write(
|
|
18325
|
+
`[${r.agentId}] cancelled ${r.durationMs}ms
|
|
18326
|
+
`
|
|
18327
|
+
);
|
|
18328
|
+
}
|
|
18329
|
+
}
|
|
18330
|
+
const { succeeded, failed, cancelled, skipped: skip } = result.summary;
|
|
18331
|
+
deps.renderer.write(
|
|
18332
|
+
`
|
|
18333
|
+
Parallel summary: ${succeeded} succeeded, ${failed} failed, ${cancelled} cancelled, ${skip} skipped.
|
|
18334
|
+
`
|
|
18335
|
+
);
|
|
18336
|
+
return succeeded > 0 ? 0 : 1;
|
|
18337
|
+
} finally {
|
|
18338
|
+
process.off("SIGINT", onSignal);
|
|
18339
|
+
process.off("SIGTERM", onSignal);
|
|
18340
|
+
}
|
|
18341
|
+
}
|
|
17308
18342
|
var auditCmd = async (args, deps) => {
|
|
17309
18343
|
const wpaths = resolveWstackPaths({
|
|
17310
18344
|
projectRoot: deps.projectRoot,
|
|
@@ -17354,14 +18388,14 @@ var auditCmd = async (args, deps) => {
|
|
|
17354
18388
|
return verify.ok ? 0 : 1;
|
|
17355
18389
|
};
|
|
17356
18390
|
async function listAudits(log, dir, deps) {
|
|
17357
|
-
const
|
|
17358
|
-
const
|
|
18391
|
+
const fs38 = await import('fs/promises');
|
|
18392
|
+
const path39 = await import('path');
|
|
17359
18393
|
const out = [];
|
|
17360
18394
|
let foundRoot = true;
|
|
17361
18395
|
const scan = async (scanDir, prefix, depth) => {
|
|
17362
18396
|
let entries;
|
|
17363
18397
|
try {
|
|
17364
|
-
entries = await
|
|
18398
|
+
entries = await fs38.readdir(scanDir, { withFileTypes: true });
|
|
17365
18399
|
} catch {
|
|
17366
18400
|
if (depth === 0) foundRoot = false;
|
|
17367
18401
|
return;
|
|
@@ -17369,7 +18403,7 @@ async function listAudits(log, dir, deps) {
|
|
|
17369
18403
|
for (const entry of entries) {
|
|
17370
18404
|
if (entry.name.startsWith(".")) continue;
|
|
17371
18405
|
if (entry.isDirectory()) {
|
|
17372
|
-
if (depth === 0) await scan(
|
|
18406
|
+
if (depth === 0) await scan(path39.join(scanDir, entry.name), entry.name, depth + 1);
|
|
17373
18407
|
continue;
|
|
17374
18408
|
}
|
|
17375
18409
|
if (!entry.isFile() || !entry.name.endsWith(".audit.jsonl")) continue;
|
|
@@ -18092,6 +19126,35 @@ ${color.amber("?")} Pick: `)).trim().toLowerCase();
|
|
|
18092
19126
|
}
|
|
18093
19127
|
}
|
|
18094
19128
|
|
|
19129
|
+
// src/auth-menu/local.ts
|
|
19130
|
+
init_provider_config_utils();
|
|
19131
|
+
|
|
19132
|
+
// src/auth-menu/local.ts
|
|
19133
|
+
var LOCAL_LLM_PRESETS = [
|
|
19134
|
+
{
|
|
19135
|
+
id: "ollama",
|
|
19136
|
+
label: "Ollama",
|
|
19137
|
+
defaultBaseUrl: "http://localhost:11434/v1",
|
|
19138
|
+
noAuth: true,
|
|
19139
|
+
hint: "https://ollama.com \u2014 port 11434, no auth"
|
|
19140
|
+
},
|
|
19141
|
+
{
|
|
19142
|
+
id: "vllm",
|
|
19143
|
+
label: "vLLM",
|
|
19144
|
+
defaultBaseUrl: "http://localhost:8000/v1",
|
|
19145
|
+
noAuth: false,
|
|
19146
|
+
hint: "https://docs.vllm.ai \u2014 port 8000, optional Bearer"
|
|
19147
|
+
},
|
|
19148
|
+
{
|
|
19149
|
+
id: "lmstudio",
|
|
19150
|
+
label: "LM Studio",
|
|
19151
|
+
defaultBaseUrl: "http://localhost:1234/v1",
|
|
19152
|
+
noAuth: false,
|
|
19153
|
+
hint: "https://lmstudio.ai \u2014 port 1234, optional Bearer"
|
|
19154
|
+
}
|
|
19155
|
+
];
|
|
19156
|
+
new Map(LOCAL_LLM_PRESETS.map((p) => [p.id, p]));
|
|
19157
|
+
|
|
18095
19158
|
// src/subcommands/handlers/auth.ts
|
|
18096
19159
|
init_provider_config_utils();
|
|
18097
19160
|
var authCmd = async (args, deps) => {
|
|
@@ -18334,7 +19397,7 @@ async function resolveWstackEntry() {
|
|
|
18334
19397
|
try {
|
|
18335
19398
|
const req2 = createRequire(import.meta.url);
|
|
18336
19399
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
18337
|
-
const entry =
|
|
19400
|
+
const entry = path4.join(path4.dirname(pkgPath), "dist", "index.js");
|
|
18338
19401
|
await fsp5.access(entry);
|
|
18339
19402
|
return entry;
|
|
18340
19403
|
} catch {
|
|
@@ -18349,9 +19412,9 @@ async function benchRun(_args, deps) {
|
|
|
18349
19412
|
const outBase = flagStr(deps, "out") ?? "bench-results";
|
|
18350
19413
|
let config;
|
|
18351
19414
|
try {
|
|
18352
|
-
config = await loadBenchConfig(
|
|
19415
|
+
config = await loadBenchConfig(path4.resolve(deps.cwd, modelsPath));
|
|
18353
19416
|
} catch (err) {
|
|
18354
|
-
deps.renderer.writeError(
|
|
19417
|
+
deps.renderer.writeError(toErrorMessage(err));
|
|
18355
19418
|
return 1;
|
|
18356
19419
|
}
|
|
18357
19420
|
const concurrencyRaw = flagStr(deps, "concurrency");
|
|
@@ -18360,8 +19423,8 @@ async function benchRun(_args, deps) {
|
|
|
18360
19423
|
if (c > 0) config.concurrency = c;
|
|
18361
19424
|
}
|
|
18362
19425
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
18363
|
-
const outDir =
|
|
18364
|
-
const predictionsDir =
|
|
19426
|
+
const outDir = path4.resolve(deps.cwd, outBase, stamp);
|
|
19427
|
+
const predictionsDir = path4.join(outDir, "predictions");
|
|
18365
19428
|
let suite;
|
|
18366
19429
|
let grade;
|
|
18367
19430
|
let isSwebench = false;
|
|
@@ -18373,14 +19436,14 @@ async function benchRun(_args, deps) {
|
|
|
18373
19436
|
}
|
|
18374
19437
|
const languagesRaw = flagStr(deps, "languages");
|
|
18375
19438
|
const languages = languagesRaw ? languagesRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
18376
|
-
suite = createPolyglotSuite({ polyglotDir:
|
|
19439
|
+
suite = createPolyglotSuite({ polyglotDir: path4.resolve(deps.cwd, polyglotDir), languages });
|
|
18377
19440
|
grade = (a) => gradePolyglot(a);
|
|
18378
19441
|
} else if (suiteId === "swebench") {
|
|
18379
19442
|
isSwebench = true;
|
|
18380
19443
|
const datasetDir = flagStr(deps, "dataset-dir");
|
|
18381
19444
|
const docker = flagBool(deps, "docker");
|
|
18382
19445
|
suite = createSwebenchSuite({
|
|
18383
|
-
datasetDir: datasetDir ?
|
|
19446
|
+
datasetDir: datasetDir ? path4.resolve(deps.cwd, datasetDir) : void 0,
|
|
18384
19447
|
docker
|
|
18385
19448
|
});
|
|
18386
19449
|
grade = (a) => gradeSwebench({ ...a, predictionsDir });
|
|
@@ -18405,12 +19468,12 @@ async function benchRun(_args, deps) {
|
|
|
18405
19468
|
onProgress: (msg) => deps.renderer.write(color.dim(msg) + "\n")
|
|
18406
19469
|
});
|
|
18407
19470
|
} catch (err) {
|
|
18408
|
-
deps.renderer.writeError(
|
|
19471
|
+
deps.renderer.writeError(toErrorMessage(err));
|
|
18409
19472
|
return 1;
|
|
18410
19473
|
}
|
|
18411
19474
|
await writeJsonArtifacts(outDir, report);
|
|
18412
19475
|
const md = renderMarkdownReport(report);
|
|
18413
|
-
await fsp5.writeFile(
|
|
19476
|
+
await fsp5.writeFile(path4.join(outDir, "report.md"), md, "utf8");
|
|
18414
19477
|
deps.renderer.write("\n" + md + "\n");
|
|
18415
19478
|
if (isSwebench) {
|
|
18416
19479
|
for (const cell of config.cells) {
|
|
@@ -18423,7 +19486,7 @@ async function benchRun(_args, deps) {
|
|
|
18423
19486
|
"Grade with the official SWE-bench harness: python -m swebench.harness.run_evaluation --predictions_path <file> --run_id <id>"
|
|
18424
19487
|
);
|
|
18425
19488
|
}
|
|
18426
|
-
deps.renderer.writeInfo(`Report written to ${
|
|
19489
|
+
deps.renderer.writeInfo(`Report written to ${path4.join(outDir, "report.md")}`);
|
|
18427
19490
|
return 0;
|
|
18428
19491
|
}
|
|
18429
19492
|
async function benchReport(args, deps) {
|
|
@@ -18432,18 +19495,18 @@ async function benchReport(args, deps) {
|
|
|
18432
19495
|
deps.renderer.writeError("Usage: wstack bench report <run-directory>");
|
|
18433
19496
|
return 1;
|
|
18434
19497
|
}
|
|
18435
|
-
const outDir =
|
|
19498
|
+
const outDir = path4.resolve(deps.cwd, dir);
|
|
18436
19499
|
let summary;
|
|
18437
19500
|
try {
|
|
18438
19501
|
summary = await readSummary(outDir);
|
|
18439
19502
|
} catch (err) {
|
|
18440
19503
|
deps.renderer.writeError(
|
|
18441
|
-
`cannot read summary.json in ${outDir}: ${
|
|
19504
|
+
`cannot read summary.json in ${outDir}: ${toErrorMessage(err)}`
|
|
18442
19505
|
);
|
|
18443
19506
|
return 1;
|
|
18444
19507
|
}
|
|
18445
19508
|
const md = renderMarkdownReport(summary);
|
|
18446
|
-
await fsp5.writeFile(
|
|
19509
|
+
await fsp5.writeFile(path4.join(outDir, "report.md"), md, "utf8");
|
|
18447
19510
|
deps.renderer.write("\n" + md + "\n");
|
|
18448
19511
|
return 0;
|
|
18449
19512
|
}
|
|
@@ -18458,7 +19521,7 @@ async function benchList(_args, deps) {
|
|
|
18458
19521
|
const modelsPath = flagStr(deps, "models");
|
|
18459
19522
|
if (modelsPath) {
|
|
18460
19523
|
try {
|
|
18461
|
-
const config = await loadBenchConfig(
|
|
19524
|
+
const config = await loadBenchConfig(path4.resolve(deps.cwd, modelsPath));
|
|
18462
19525
|
deps.renderer.write("\n" + color.bold("Model cells\n"));
|
|
18463
19526
|
for (const cell of config.cells) {
|
|
18464
19527
|
deps.renderer.write(
|
|
@@ -18476,7 +19539,7 @@ async function benchList(_args, deps) {
|
|
|
18476
19539
|
});
|
|
18477
19540
|
deps.renderer.write("\n" + color.dim(`Harness: ${fp}`) + "\n");
|
|
18478
19541
|
} catch (err) {
|
|
18479
|
-
deps.renderer.writeError(
|
|
19542
|
+
deps.renderer.writeError(toErrorMessage(err));
|
|
18480
19543
|
return 1;
|
|
18481
19544
|
}
|
|
18482
19545
|
}
|
|
@@ -18513,14 +19576,14 @@ var doctorCmd = async (_args, deps) => {
|
|
|
18513
19576
|
checks.push({
|
|
18514
19577
|
name: "provider",
|
|
18515
19578
|
status: "fail",
|
|
18516
|
-
detail: "no provider configured \u2014 run `wstack
|
|
19579
|
+
detail: "no provider configured \u2014 run `wstack auth` to set up"
|
|
18517
19580
|
});
|
|
18518
19581
|
else checks.push({ name: "provider", status: "ok", detail: cfg.provider });
|
|
18519
19582
|
if (!cfg.model)
|
|
18520
19583
|
checks.push({
|
|
18521
19584
|
name: "model",
|
|
18522
19585
|
status: "fail",
|
|
18523
|
-
detail: "no model configured \u2014 run `wstack
|
|
19586
|
+
detail: "no model configured \u2014 run `wstack auth` to configure"
|
|
18524
19587
|
});
|
|
18525
19588
|
else checks.push({ name: "model", status: "ok", detail: cfg.model });
|
|
18526
19589
|
if (cfg.provider) {
|
|
@@ -18560,7 +19623,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
18560
19623
|
checks.push({
|
|
18561
19624
|
name: "models cache",
|
|
18562
19625
|
status: "warn",
|
|
18563
|
-
detail: `read failed: ${
|
|
19626
|
+
detail: `read failed: ${toErrorMessage(err)}`
|
|
18564
19627
|
});
|
|
18565
19628
|
}
|
|
18566
19629
|
try {
|
|
@@ -18575,7 +19638,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
18575
19638
|
}
|
|
18576
19639
|
try {
|
|
18577
19640
|
await fsp5.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
18578
|
-
const probe =
|
|
19641
|
+
const probe = path4.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
18579
19642
|
await fsp5.writeFile(probe, "");
|
|
18580
19643
|
await fsp5.unlink(probe);
|
|
18581
19644
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -18583,7 +19646,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
18583
19646
|
checks.push({
|
|
18584
19647
|
name: "sessions writable",
|
|
18585
19648
|
status: "fail",
|
|
18586
|
-
detail: `cannot write to ${deps.paths.projectSessions}: ${
|
|
19649
|
+
detail: `cannot write to ${deps.paths.projectSessions}: ${toErrorMessage(err)}`
|
|
18587
19650
|
});
|
|
18588
19651
|
}
|
|
18589
19652
|
const mcpEntries = Object.entries(cfg.mcpServers ?? {});
|
|
@@ -18674,12 +19737,12 @@ var exportCmd = async (args, deps) => {
|
|
|
18674
19737
|
try {
|
|
18675
19738
|
rendered = await reader.export(sessionId, { format, includeTools, includeDiagnostics });
|
|
18676
19739
|
} catch (err) {
|
|
18677
|
-
deps.renderer.writeError(`Export failed: ${
|
|
19740
|
+
deps.renderer.writeError(`Export failed: ${toErrorMessage(err)}`);
|
|
18678
19741
|
return 1;
|
|
18679
19742
|
}
|
|
18680
19743
|
if (output) {
|
|
18681
|
-
await fsp5.mkdir(
|
|
18682
|
-
await fsp5.writeFile(
|
|
19744
|
+
await fsp5.mkdir(path4.dirname(path4.resolve(deps.cwd, output)), { recursive: true });
|
|
19745
|
+
await fsp5.writeFile(path4.resolve(deps.cwd, output), rendered, "utf8");
|
|
18683
19746
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
18684
19747
|
`);
|
|
18685
19748
|
} else {
|
|
@@ -18688,80 +19751,23 @@ var exportCmd = async (args, deps) => {
|
|
|
18688
19751
|
}
|
|
18689
19752
|
return 0;
|
|
18690
19753
|
};
|
|
18691
|
-
|
|
18692
|
-
// src/subcommands/handlers/init.ts
|
|
18693
|
-
init_helpers();
|
|
18694
19754
|
var initCmd = async (_args, deps) => {
|
|
18695
|
-
deps.renderer.write(color.bold("WrongStack init\n"));
|
|
18696
|
-
deps.renderer.
|
|
18697
|
-
|
|
18698
|
-
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
|
|
18709
|
-
deps.renderer.write(
|
|
18710
|
-
`Detected API keys for: ${detected.map((p) => p.name).join(", ")}
|
|
19755
|
+
deps.renderer.write(color.bold("WrongStack init (deprecated)\n"));
|
|
19756
|
+
deps.renderer.write(
|
|
19757
|
+
`
|
|
19758
|
+
${color.amber("\u26A0 This command is deprecated.")}
|
|
19759
|
+
|
|
19760
|
+
Use ${color.bold("wstack auth")} to set up providers, add API keys,
|
|
19761
|
+
and configure your default model in one interactive workflow.
|
|
19762
|
+
|
|
19763
|
+
${color.dim("Examples:")}
|
|
19764
|
+
${color.cyan("wstack auth")} Interactive setup menu
|
|
19765
|
+
${color.cyan("wstack auth anthropic")} Add Anthropic API key directly
|
|
19766
|
+
${color.cyan("wstack auth local")} Add local LLM (Ollama, vLLM, LM Studio)
|
|
19767
|
+
|
|
19768
|
+
Run ${color.bold("wstack auth --help")} for more options.
|
|
18711
19769
|
`
|
|
18712
|
-
|
|
18713
|
-
const defaultId = ranked[0]?.id ?? "anthropic";
|
|
18714
|
-
const providerAnswer = (await deps.reader.readLine(`Provider [${defaultId}]: `)).trim();
|
|
18715
|
-
if (providerAnswer === "q") {
|
|
18716
|
-
deps.renderer.write(color.dim("Cancelled.\n"));
|
|
18717
|
-
return 0;
|
|
18718
|
-
}
|
|
18719
|
-
const providerId = providerAnswer || defaultId;
|
|
18720
|
-
const provider = await deps.modelsRegistry.getProvider(providerId);
|
|
18721
|
-
if (!provider) {
|
|
18722
|
-
deps.renderer.writeError(`Provider "${providerId}" not found in models.dev catalog.`);
|
|
18723
|
-
return 1;
|
|
18724
|
-
}
|
|
18725
|
-
if (provider.family === "unsupported") {
|
|
18726
|
-
deps.renderer.writeError(
|
|
18727
|
-
`Provider "${providerId}" uses ${provider.npm} which has no built-in transport. Install a plugin to enable it.`
|
|
18728
|
-
);
|
|
18729
|
-
return 1;
|
|
18730
|
-
}
|
|
18731
|
-
const suggestedModel = await deps.modelsRegistry.suggestModel(providerId) ?? "";
|
|
18732
|
-
const modelHint = suggestedModel ? ` [${suggestedModel}]` : "";
|
|
18733
|
-
const modelAnswer = (await deps.reader.readLine(`Model${modelHint}: `)).trim();
|
|
18734
|
-
if (modelAnswer === "q") {
|
|
18735
|
-
deps.renderer.write(color.dim("Cancelled.\n"));
|
|
18736
|
-
return 0;
|
|
18737
|
-
}
|
|
18738
|
-
const modelId = modelAnswer || suggestedModel;
|
|
18739
|
-
if (!modelId) {
|
|
18740
|
-
deps.renderer.writeError("No model selected. Aborting.");
|
|
18741
|
-
return 1;
|
|
18742
|
-
}
|
|
18743
|
-
const envHit = provider.envVars.map((v) => process.env[v]).find(Boolean);
|
|
18744
|
-
let apiKey = "";
|
|
18745
|
-
if (!envHit) {
|
|
18746
|
-
apiKey = (await deps.reader.readLine(
|
|
18747
|
-
`API key (stored in ${deps.paths.globalConfig}; empty = expect ${provider.envVars[0] ?? "env var"}): `
|
|
18748
|
-
)).trim();
|
|
18749
|
-
} else {
|
|
18750
|
-
deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
|
|
18751
|
-
}
|
|
18752
|
-
await fsp5.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
18753
|
-
const config = { version: 1, provider: providerId, model: modelId };
|
|
18754
|
-
if (apiKey) config.apiKey = apiKey;
|
|
18755
|
-
const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
|
|
18756
|
-
const encrypted = encryptConfigSecrets$1(config, vault);
|
|
18757
|
-
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
18758
|
-
await fsp5.mkdir(path39.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
18759
|
-
const agentsFile = path39.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
18760
|
-
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
18761
|
-
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
18762
|
-
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
18763
|
-
deps.renderer.writeInfo(`Project state lives in ${deps.paths.projectDir}`);
|
|
18764
|
-
deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
|
|
19770
|
+
);
|
|
18765
19771
|
return 0;
|
|
18766
19772
|
};
|
|
18767
19773
|
var AllowAllPermissionPolicy = class extends AutoApprovePermissionPolicy {
|
|
@@ -18777,7 +19783,7 @@ function parseToolsFlag(flags) {
|
|
|
18777
19783
|
);
|
|
18778
19784
|
return set.size > 0 ? set : null;
|
|
18779
19785
|
}
|
|
18780
|
-
function makeServeContext(cwd, projectRoot, signal) {
|
|
19786
|
+
function makeServeContext(cwd, projectRoot, signal, restrictFsToRoot = true) {
|
|
18781
19787
|
const provider = {
|
|
18782
19788
|
id: "mcp-serve",
|
|
18783
19789
|
capabilities: { maxContext: 0 },
|
|
@@ -18804,6 +19810,7 @@ function makeServeContext(cwd, projectRoot, signal) {
|
|
|
18804
19810
|
tokenCounter,
|
|
18805
19811
|
cwd,
|
|
18806
19812
|
projectRoot,
|
|
19813
|
+
allowOutsideProjectRoot: !restrictFsToRoot,
|
|
18807
19814
|
model: "mcp-serve",
|
|
18808
19815
|
tools: []
|
|
18809
19816
|
});
|
|
@@ -18829,7 +19836,12 @@ async function serveMcpStdio(deps) {
|
|
|
18829
19836
|
registry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
18830
19837
|
}
|
|
18831
19838
|
const controller = new AbortController();
|
|
18832
|
-
const ctx = makeServeContext(
|
|
19839
|
+
const ctx = makeServeContext(
|
|
19840
|
+
deps.cwd,
|
|
19841
|
+
deps.projectRoot,
|
|
19842
|
+
controller.signal,
|
|
19843
|
+
deps.config.tools?.restrictToProjectRoot ?? false
|
|
19844
|
+
);
|
|
18833
19845
|
const permissionPolicy = yolo ? new AllowAllPermissionPolicy() : new AutoApprovePermissionPolicy();
|
|
18834
19846
|
const executor = new ToolExecutor(registry, {
|
|
18835
19847
|
permissionPolicy,
|
|
@@ -19647,7 +20659,7 @@ var modeldiagCmd = async (args, deps) => {
|
|
|
19647
20659
|
` ${label} ${provColor(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.amber(fmtMs(latency).padEnd(8))} ${color.dim(`in${usage?.input ?? "?"}/out${usage?.output ?? "?"}`.padEnd(12))} ${firstLineClean}`
|
|
19648
20660
|
);
|
|
19649
20661
|
} catch (err) {
|
|
19650
|
-
const errMsg =
|
|
20662
|
+
const errMsg = toErrorMessage(err);
|
|
19651
20663
|
writeLine(
|
|
19652
20664
|
` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("FAILED")} ${color.dim(errMsg.slice(0, 40))}`
|
|
19653
20665
|
);
|
|
@@ -20119,7 +21131,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
20119
21131
|
return 0;
|
|
20120
21132
|
};
|
|
20121
21133
|
var projectsCmd = async (_args, deps) => {
|
|
20122
|
-
const projectsRoot =
|
|
21134
|
+
const projectsRoot = path4.join(deps.paths.globalRoot, "projects");
|
|
20123
21135
|
try {
|
|
20124
21136
|
const entries = await fsp5.readdir(projectsRoot);
|
|
20125
21137
|
if (entries.length === 0) {
|
|
@@ -20129,7 +21141,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
20129
21141
|
for (const hash of entries) {
|
|
20130
21142
|
try {
|
|
20131
21143
|
const meta = JSON.parse(
|
|
20132
|
-
await fsp5.readFile(
|
|
21144
|
+
await fsp5.readFile(path4.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
20133
21145
|
);
|
|
20134
21146
|
deps.renderer.write(
|
|
20135
21147
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -20578,7 +21590,7 @@ function findSessionId(args) {
|
|
|
20578
21590
|
var rewindCmd = async (args, deps) => {
|
|
20579
21591
|
const flags = parseRewindFlags(args);
|
|
20580
21592
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
20581
|
-
const sessionsDir =
|
|
21593
|
+
const sessionsDir = path4.join(wpaths.globalRoot, "sessions");
|
|
20582
21594
|
const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
|
|
20583
21595
|
let sessionId = findSessionId(args);
|
|
20584
21596
|
if (!sessionId) {
|
|
@@ -20691,7 +21703,7 @@ ${result.errors.length} error(s):
|
|
|
20691
21703
|
}
|
|
20692
21704
|
return 0;
|
|
20693
21705
|
} catch (err) {
|
|
20694
|
-
deps.renderer.writeError(
|
|
21706
|
+
deps.renderer.writeError(toErrorMessage(err));
|
|
20695
21707
|
return 1;
|
|
20696
21708
|
}
|
|
20697
21709
|
};
|
|
@@ -20723,7 +21735,7 @@ async function listFleetRuns(deps) {
|
|
|
20723
21735
|
}
|
|
20724
21736
|
const runs = [];
|
|
20725
21737
|
for (const id of entries) {
|
|
20726
|
-
const runDir =
|
|
21738
|
+
const runDir = path4.join(deps.paths.projectSessions, id);
|
|
20727
21739
|
let stat7;
|
|
20728
21740
|
try {
|
|
20729
21741
|
stat7 = await fsp5.stat(runDir);
|
|
@@ -20736,17 +21748,17 @@ async function listFleetRuns(deps) {
|
|
|
20736
21748
|
let subagentCount = 0;
|
|
20737
21749
|
let subagentsDir;
|
|
20738
21750
|
try {
|
|
20739
|
-
await fsp5.access(
|
|
21751
|
+
await fsp5.access(path4.join(runDir, "fleet.json"));
|
|
20740
21752
|
manifest = true;
|
|
20741
21753
|
} catch {
|
|
20742
21754
|
}
|
|
20743
21755
|
try {
|
|
20744
|
-
await fsp5.access(
|
|
21756
|
+
await fsp5.access(path4.join(runDir, "checkpoint.json"));
|
|
20745
21757
|
checkpoint = true;
|
|
20746
21758
|
} catch {
|
|
20747
21759
|
}
|
|
20748
21760
|
try {
|
|
20749
|
-
subagentsDir =
|
|
21761
|
+
subagentsDir = path4.join(runDir, "subagents");
|
|
20750
21762
|
const files = await fsp5.readdir(subagentsDir);
|
|
20751
21763
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
20752
21764
|
} catch {
|
|
@@ -20773,7 +21785,7 @@ async function listFleetRuns(deps) {
|
|
|
20773
21785
|
return 0;
|
|
20774
21786
|
}
|
|
20775
21787
|
async function showFleetRun(runId, deps) {
|
|
20776
|
-
const runDir =
|
|
21788
|
+
const runDir = path4.join(deps.paths.projectSessions, runId);
|
|
20777
21789
|
let stat7;
|
|
20778
21790
|
try {
|
|
20779
21791
|
stat7 = await fsp5.stat(runDir);
|
|
@@ -20790,7 +21802,7 @@ async function showFleetRun(runId, deps) {
|
|
|
20790
21802
|
deps.renderer.write(color.bold(`
|
|
20791
21803
|
Fleet Run: ${runId}
|
|
20792
21804
|
`) + "\n");
|
|
20793
|
-
const manifestPath =
|
|
21805
|
+
const manifestPath = path4.join(runDir, "fleet.json");
|
|
20794
21806
|
let manifestData = null;
|
|
20795
21807
|
try {
|
|
20796
21808
|
manifestData = await fsp5.readFile(manifestPath, "utf8");
|
|
@@ -20808,7 +21820,7 @@ Fleet Run: ${runId}
|
|
|
20808
21820
|
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
20809
21821
|
`);
|
|
20810
21822
|
}
|
|
20811
|
-
const checkpointPath =
|
|
21823
|
+
const checkpointPath = path4.join(runDir, "checkpoint.json");
|
|
20812
21824
|
let checkpointData = null;
|
|
20813
21825
|
try {
|
|
20814
21826
|
checkpointData = await fsp5.readFile(checkpointPath, "utf8");
|
|
@@ -20855,7 +21867,7 @@ Fleet Run: ${runId}
|
|
|
20855
21867
|
} catch {
|
|
20856
21868
|
}
|
|
20857
21869
|
}
|
|
20858
|
-
const subagentsDir =
|
|
21870
|
+
const subagentsDir = path4.join(runDir, "subagents");
|
|
20859
21871
|
let subagentFiles = [];
|
|
20860
21872
|
try {
|
|
20861
21873
|
subagentFiles = await fsp5.readdir(subagentsDir);
|
|
@@ -20867,7 +21879,7 @@ Fleet Run: ${runId}
|
|
|
20867
21879
|
Subagent transcripts (${subagentFiles.length}):
|
|
20868
21880
|
`);
|
|
20869
21881
|
for (const f of subagentFiles.sort()) {
|
|
20870
|
-
const filePath =
|
|
21882
|
+
const filePath = path4.join(subagentsDir, f);
|
|
20871
21883
|
let size;
|
|
20872
21884
|
try {
|
|
20873
21885
|
const s = await fsp5.stat(filePath);
|
|
@@ -20884,7 +21896,7 @@ Fleet Run: ${runId}
|
|
|
20884
21896
|
${color.dim("\u25CB")} No subagent transcripts
|
|
20885
21897
|
`);
|
|
20886
21898
|
}
|
|
20887
|
-
const sharedDir =
|
|
21899
|
+
const sharedDir = path4.join(runDir, "shared");
|
|
20888
21900
|
try {
|
|
20889
21901
|
const files = await fsp5.readdir(sharedDir);
|
|
20890
21902
|
deps.renderer.write(`
|
|
@@ -21125,8 +22137,8 @@ var helpCmd = async (_args, deps) => {
|
|
|
21125
22137
|
' wstack --eternal "<mission>" Launch eternal-autonomy loop against a goal \u2014 Ctrl+C to stop',
|
|
21126
22138
|
" wstack resume [<id>] Resume a session",
|
|
21127
22139
|
" wstack sessions List recent sessions",
|
|
21128
|
-
" wstack
|
|
21129
|
-
" wstack auth
|
|
22140
|
+
" wstack auth Interactive setup + key manager (add/edit/delete)",
|
|
22141
|
+
" wstack auth <provider> Add API key for a provider",
|
|
21130
22142
|
" wstack auth list Quick listing of saved providers and keys",
|
|
21131
22143
|
" wstack auth status <id> Detailed view of one provider",
|
|
21132
22144
|
" wstack auth remove <id> Delete a provider (asks for confirmation)",
|
|
@@ -21211,7 +22223,7 @@ function resolveBundledSkillsDir() {
|
|
|
21211
22223
|
try {
|
|
21212
22224
|
const req2 = createRequire(import.meta.url);
|
|
21213
22225
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
21214
|
-
return
|
|
22226
|
+
return path4.join(path4.dirname(corePkg), "skills");
|
|
21215
22227
|
} catch {
|
|
21216
22228
|
return void 0;
|
|
21217
22229
|
}
|
|
@@ -21230,7 +22242,7 @@ async function boot(argv) {
|
|
|
21230
22242
|
try {
|
|
21231
22243
|
bootResult = await bootConfig(flags);
|
|
21232
22244
|
} catch (err) {
|
|
21233
|
-
writeErr(`Config error: ${
|
|
22245
|
+
writeErr(`Config error: ${toErrorMessage(err)}
|
|
21234
22246
|
`);
|
|
21235
22247
|
return 2;
|
|
21236
22248
|
}
|
|
@@ -21283,7 +22295,7 @@ async function boot(argv) {
|
|
|
21283
22295
|
await modelsRegistry.refresh();
|
|
21284
22296
|
logger.info("models.dev catalog refreshed");
|
|
21285
22297
|
} catch (err) {
|
|
21286
|
-
const msg =
|
|
22298
|
+
const msg = toErrorMessage(err);
|
|
21287
22299
|
logger.warn(`models.dev refresh failed (${msg}); using cached catalog`);
|
|
21288
22300
|
}
|
|
21289
22301
|
}
|
|
@@ -21432,7 +22444,7 @@ async function boot(argv) {
|
|
|
21432
22444
|
const created = await registerProjectAtBoot({ projectRoot, cwd, wpaths });
|
|
21433
22445
|
if (created && isInteractiveTTY) {
|
|
21434
22446
|
renderer.write(
|
|
21435
|
-
color.dim(` \u2713 Registered "${
|
|
22447
|
+
color.dim(` \u2713 Registered "${path4.basename(projectRoot)}" in projects.json.
|
|
21436
22448
|
`)
|
|
21437
22449
|
);
|
|
21438
22450
|
}
|
|
@@ -21512,7 +22524,7 @@ async function boot(argv) {
|
|
|
21512
22524
|
} catch {
|
|
21513
22525
|
}
|
|
21514
22526
|
printLaunchHints(renderer, flags, {
|
|
21515
|
-
cursorFile:
|
|
22527
|
+
cursorFile: path4.join(wpaths.cacheDir, "hint-cursor")
|
|
21516
22528
|
});
|
|
21517
22529
|
} else {
|
|
21518
22530
|
const effectiveChoices = config.launch ? {
|
|
@@ -21550,7 +22562,7 @@ async function boot(argv) {
|
|
|
21550
22562
|
}
|
|
21551
22563
|
async function checkGitInCwd(opts) {
|
|
21552
22564
|
const { cwd, renderer, reader } = opts;
|
|
21553
|
-
const cwdGit =
|
|
22565
|
+
const cwdGit = path4.join(cwd, ".git");
|
|
21554
22566
|
let hasCwdGit = false;
|
|
21555
22567
|
try {
|
|
21556
22568
|
await fsp5.access(cwdGit);
|
|
@@ -21584,16 +22596,16 @@ async function checkGitInCwd(opts) {
|
|
|
21584
22596
|
hasCwdGit = true;
|
|
21585
22597
|
} catch (err) {
|
|
21586
22598
|
renderer.writeError(
|
|
21587
|
-
`git init failed: ${
|
|
22599
|
+
`git init failed: ${toErrorMessage(err)}
|
|
21588
22600
|
`
|
|
21589
22601
|
);
|
|
21590
22602
|
}
|
|
21591
22603
|
}
|
|
21592
22604
|
}
|
|
21593
|
-
const parentDir =
|
|
22605
|
+
const parentDir = path4.dirname(cwd);
|
|
21594
22606
|
if (parentDir !== cwd) {
|
|
21595
22607
|
try {
|
|
21596
|
-
await fsp5.access(
|
|
22608
|
+
await fsp5.access(path4.join(parentDir, ".git"));
|
|
21597
22609
|
renderer.write(
|
|
21598
22610
|
` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
|
|
21599
22611
|
`
|
|
@@ -21605,7 +22617,7 @@ async function checkGitInCwd(opts) {
|
|
|
21605
22617
|
async function registerProjectAtBoot(opts) {
|
|
21606
22618
|
const { projectRoot, cwd, wpaths } = opts;
|
|
21607
22619
|
const manifest = await loadManifest(wpaths.globalConfig);
|
|
21608
|
-
const existed = manifest.projects.some((p) =>
|
|
22620
|
+
const existed = manifest.projects.some((p) => path4.resolve(p.root) === path4.resolve(projectRoot));
|
|
21609
22621
|
await touchProjectInManifest({
|
|
21610
22622
|
projectRoot,
|
|
21611
22623
|
globalConfigPath: wpaths.globalConfig,
|
|
@@ -21613,9 +22625,26 @@ async function registerProjectAtBoot(opts) {
|
|
|
21613
22625
|
});
|
|
21614
22626
|
return !existed;
|
|
21615
22627
|
}
|
|
22628
|
+
function toolsForTier(tier, allTools) {
|
|
22629
|
+
switch (tier) {
|
|
22630
|
+
case "off":
|
|
22631
|
+
return allTools;
|
|
22632
|
+
case "minimal":
|
|
22633
|
+
case "light":
|
|
22634
|
+
return TIER1_TOOLS;
|
|
22635
|
+
case "medium":
|
|
22636
|
+
return [...TIER1_TOOLS, ...TIER2_TOOLS];
|
|
22637
|
+
case "aggressive": {
|
|
22638
|
+
const t2WithoutTask = TIER2_TOOLS.filter((t) => t.name !== "task");
|
|
22639
|
+
const t3WithoutSetCwd = TIER3_TOOLS.filter((t) => t.name !== "setWorkingDir");
|
|
22640
|
+
return [...TIER1_TOOLS, ...t2WithoutTask, ...t3WithoutSetCwd];
|
|
22641
|
+
}
|
|
22642
|
+
}
|
|
22643
|
+
}
|
|
21616
22644
|
function registerBuiltinTools(deps) {
|
|
22645
|
+
const tier = normalizeTokenSavingTier(deps.config.features.tokenSavingMode);
|
|
21617
22646
|
const allTools = builtinToolsPack.tools ?? [];
|
|
21618
|
-
const toolsToRegister =
|
|
22647
|
+
const toolsToRegister = toolsForTier(tier, allTools);
|
|
21619
22648
|
deps.toolRegistry.registerAllOrThrow([...toolsToRegister], builtinToolsPack.name);
|
|
21620
22649
|
deps.toolRegistry.registerDefault(
|
|
21621
22650
|
createContextManagerTool({ compactor: deps.compactor })
|
|
@@ -21806,7 +22835,7 @@ function resolveBundledSkillsDir2() {
|
|
|
21806
22835
|
try {
|
|
21807
22836
|
const req2 = createRequire(import.meta.url);
|
|
21808
22837
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
21809
|
-
return
|
|
22838
|
+
return path4.join(path4.dirname(corePkg), "skills");
|
|
21810
22839
|
} catch {
|
|
21811
22840
|
return void 0;
|
|
21812
22841
|
}
|
|
@@ -22294,7 +23323,7 @@ async function runRepl(opts) {
|
|
|
22294
23323
|
clientMailbox.registerClient({
|
|
22295
23324
|
clientId,
|
|
22296
23325
|
sessionId: replProjectRoot,
|
|
22297
|
-
name: `REPL [${
|
|
23326
|
+
name: `REPL [${path4.basename(replProjectRoot)}]`,
|
|
22298
23327
|
source: "repl",
|
|
22299
23328
|
pid: process.pid
|
|
22300
23329
|
}).then(() => {
|
|
@@ -22359,7 +23388,7 @@ async function runRepl(opts) {
|
|
|
22359
23388
|
}
|
|
22360
23389
|
} catch (err) {
|
|
22361
23390
|
opts.renderer.writeError(
|
|
22362
|
-
`[eternal] ${
|
|
23391
|
+
`[eternal] ${toErrorMessage(err)}`
|
|
22363
23392
|
);
|
|
22364
23393
|
}
|
|
22365
23394
|
await new Promise((resolve11) => setTimeout(resolve11, 250));
|
|
@@ -22435,7 +23464,7 @@ async function runRepl(opts) {
|
|
|
22435
23464
|
}
|
|
22436
23465
|
} catch (err) {
|
|
22437
23466
|
opts.renderer.writeError(
|
|
22438
|
-
`[parallel] ${
|
|
23467
|
+
`[parallel] ${toErrorMessage(err)}`
|
|
22439
23468
|
);
|
|
22440
23469
|
}
|
|
22441
23470
|
await new Promise((resolve11) => setTimeout(resolve11, 250));
|
|
@@ -22508,7 +23537,7 @@ ${lines.join("\n")}
|
|
|
22508
23537
|
if (res?.message) opts.renderer.write(`${res.message}
|
|
22509
23538
|
`);
|
|
22510
23539
|
} catch (err) {
|
|
22511
|
-
opts.renderer.writeError(
|
|
23540
|
+
opts.renderer.writeError(toErrorMessage(err));
|
|
22512
23541
|
}
|
|
22513
23542
|
continue;
|
|
22514
23543
|
}
|
|
@@ -22597,7 +23626,7 @@ ${color.dim(taskList2)}
|
|
|
22597
23626
|
}
|
|
22598
23627
|
}
|
|
22599
23628
|
} catch (err) {
|
|
22600
|
-
opts.renderer.writeError(
|
|
23629
|
+
opts.renderer.writeError(toErrorMessage(err));
|
|
22601
23630
|
}
|
|
22602
23631
|
continue;
|
|
22603
23632
|
}
|
|
@@ -22786,7 +23815,7 @@ ${color.dim(
|
|
|
22786
23815
|
}
|
|
22787
23816
|
} catch (err) {
|
|
22788
23817
|
opts.renderer.writeError(
|
|
22789
|
-
`[autonomy] ${
|
|
23818
|
+
`[autonomy] ${toErrorMessage(err)}`
|
|
22790
23819
|
);
|
|
22791
23820
|
} finally {
|
|
22792
23821
|
activeCtrl = void 0;
|
|
@@ -22857,7 +23886,7 @@ ${color.dim(lines)}
|
|
|
22857
23886
|
}
|
|
22858
23887
|
}
|
|
22859
23888
|
} catch (err) {
|
|
22860
|
-
opts.renderer.writeError(
|
|
23889
|
+
opts.renderer.writeError(toErrorMessage(err));
|
|
22861
23890
|
} finally {
|
|
22862
23891
|
activeCtrl = void 0;
|
|
22863
23892
|
}
|
|
@@ -22884,7 +23913,7 @@ async function pasteClipboardImage(builder, opts) {
|
|
|
22884
23913
|
`));
|
|
22885
23914
|
} catch (err) {
|
|
22886
23915
|
opts.renderer.writeError(
|
|
22887
|
-
`Clipboard image error: ${
|
|
23916
|
+
`Clipboard image error: ${toErrorMessage(err)}`
|
|
22888
23917
|
);
|
|
22889
23918
|
}
|
|
22890
23919
|
}
|
|
@@ -22922,7 +23951,7 @@ async function renderGoalBanner(opts) {
|
|
|
22922
23951
|
await opts.slashRegistry.dispatch("/autonomy eternal", opts.agent.ctx);
|
|
22923
23952
|
} catch (err) {
|
|
22924
23953
|
opts.renderer.writeError(
|
|
22925
|
-
`Auto-resume failed: ${
|
|
23954
|
+
`Auto-resume failed: ${toErrorMessage(err)}`
|
|
22926
23955
|
);
|
|
22927
23956
|
}
|
|
22928
23957
|
} else {
|
|
@@ -23111,6 +24140,7 @@ function printBanner(renderer, projectName) {
|
|
|
23111
24140
|
}
|
|
23112
24141
|
|
|
23113
24142
|
// src/execution.ts
|
|
24143
|
+
init_provider_config_utils();
|
|
23114
24144
|
init_utils();
|
|
23115
24145
|
async function execute(deps) {
|
|
23116
24146
|
const {
|
|
@@ -23134,6 +24164,7 @@ async function execute(deps) {
|
|
|
23134
24164
|
effectiveMaxContext,
|
|
23135
24165
|
queueStore,
|
|
23136
24166
|
context,
|
|
24167
|
+
mailbox,
|
|
23137
24168
|
stats,
|
|
23138
24169
|
detachTodosCheckpoint,
|
|
23139
24170
|
savedProviderCfg,
|
|
@@ -23223,8 +24254,8 @@ async function execute(deps) {
|
|
|
23223
24254
|
timeoutMs: 3e5
|
|
23224
24255
|
};
|
|
23225
24256
|
const subagentId = await dir.spawn(cfg);
|
|
23226
|
-
const { randomUUID:
|
|
23227
|
-
const taskId =
|
|
24257
|
+
const { randomUUID: randomUUID7 } = await import('crypto');
|
|
24258
|
+
const taskId = randomUUID7();
|
|
23228
24259
|
await dir.assign({
|
|
23229
24260
|
id: taskId,
|
|
23230
24261
|
description: taskDesc,
|
|
@@ -23329,8 +24360,8 @@ async function execute(deps) {
|
|
|
23329
24360
|
result = await agent.run(query, { signal: ctrl.signal });
|
|
23330
24361
|
} finally {
|
|
23331
24362
|
process.off("SIGINT", onSigint);
|
|
23332
|
-
const { getProcessRegistry:
|
|
23333
|
-
|
|
24363
|
+
const { getProcessRegistry: getProcessRegistry3 } = await import('@wrongstack/tools');
|
|
24364
|
+
getProcessRegistry3().killAll();
|
|
23334
24365
|
}
|
|
23335
24366
|
const after = tokenCounter.total();
|
|
23336
24367
|
const costAfter = tokenCounter.estimateCost().total;
|
|
@@ -23389,7 +24420,7 @@ async function execute(deps) {
|
|
|
23389
24420
|
const { runTui } = await import('@wrongstack/tui');
|
|
23390
24421
|
renderer.setSilent(true);
|
|
23391
24422
|
const banneredFamily = savedProviderCfg?.family ?? resolvedProvider?.family;
|
|
23392
|
-
const banneredKey = savedProviderCfg
|
|
24423
|
+
const banneredKey = (savedProviderCfg ? resolveActiveApiKey(savedProviderCfg) : void 0) ?? config.apiKey ?? (resolvedProvider?.envVars ?? savedProviderCfg?.envVars ?? []).map((v) => process.env[v]).find((v) => !!v);
|
|
23393
24424
|
const banneredKeyTail = banneredKey && banneredKey.length >= 3 ? banneredKey.slice(-3) : void 0;
|
|
23394
24425
|
const autoPhaseHandlers = /* @__PURE__ */ new Map();
|
|
23395
24426
|
const subscribeAutoPhase = (handler) => {
|
|
@@ -23437,6 +24468,80 @@ async function execute(deps) {
|
|
|
23437
24468
|
};
|
|
23438
24469
|
const PROJECT_SWITCH_EXIT_CODE = 42;
|
|
23439
24470
|
let pendingProjectSwitch = null;
|
|
24471
|
+
const coordinatorEvents = /* @__PURE__ */ new Set();
|
|
24472
|
+
let autonomousCoordinator = null;
|
|
24473
|
+
const onDirectorReady = (dir) => {
|
|
24474
|
+
if (autonomousCoordinator) return;
|
|
24475
|
+
const transcript = context.session.transcriptPath;
|
|
24476
|
+
const sessionDir = transcript ? path4.dirname(transcript) : wpaths.projectDir;
|
|
24477
|
+
const llmProvider = {
|
|
24478
|
+
decide: async (prompt) => {
|
|
24479
|
+
const sysPrompt = [
|
|
24480
|
+
{
|
|
24481
|
+
type: "text",
|
|
24482
|
+
text: 'You are the autonomous brain of a multi-agent coordination system. Pick the best option for the decision described and reply with JSON: {"optionId":"<id>","rationale":"<short why>"}.'
|
|
24483
|
+
}
|
|
24484
|
+
];
|
|
24485
|
+
const userPrompt = {
|
|
24486
|
+
type: "text",
|
|
24487
|
+
text: `Decision: ${prompt.question}
|
|
24488
|
+
|
|
24489
|
+
Context: ${prompt.context}
|
|
24490
|
+
|
|
24491
|
+
Options:
|
|
24492
|
+
${prompt.options.map((o, i) => ` ${i + 1}. [${o.id}] ${o.label}${o.consequence ? ` \u2014 ${o.consequence}` : ""}`).join("\n")}
|
|
24493
|
+
|
|
24494
|
+
Risk: ${prompt.risk}
|
|
24495
|
+
|
|
24496
|
+
Reply with ONLY the JSON object.`
|
|
24497
|
+
};
|
|
24498
|
+
const resp = await context.provider.complete(
|
|
24499
|
+
{
|
|
24500
|
+
model: context.model,
|
|
24501
|
+
system: sysPrompt,
|
|
24502
|
+
messages: [
|
|
24503
|
+
{
|
|
24504
|
+
role: "user",
|
|
24505
|
+
content: [userPrompt]
|
|
24506
|
+
}
|
|
24507
|
+
],
|
|
24508
|
+
maxTokens: 1024,
|
|
24509
|
+
temperature: 0
|
|
24510
|
+
},
|
|
24511
|
+
{ signal: context.signal }
|
|
24512
|
+
);
|
|
24513
|
+
const text = resp.content.filter((b) => b.type === "text").map((b) => b.text).join("\n").trim();
|
|
24514
|
+
const cleaned = text.replace(/^```(?:json)?\s*/i, "").replace(/```$/, "").trim();
|
|
24515
|
+
try {
|
|
24516
|
+
const parsed = JSON.parse(cleaned);
|
|
24517
|
+
const optId = parsed.optionId ?? prompt.options[0]?.id ?? "";
|
|
24518
|
+
return { optionId: optId, rationale: parsed.rationale ?? "" };
|
|
24519
|
+
} catch {
|
|
24520
|
+
return { optionId: prompt.options[0]?.id ?? "", rationale: text };
|
|
24521
|
+
}
|
|
24522
|
+
}
|
|
24523
|
+
};
|
|
24524
|
+
autonomousCoordinator = new AutonomousCoordinator({
|
|
24525
|
+
sessionDir,
|
|
24526
|
+
fleet: dir.fleet,
|
|
24527
|
+
mailbox,
|
|
24528
|
+
selfAgentId: `leader@${context.session.id ?? "unknown"}`,
|
|
24529
|
+
selfAgentName: "Leader",
|
|
24530
|
+
llmProvider,
|
|
24531
|
+
onCoordinatorEvent: (event) => {
|
|
24532
|
+
for (const fn of coordinatorEvents) fn(event);
|
|
24533
|
+
}
|
|
24534
|
+
});
|
|
24535
|
+
deps.onCoordinatorStop = () => autonomousCoordinator?.stop();
|
|
24536
|
+
};
|
|
24537
|
+
if (director) onDirectorReady(director);
|
|
24538
|
+
const offDirectorSpawned = events.onPattern("subagent.spawned", () => {
|
|
24539
|
+
const dir = director;
|
|
24540
|
+
if (dir) {
|
|
24541
|
+
offDirectorSpawned();
|
|
24542
|
+
onDirectorReady(dir);
|
|
24543
|
+
}
|
|
24544
|
+
});
|
|
23440
24545
|
try {
|
|
23441
24546
|
code = await runTui({
|
|
23442
24547
|
agent,
|
|
@@ -23520,13 +24625,15 @@ async function execute(deps) {
|
|
|
23520
24625
|
featureMemory: cfg.features?.memory !== false,
|
|
23521
24626
|
featureSkills: cfg.features?.skills !== false,
|
|
23522
24627
|
featureModelsRegistry: cfg.features?.modelsRegistry !== false,
|
|
23523
|
-
featureTokenSaving: cfg.features?.tokenSavingMode
|
|
24628
|
+
featureTokenSaving: normalizeTokenSavingTier(cfg.features?.tokenSavingMode),
|
|
24629
|
+
allowOutsideProjectRoot: cfg.features?.allowOutsideProjectRoot ?? true,
|
|
23524
24630
|
contextAutoCompact: cfg.context?.autoCompact !== false,
|
|
23525
24631
|
contextStrategy: cfg.context?.strategy ?? "hybrid",
|
|
23526
24632
|
logLevel: cfg.log?.level ?? "info",
|
|
23527
24633
|
auditLevel: cfg.session?.auditLevel ?? "standard",
|
|
23528
24634
|
indexOnStart: cfg.indexing?.onSessionStart !== false,
|
|
23529
24635
|
maxIterations: cfg.tools?.maxIterations ?? 500,
|
|
24636
|
+
restrictFsToRoot: cfg.tools?.restrictToProjectRoot ?? false,
|
|
23530
24637
|
autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
|
|
23531
24638
|
debugStream: cfg.debugStream ?? false,
|
|
23532
24639
|
configScope: cfg.configScope ?? "global",
|
|
@@ -23534,7 +24641,9 @@ async function execute(deps) {
|
|
|
23534
24641
|
enhanceEnabled: cfg.autonomy?.enhance ?? true,
|
|
23535
24642
|
enhanceLanguage: cfg.autonomy?.enhanceLanguage === "english" ? "english" : "original",
|
|
23536
24643
|
mouseMode: autonomy?.mouseMode ?? false,
|
|
23537
|
-
autonomyNextPrompt: cfg.autonomy?.autonomyNextPrompt ?? "auto {{suggestion}}"
|
|
24644
|
+
autonomyNextPrompt: cfg.autonomy?.autonomyNextPrompt ?? "auto {{suggestion}}",
|
|
24645
|
+
breakerEnabled: cfg.circuitBreaker?.enabled === true,
|
|
24646
|
+
breakerAutoKillResetMs: cfg.circuitBreaker?.autoKillResetMs ?? 6e4
|
|
23538
24647
|
};
|
|
23539
24648
|
},
|
|
23540
24649
|
async saveSettings(s) {
|
|
@@ -23559,9 +24668,11 @@ async function execute(deps) {
|
|
|
23559
24668
|
if (s.enhanceEnabled !== void 0) a["enhance"] = s.enhanceEnabled;
|
|
23560
24669
|
if (s.enhanceLanguage !== void 0) a["enhanceLanguage"] = s.enhanceLanguage;
|
|
23561
24670
|
if (s.autonomyNextPrompt !== void 0) a["autonomyNextPrompt"] = s.autonomyNextPrompt;
|
|
24671
|
+
if (s.autoProceedMaxIterations !== void 0)
|
|
24672
|
+
a["autoProceedMaxIterations"] = s.autoProceedMaxIterations;
|
|
23562
24673
|
}
|
|
23563
24674
|
);
|
|
23564
|
-
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
|
|
24675
|
+
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
|
|
23565
24676
|
const configScope = s.configScope ?? configStore.get().configScope ?? "global";
|
|
23566
24677
|
const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
|
|
23567
24678
|
let raw;
|
|
@@ -23578,7 +24689,7 @@ async function execute(deps) {
|
|
|
23578
24689
|
if (s.nextPrediction !== void 0) {
|
|
23579
24690
|
decrypted.nextPrediction = s.nextPrediction;
|
|
23580
24691
|
}
|
|
23581
|
-
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0) {
|
|
24692
|
+
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0) {
|
|
23582
24693
|
const feats = decrypted.features ?? {};
|
|
23583
24694
|
if (s.featureMcp !== void 0) feats.mcp = s.featureMcp;
|
|
23584
24695
|
if (s.featurePlugins !== void 0) feats.plugins = s.featurePlugins;
|
|
@@ -23588,6 +24699,8 @@ async function execute(deps) {
|
|
|
23588
24699
|
feats.modelsRegistry = s.featureModelsRegistry;
|
|
23589
24700
|
if (s.featureTokenSaving !== void 0)
|
|
23590
24701
|
feats.tokenSavingMode = s.featureTokenSaving;
|
|
24702
|
+
if (s.allowOutsideProjectRoot !== void 0)
|
|
24703
|
+
feats.allowOutsideProjectRoot = s.allowOutsideProjectRoot;
|
|
23591
24704
|
decrypted.features = feats;
|
|
23592
24705
|
}
|
|
23593
24706
|
if (s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0) {
|
|
@@ -23611,11 +24724,18 @@ async function execute(deps) {
|
|
|
23611
24724
|
idx.onSessionStart = s.indexOnStart;
|
|
23612
24725
|
decrypted.indexing = idx;
|
|
23613
24726
|
}
|
|
23614
|
-
if (s.maxIterations !== void 0) {
|
|
24727
|
+
if (s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0) {
|
|
23615
24728
|
const tools = decrypted.tools ?? {};
|
|
23616
|
-
tools.maxIterations = s.maxIterations;
|
|
24729
|
+
if (s.maxIterations !== void 0) tools.maxIterations = s.maxIterations;
|
|
24730
|
+
if (s.restrictFsToRoot !== void 0)
|
|
24731
|
+
tools.restrictToProjectRoot = s.restrictFsToRoot;
|
|
23617
24732
|
decrypted.tools = tools;
|
|
23618
24733
|
}
|
|
24734
|
+
if (s.restrictFsToRoot !== void 0) {
|
|
24735
|
+
const features = decrypted.features ?? {};
|
|
24736
|
+
features.allowOutsideProjectRoot = !s.restrictFsToRoot;
|
|
24737
|
+
decrypted.features = features;
|
|
24738
|
+
}
|
|
23619
24739
|
if (s.debugStream !== void 0) {
|
|
23620
24740
|
decrypted.debugStream = s.debugStream;
|
|
23621
24741
|
const { setDebugStreamEnabled } = await import('@wrongstack/providers');
|
|
@@ -23637,7 +24757,7 @@ async function execute(deps) {
|
|
|
23637
24757
|
const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
|
|
23638
24758
|
const encrypted = encryptConfigSecrets(toWrite, noOpVault);
|
|
23639
24759
|
if (targetPath !== wpaths.globalConfig) {
|
|
23640
|
-
await fsp5.mkdir(
|
|
24760
|
+
await fsp5.mkdir(path4.dirname(targetPath), { recursive: true });
|
|
23641
24761
|
}
|
|
23642
24762
|
await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
23643
24763
|
configStore.update({
|
|
@@ -23647,7 +24767,7 @@ async function execute(deps) {
|
|
|
23647
24767
|
...s.logLevel !== void 0 ? { log: decrypted.log } : {},
|
|
23648
24768
|
...s.auditLevel !== void 0 ? { session: decrypted.session } : {},
|
|
23649
24769
|
...s.indexOnStart !== void 0 ? { indexing: decrypted.indexing } : {},
|
|
23650
|
-
...s.maxIterations !== void 0 ? { tools: decrypted.tools } : {},
|
|
24770
|
+
...s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 ? { tools: decrypted.tools } : {},
|
|
23651
24771
|
...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
|
|
23652
24772
|
...s.configScope !== void 0 ? { configScope: s.configScope } : {},
|
|
23653
24773
|
...s.enhanceDelayMs !== void 0 ? {
|
|
@@ -23673,6 +24793,7 @@ async function execute(deps) {
|
|
|
23673
24793
|
if (s.streamFleet !== void 0) {
|
|
23674
24794
|
fleetStreamController?.setEnabled(s.streamFleet);
|
|
23675
24795
|
}
|
|
24796
|
+
deps.applyLiveSettings?.(s);
|
|
23676
24797
|
return null;
|
|
23677
24798
|
} catch (err) {
|
|
23678
24799
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -23697,6 +24818,29 @@ async function execute(deps) {
|
|
|
23697
24818
|
confirmExit: config.autonomy?.["confirmExit"] ?? true,
|
|
23698
24819
|
director,
|
|
23699
24820
|
fleetRoster,
|
|
24821
|
+
// ── AutonomousCoordinator: project-level multi-session coordination ─────────
|
|
24822
|
+
// The coordinator tracks goals, tasks, knowledge, and consensus across all
|
|
24823
|
+
// active sessions in the same project. It runs independently of the leader
|
|
24824
|
+
// agent and is accessible to any session in the project via the GlobalMailbox.
|
|
24825
|
+
getAutonomousCoordinator: () => autonomousCoordinator,
|
|
24826
|
+
subscribeCoordinatorEvents: (fn) => {
|
|
24827
|
+
coordinatorEvents.add(fn);
|
|
24828
|
+
return () => {
|
|
24829
|
+
coordinatorEvents.delete(fn);
|
|
24830
|
+
};
|
|
24831
|
+
},
|
|
24832
|
+
onCoordinatorStart: (goal) => {
|
|
24833
|
+
if (!autonomousCoordinator) {
|
|
24834
|
+
console.error("[coordinator] not ready \u2014 no director yet (spawn a subagent first)");
|
|
24835
|
+
return;
|
|
24836
|
+
}
|
|
24837
|
+
autonomousCoordinator.run({ goal: goal ?? "" }).catch((err) => {
|
|
24838
|
+
console.error("[coordinator] run() failed:", err);
|
|
24839
|
+
});
|
|
24840
|
+
},
|
|
24841
|
+
onCoordinatorStop: () => {
|
|
24842
|
+
autonomousCoordinator?.stop();
|
|
24843
|
+
},
|
|
23700
24844
|
// /clear: signal the TUI to wipe entries and reset fleet/leader stats
|
|
23701
24845
|
// AND bump the context chip version — so the display reflects a
|
|
23702
24846
|
// completely fresh session after the backend has been cleared.
|
|
@@ -23714,7 +24858,7 @@ async function execute(deps) {
|
|
|
23714
24858
|
agentsMonitorController,
|
|
23715
24859
|
getLiveSessions: async () => {
|
|
23716
24860
|
const { SessionRegistry } = await import('@wrongstack/core');
|
|
23717
|
-
const globalRoot =
|
|
24861
|
+
const globalRoot = path4.dirname(wpaths.globalConfig);
|
|
23718
24862
|
const registry = new SessionRegistry(globalRoot);
|
|
23719
24863
|
const sessions = await registry.list();
|
|
23720
24864
|
return sessions.filter((s) => s.status !== "stale").map((s) => ({
|
|
@@ -23843,7 +24987,7 @@ async function execute(deps) {
|
|
|
23843
24987
|
if (!sessionStore) return null;
|
|
23844
24988
|
try {
|
|
23845
24989
|
const { SessionRegistry } = await import('@wrongstack/core');
|
|
23846
|
-
const registry = new SessionRegistry(
|
|
24990
|
+
const registry = new SessionRegistry(path4.dirname(wpaths.globalConfig));
|
|
23847
24991
|
const live = (await registry.list()).find(
|
|
23848
24992
|
(s) => s.sessionId === sessionId && s.status !== "stale" && s.pid !== process.pid
|
|
23849
24993
|
);
|
|
@@ -23981,7 +25125,7 @@ async function execute(deps) {
|
|
|
23981
25125
|
if (slug === "new-session") {
|
|
23982
25126
|
pendingProjectSwitch = {
|
|
23983
25127
|
root: projectRoot,
|
|
23984
|
-
name:
|
|
25128
|
+
name: path4.basename(projectRoot) || projectRoot
|
|
23985
25129
|
};
|
|
23986
25130
|
}
|
|
23987
25131
|
return;
|
|
@@ -24035,7 +25179,7 @@ ${parts.join("\n")}
|
|
|
24035
25179
|
},
|
|
24036
25180
|
// `wrongstack quick` sets flags.quick — open the F3 agents monitor by default.
|
|
24037
25181
|
initialAgentsMonitorOpen: !!flags.quick,
|
|
24038
|
-
tokenSavingMode: config.features.tokenSavingMode,
|
|
25182
|
+
tokenSavingMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off",
|
|
24039
25183
|
toolCount: agent.tools.list().length
|
|
24040
25184
|
});
|
|
24041
25185
|
if (code === PROJECT_SWITCH_EXIT_CODE && pendingProjectSwitch) {
|
|
@@ -24047,8 +25191,8 @@ ${parts.join("\n")}
|
|
|
24047
25191
|
try {
|
|
24048
25192
|
const req2 = createRequire8(import.meta.url);
|
|
24049
25193
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
24050
|
-
const pkgDir =
|
|
24051
|
-
cliPath =
|
|
25194
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
25195
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
24052
25196
|
await fsp5.access(cliPath);
|
|
24053
25197
|
} catch {
|
|
24054
25198
|
cliPath = process.argv[1] ?? "";
|
|
@@ -24080,6 +25224,7 @@ ${parts.join("\n")}
|
|
|
24080
25224
|
}
|
|
24081
25225
|
} finally {
|
|
24082
25226
|
renderer.setSilent(false);
|
|
25227
|
+
offDirectorSpawned();
|
|
24083
25228
|
}
|
|
24084
25229
|
} else if (flags.webui) {
|
|
24085
25230
|
agent.disableInteractiveConfirmation();
|
|
@@ -24108,6 +25253,23 @@ ${parts.join("\n")}
|
|
|
24108
25253
|
modeStore,
|
|
24109
25254
|
modeId,
|
|
24110
25255
|
needsSetup,
|
|
25256
|
+
// Print the "open this" banner only once the server is actually
|
|
25257
|
+
// listening, using the RESOLVED ports. The requested port
|
|
25258
|
+
// (flags.port) auto-advances past busy ports inside runWebUI, so a
|
|
25259
|
+
// banner printed up-front with flags.port lies whenever 3456/3457 are
|
|
25260
|
+
// taken (a second instance, leftover sockets). Bind is 127.0.0.1-only,
|
|
25261
|
+
// so the host must be the literal IPv4 loopback — `localhost` resolves
|
|
25262
|
+
// to `::1` first on Windows and never reaches the server.
|
|
25263
|
+
onListening: ({ httpPort: boundHttpPort }) => {
|
|
25264
|
+
renderer.writeInfo(
|
|
25265
|
+
color.green(
|
|
25266
|
+
` \u2726 WebUI running \u2192 ${color.bold(`http://127.0.0.1:${boundHttpPort}`)}`
|
|
25267
|
+
)
|
|
25268
|
+
);
|
|
25269
|
+
renderer.writeInfo(
|
|
25270
|
+
color.dim(" Press Ctrl+C in this terminal to stop the WebUI server.\n")
|
|
25271
|
+
);
|
|
25272
|
+
},
|
|
24111
25273
|
// Make autonomy.switch from the browser flip the CLI's real
|
|
24112
25274
|
// autonomy mode — context.meta alone never reaches the run loop.
|
|
24113
25275
|
onAutonomySwitch: (mode) => {
|
|
@@ -24116,12 +25278,6 @@ ${parts.join("\n")}
|
|
|
24116
25278
|
}
|
|
24117
25279
|
}
|
|
24118
25280
|
});
|
|
24119
|
-
renderer.writeInfo(
|
|
24120
|
-
color.green(
|
|
24121
|
-
` \u2726 WebUI running \u2192 ${color.bold(`http://localhost:${Number.parseInt(String(flags.port ?? "3457"), 10)}`)}`
|
|
24122
|
-
)
|
|
24123
|
-
);
|
|
24124
|
-
renderer.writeInfo(color.dim(" Press Ctrl+C in this terminal to stop the WebUI server.\n"));
|
|
24125
25281
|
const webuiExit = new Promise((resolve11) => {
|
|
24126
25282
|
const onSigint = () => {
|
|
24127
25283
|
renderer.setSilent(false);
|
|
@@ -24156,7 +25312,7 @@ ${parts.join("\n")}
|
|
|
24156
25312
|
supportsVision,
|
|
24157
25313
|
attachments,
|
|
24158
25314
|
effectiveMaxContext,
|
|
24159
|
-
projectName:
|
|
25315
|
+
projectName: path4.basename(projectRoot) || void 0,
|
|
24160
25316
|
getAutonomy,
|
|
24161
25317
|
onAutonomy,
|
|
24162
25318
|
getNextPredict,
|
|
@@ -24194,6 +25350,7 @@ ${parts.join("\n")}
|
|
|
24194
25350
|
}
|
|
24195
25351
|
} finally {
|
|
24196
25352
|
fleetStatusLine?.stop();
|
|
25353
|
+
deps.onCoordinatorStop?.();
|
|
24197
25354
|
try {
|
|
24198
25355
|
stats.render(renderer);
|
|
24199
25356
|
} catch (_err) {
|
|
@@ -24378,7 +25535,7 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
24378
25535
|
doneCondition: { type: "all_tasks_done" },
|
|
24379
25536
|
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
24380
25537
|
};
|
|
24381
|
-
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ?
|
|
25538
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path4.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
24382
25539
|
this.director = new Director({
|
|
24383
25540
|
config: coordinatorConfig,
|
|
24384
25541
|
manifestPath: this.opts.manifestPath,
|
|
@@ -24577,6 +25734,8 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
24577
25734
|
tokenCounter: this.deps.tokenCounter,
|
|
24578
25735
|
cwd: subCwd,
|
|
24579
25736
|
projectRoot: this.deps.projectRoot,
|
|
25737
|
+
// Subagents inherit the leader's filesystem-access scope.
|
|
25738
|
+
allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
|
|
24580
25739
|
model: effModel,
|
|
24581
25740
|
tools: this.filterTools(tools),
|
|
24582
25741
|
// Distinct mailbox identity: without these, every subagent fell back
|
|
@@ -24935,16 +26094,16 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
24935
26094
|
if (this.director) return this.director;
|
|
24936
26095
|
this.opts.directorMode = true;
|
|
24937
26096
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
24938
|
-
this.opts.manifestPath =
|
|
26097
|
+
this.opts.manifestPath = path4.join(this.opts.fleetRoot, "fleet.json");
|
|
24939
26098
|
}
|
|
24940
26099
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
24941
|
-
this.opts.sharedScratchpadPath =
|
|
26100
|
+
this.opts.sharedScratchpadPath = path4.join(this.opts.fleetRoot, "shared");
|
|
24942
26101
|
}
|
|
24943
26102
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
24944
|
-
this.opts.sessionsRoot =
|
|
26103
|
+
this.opts.sessionsRoot = path4.join(this.opts.fleetRoot, "subagents");
|
|
24945
26104
|
}
|
|
24946
26105
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
24947
|
-
this.opts.stateCheckpointPath =
|
|
26106
|
+
this.opts.stateCheckpointPath = path4.join(this.opts.fleetRoot, "director-state.json");
|
|
24948
26107
|
}
|
|
24949
26108
|
await this.ensureDirector();
|
|
24950
26109
|
return this.director ?? null;
|
|
@@ -25073,11 +26232,11 @@ var SessionStats = class {
|
|
|
25073
26232
|
if (tool.name === "bash") this.bashCommands++;
|
|
25074
26233
|
else if (tool.name === "fetch") this.fetches++;
|
|
25075
26234
|
if (!tool.ok) return;
|
|
25076
|
-
const
|
|
25077
|
-
if (tool.name === "read" &&
|
|
25078
|
-
else if (tool.name === "edit" &&
|
|
25079
|
-
else if (tool.name === "write" &&
|
|
25080
|
-
this.writtenPaths.add(
|
|
26235
|
+
const path39 = typeof input?.path === "string" ? input.path : void 0;
|
|
26236
|
+
if (tool.name === "read" && path39) this.readPaths.add(path39);
|
|
26237
|
+
else if (tool.name === "edit" && path39) this.editedPaths.add(path39);
|
|
26238
|
+
else if (tool.name === "write" && path39) {
|
|
26239
|
+
this.writtenPaths.add(path39);
|
|
25081
26240
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
25082
26241
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
25083
26242
|
}
|
|
@@ -25389,7 +26548,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
25389
26548
|
if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
|
|
25390
26549
|
const fp = payload.toolUse.input?.file_path;
|
|
25391
26550
|
if (typeof fp === "string" && fp.length > 0) {
|
|
25392
|
-
const abs =
|
|
26551
|
+
const abs = path4.resolve(payload.ctx.cwd, fp);
|
|
25393
26552
|
if (isIndexableFile(abs)) {
|
|
25394
26553
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
25395
26554
|
}
|
|
@@ -25408,7 +26567,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
25408
26567
|
if (!filename) return;
|
|
25409
26568
|
const rel = filename.toString();
|
|
25410
26569
|
if (isIgnored(rel)) return;
|
|
25411
|
-
const abs =
|
|
26570
|
+
const abs = path4.resolve(projectRoot, rel);
|
|
25412
26571
|
if (!isIndexableFile(abs)) return;
|
|
25413
26572
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
25414
26573
|
});
|
|
@@ -25462,7 +26621,7 @@ function setupMetrics(params) {
|
|
|
25462
26621
|
const dumpMetrics = () => {
|
|
25463
26622
|
if (!metricsSink) return;
|
|
25464
26623
|
try {
|
|
25465
|
-
const out =
|
|
26624
|
+
const out = path4.join(wpaths.projectSessions, "metrics.json");
|
|
25466
26625
|
const snap = metricsSink.snapshot();
|
|
25467
26626
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
25468
26627
|
} catch {
|
|
@@ -25486,7 +26645,7 @@ function setupMetrics(params) {
|
|
|
25486
26645
|
});
|
|
25487
26646
|
} catch (err) {
|
|
25488
26647
|
logger.warn(
|
|
25489
|
-
`metrics endpoint failed to start: ${
|
|
26648
|
+
`metrics endpoint failed to start: ${toErrorMessage(err)}`
|
|
25490
26649
|
);
|
|
25491
26650
|
}
|
|
25492
26651
|
}
|
|
@@ -25542,9 +26701,11 @@ async function setupCompaction(params) {
|
|
|
25542
26701
|
context.meta["contextWindowMode"] = initialPolicy.id;
|
|
25543
26702
|
context.meta["contextWindowPolicy"] = initialPolicy;
|
|
25544
26703
|
let autoCompactor;
|
|
25545
|
-
|
|
26704
|
+
let resolvedBridge;
|
|
26705
|
+
if (effectiveMaxContext > 0) {
|
|
25546
26706
|
const auditLevel = resolveAuditLevel(fullConfig ?? config);
|
|
25547
26707
|
const sessionBridge = providedBridge ?? createSessionEventBridge(sessionWriter, auditLevel);
|
|
26708
|
+
resolvedBridge = sessionBridge;
|
|
25548
26709
|
autoCompactor = new AutoCompactionMiddleware(
|
|
25549
26710
|
compactor,
|
|
25550
26711
|
effectiveMaxContext,
|
|
@@ -25568,9 +26729,10 @@ async function setupCompaction(params) {
|
|
|
25568
26729
|
sessionBridge
|
|
25569
26730
|
}
|
|
25570
26731
|
);
|
|
26732
|
+
autoCompactor.setEnabled(config.context.autoCompact !== false);
|
|
25571
26733
|
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
25572
26734
|
}
|
|
25573
|
-
return { effectiveMaxContext, autoCompactor };
|
|
26735
|
+
return { effectiveMaxContext, autoCompactor, sessionBridge: resolvedBridge };
|
|
25574
26736
|
}
|
|
25575
26737
|
function createAgent(params) {
|
|
25576
26738
|
const secretScrubber = params.container.resolve(TOKENS.SecretScrubber);
|
|
@@ -25676,7 +26838,7 @@ async function setupSession(params) {
|
|
|
25676
26838
|
`Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${restoredToolCalls.length} tool executions, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`
|
|
25677
26839
|
);
|
|
25678
26840
|
} catch (err) {
|
|
25679
|
-
renderer.writeError(`Resume failed: ${
|
|
26841
|
+
renderer.writeError(`Resume failed: ${toErrorMessage(err)}`);
|
|
25680
26842
|
throw Object.assign(new Error("RESUME_FAILED"), { exitCode: 2 });
|
|
25681
26843
|
}
|
|
25682
26844
|
} else {
|
|
@@ -25699,7 +26861,7 @@ async function setupSession(params) {
|
|
|
25699
26861
|
);
|
|
25700
26862
|
});
|
|
25701
26863
|
const attachments = new DefaultAttachmentStore({
|
|
25702
|
-
spoolDir:
|
|
26864
|
+
spoolDir: path4.join(wpaths.projectSessions, session?.id, "attachments")
|
|
25703
26865
|
});
|
|
25704
26866
|
const ctxSignal = new AbortController().signal;
|
|
25705
26867
|
const traceId = randomBytes(16).toString("hex");
|
|
@@ -25711,6 +26873,11 @@ async function setupSession(params) {
|
|
|
25711
26873
|
tokenCounter,
|
|
25712
26874
|
cwd,
|
|
25713
26875
|
projectRoot,
|
|
26876
|
+
// Filesystem-access scope: derived from features.allowOutsideProjectRoot,
|
|
26877
|
+
// falling back to the old tools.restrictToProjectRoot config key (inverted:
|
|
26878
|
+
// restrict=false in old config → allow=true in new). Togglable live via
|
|
26879
|
+
// `/settings` ("Allow outside project").
|
|
26880
|
+
allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
|
|
25714
26881
|
model: config.model,
|
|
25715
26882
|
agentId: "leader",
|
|
25716
26883
|
agentName: "Leader Agent",
|
|
@@ -25722,11 +26889,11 @@ async function setupSession(params) {
|
|
|
25722
26889
|
};
|
|
25723
26890
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
25724
26891
|
const queueStore = new QueueStore({
|
|
25725
|
-
dir:
|
|
26892
|
+
dir: path4.join(wpaths.projectSessions, session?.id),
|
|
25726
26893
|
...eventsBus ? { events: eventsBus } : {},
|
|
25727
26894
|
...traceId ? { traceId } : {}
|
|
25728
26895
|
});
|
|
25729
|
-
const todosCheckpointPath =
|
|
26896
|
+
const todosCheckpointPath = path4.join(wpaths.projectSessions, `${session?.id}.todos.json`);
|
|
25730
26897
|
if (resumeId) {
|
|
25731
26898
|
try {
|
|
25732
26899
|
const restoredTodos = await loadTodosCheckpoint(
|
|
@@ -25750,15 +26917,15 @@ async function setupSession(params) {
|
|
|
25750
26917
|
eventsBus,
|
|
25751
26918
|
traceId
|
|
25752
26919
|
);
|
|
25753
|
-
const planPath =
|
|
26920
|
+
const planPath = path4.join(wpaths.projectSessions, `${session?.id}.plan.json`);
|
|
25754
26921
|
context.state.setMeta("plan.path", planPath);
|
|
25755
|
-
const taskPath =
|
|
26922
|
+
const taskPath = path4.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
|
|
25756
26923
|
context.state.setMeta("task.path", taskPath);
|
|
25757
26924
|
let dirState;
|
|
25758
26925
|
if (resumeId) {
|
|
25759
26926
|
try {
|
|
25760
|
-
const fleetRoot =
|
|
25761
|
-
dirState = await loadDirectorState(
|
|
26927
|
+
const fleetRoot = path4.join(wpaths.projectSessions, session?.id);
|
|
26928
|
+
dirState = await loadDirectorState(path4.join(fleetRoot, "director-state.json"));
|
|
25762
26929
|
if (dirState) {
|
|
25763
26930
|
const tCounts = {};
|
|
25764
26931
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -26005,7 +27172,7 @@ async function main(argv) {
|
|
|
26005
27172
|
projectGoal: wpaths.projectGoal,
|
|
26006
27173
|
projectSessions: wpaths.projectSessions
|
|
26007
27174
|
},
|
|
26008
|
-
pathJoiner: { join: (a, b) =>
|
|
27175
|
+
pathJoiner: { join: (a, b) => path4.join(a, b) },
|
|
26009
27176
|
systemPromptBuilderToken: TOKENS.SystemPromptBuilder
|
|
26010
27177
|
});
|
|
26011
27178
|
const toolRegistry = new ToolRegistry();
|
|
@@ -26034,7 +27201,10 @@ async function main(argv) {
|
|
|
26034
27201
|
const evOn = (event, handler) => {
|
|
26035
27202
|
events.on(event, handler);
|
|
26036
27203
|
teardownHandlers.push(
|
|
26037
|
-
() =>
|
|
27204
|
+
() => (
|
|
27205
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic event dispatcher signature
|
|
27206
|
+
events.off(event, handler)
|
|
27207
|
+
)
|
|
26038
27208
|
);
|
|
26039
27209
|
};
|
|
26040
27210
|
evOn("provider.response", (e) => {
|
|
@@ -26097,6 +27267,49 @@ async function main(argv) {
|
|
|
26097
27267
|
writeErr(color.red(` \u2717 ${p.description}
|
|
26098
27268
|
`));
|
|
26099
27269
|
});
|
|
27270
|
+
const cliClientId = `cli@${randomBytes(4).toString("hex")}`;
|
|
27271
|
+
let cliToolCalls = 0;
|
|
27272
|
+
let cliInputTokens = 0;
|
|
27273
|
+
let cliOutputTokens = 0;
|
|
27274
|
+
let cliCacheTokens = 0;
|
|
27275
|
+
let cliCostUsd = 0;
|
|
27276
|
+
const emitClientStatus = () => {
|
|
27277
|
+
events.emit("client.status", {
|
|
27278
|
+
clientType: "cli",
|
|
27279
|
+
clientId: cliClientId,
|
|
27280
|
+
projectHash: wpaths.projectSlug,
|
|
27281
|
+
agentCount: 1,
|
|
27282
|
+
model: config.model,
|
|
27283
|
+
mode: activeMode?.id ?? "off",
|
|
27284
|
+
toolCalls: cliToolCalls,
|
|
27285
|
+
inputTokens: cliInputTokens,
|
|
27286
|
+
outputTokens: cliOutputTokens,
|
|
27287
|
+
cacheTokens: cliCacheTokens,
|
|
27288
|
+
costUsd: cliCostUsd,
|
|
27289
|
+
timestamp: Date.now(),
|
|
27290
|
+
projectSlug: wpaths.projectSlug
|
|
27291
|
+
});
|
|
27292
|
+
};
|
|
27293
|
+
evOn("tool.executed", () => {
|
|
27294
|
+
cliToolCalls++;
|
|
27295
|
+
emitClientStatus();
|
|
27296
|
+
});
|
|
27297
|
+
evOn("provider.response", (e) => {
|
|
27298
|
+
if (e.usage) {
|
|
27299
|
+
cliInputTokens = e.usage.input ?? cliInputTokens;
|
|
27300
|
+
cliOutputTokens = e.usage.output ?? cliOutputTokens;
|
|
27301
|
+
cliCacheTokens = (e.usage.cacheRead ?? 0) + (e.usage.cacheWrite ?? 0);
|
|
27302
|
+
}
|
|
27303
|
+
emitClientStatus();
|
|
27304
|
+
});
|
|
27305
|
+
evOn("token.accounted", (e) => {
|
|
27306
|
+
cliCostUsd = e.cost.total;
|
|
27307
|
+
emitClientStatus();
|
|
27308
|
+
});
|
|
27309
|
+
evOn("iteration.completed", () => {
|
|
27310
|
+
emitClientStatus();
|
|
27311
|
+
});
|
|
27312
|
+
emitClientStatus();
|
|
26100
27313
|
const promptBuilder = container.resolve(TOKENS.SystemPromptBuilder);
|
|
26101
27314
|
let onlineAgents = [];
|
|
26102
27315
|
try {
|
|
@@ -26140,10 +27353,10 @@ async function main(argv) {
|
|
|
26140
27353
|
memoryStore.withTraceId(sessResult.traceId);
|
|
26141
27354
|
let tracker;
|
|
26142
27355
|
try {
|
|
26143
|
-
const { getSessionRegistry, AgentStatusTracker } = await import('@wrongstack/core');
|
|
27356
|
+
const { getSessionRegistry, AgentStatusTracker, FleetNotifier } = await import('@wrongstack/core');
|
|
26144
27357
|
const registry = getSessionRegistry(wpaths.globalRoot);
|
|
26145
|
-
const projectSlug2 =
|
|
26146
|
-
const projectName =
|
|
27358
|
+
const projectSlug2 = path4.basename(wpaths.projectDir);
|
|
27359
|
+
const projectName = path4.basename(projectRoot);
|
|
26147
27360
|
let gitBranch;
|
|
26148
27361
|
try {
|
|
26149
27362
|
const { execSync } = await import('child_process');
|
|
@@ -26163,13 +27376,22 @@ async function main(argv) {
|
|
|
26163
27376
|
projectName,
|
|
26164
27377
|
workingDir: context.workingDir,
|
|
26165
27378
|
gitBranch,
|
|
27379
|
+
// The TUI and the REPL both boot through cli-main; `tuiOwnsScreen`
|
|
27380
|
+
// distinguishes the surface so the Fleet HQ map can label this session.
|
|
27381
|
+
clientType: tuiOwnsScreen ? "tui" : "cli",
|
|
26166
27382
|
pid: process.pid,
|
|
26167
27383
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26168
27384
|
});
|
|
26169
|
-
|
|
27385
|
+
const fleetNotifier = new FleetNotifier({
|
|
27386
|
+
baseDir: wpaths.globalRoot,
|
|
27387
|
+
projectRoot,
|
|
27388
|
+
selfPid: process.pid
|
|
27389
|
+
});
|
|
27390
|
+
tracker = new AgentStatusTracker({ events, registry, onUpdate: () => fleetNotifier.notify() });
|
|
26170
27391
|
tracker.start();
|
|
26171
27392
|
const cleanup = async () => {
|
|
26172
27393
|
try {
|
|
27394
|
+
fleetNotifier.dispose();
|
|
26173
27395
|
await registry.markClosing();
|
|
26174
27396
|
tracker?.stop();
|
|
26175
27397
|
} catch {
|
|
@@ -26242,7 +27464,7 @@ async function main(argv) {
|
|
|
26242
27464
|
if (e.ok && (e.name === "write" || e.name === "edit" || e.name === "replace" || e.name === "patch")) {
|
|
26243
27465
|
const filePath = e.input?.path;
|
|
26244
27466
|
if (filePath) {
|
|
26245
|
-
const projectDir =
|
|
27467
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
26246
27468
|
void recordFileAction(
|
|
26247
27469
|
{ storageDir: projectDir, projectRoot },
|
|
26248
27470
|
{
|
|
@@ -26372,7 +27594,7 @@ async function main(argv) {
|
|
|
26372
27594
|
toolRegistry,
|
|
26373
27595
|
events,
|
|
26374
27596
|
log: logger,
|
|
26375
|
-
lazyMode: config.features.tokenSavingMode
|
|
27597
|
+
lazyMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off"
|
|
26376
27598
|
});
|
|
26377
27599
|
if (config.features.mcp) {
|
|
26378
27600
|
for (const cfg of Object.values(config.mcpServers ?? {})) {
|
|
@@ -26408,7 +27630,7 @@ async function main(argv) {
|
|
|
26408
27630
|
let depWatcherDispose;
|
|
26409
27631
|
if (dwCfg?.["enabled"] === true) {
|
|
26410
27632
|
try {
|
|
26411
|
-
const projectDir =
|
|
27633
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
26412
27634
|
const dwMailbox = new GlobalMailbox(projectDir, events);
|
|
26413
27635
|
depWatcherDispose = attachDepWatcherBridge({
|
|
26414
27636
|
events,
|
|
@@ -26507,12 +27729,12 @@ async function main(argv) {
|
|
|
26507
27729
|
}
|
|
26508
27730
|
}
|
|
26509
27731
|
};
|
|
26510
|
-
const fleetRoot = directorMode ?
|
|
26511
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
26512
|
-
const sharedScratchpadPath = directorMode ?
|
|
26513
|
-
const subagentSessionsRoot = directorMode ?
|
|
26514
|
-
const stateCheckpointPath = directorMode ?
|
|
26515
|
-
const fleetRootForPromotion =
|
|
27732
|
+
const fleetRoot = directorMode ? path4.join(wpaths.projectSessions, session.id) : void 0;
|
|
27733
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path4.join(expectDefined(fleetRoot), "fleet.json") : void 0;
|
|
27734
|
+
const sharedScratchpadPath = directorMode ? path4.join(expectDefined(fleetRoot), "shared") : void 0;
|
|
27735
|
+
const subagentSessionsRoot = directorMode ? path4.join(expectDefined(fleetRoot), "subagents") : void 0;
|
|
27736
|
+
const stateCheckpointPath = directorMode ? path4.join(expectDefined(fleetRoot), "director-state.json") : void 0;
|
|
27737
|
+
const fleetRootForPromotion = path4.join(wpaths.projectSessions, session.id);
|
|
26516
27738
|
const brainSettings = {
|
|
26517
27739
|
maxAutoRisk: "medium"
|
|
26518
27740
|
};
|
|
@@ -26655,7 +27877,7 @@ async function main(argv) {
|
|
|
26655
27877
|
let techStackConsumerDispose;
|
|
26656
27878
|
if (dwCfg?.["enabled"] === true) {
|
|
26657
27879
|
try {
|
|
26658
|
-
const projectDir =
|
|
27880
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
26659
27881
|
const tsMailbox = new GlobalMailbox(projectDir, events);
|
|
26660
27882
|
const fileAuthorOpts = {
|
|
26661
27883
|
storageDir: projectDir,
|
|
@@ -26688,7 +27910,7 @@ async function main(argv) {
|
|
|
26688
27910
|
let pkgOutdatedDispose;
|
|
26689
27911
|
if (dwCfg?.["enabled"] === true) {
|
|
26690
27912
|
try {
|
|
26691
|
-
const projectDir =
|
|
27913
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
26692
27914
|
const pkgMailbox = new GlobalMailbox(projectDir, events);
|
|
26693
27915
|
const pkgTrackerOpts = {
|
|
26694
27916
|
storageDir: projectDir,
|
|
@@ -27062,7 +28284,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27062
28284
|
return director.spawn(cfg);
|
|
27063
28285
|
},
|
|
27064
28286
|
onFleetLog: async (subagentId, mode) => {
|
|
27065
|
-
const subagentsRoot =
|
|
28287
|
+
const subagentsRoot = path4.join(fleetRootForPromotion, "subagents");
|
|
27066
28288
|
let runDirs;
|
|
27067
28289
|
try {
|
|
27068
28290
|
runDirs = await fsp5.readdir(subagentsRoot);
|
|
@@ -27071,7 +28293,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27071
28293
|
}
|
|
27072
28294
|
const found = [];
|
|
27073
28295
|
for (const runId of runDirs) {
|
|
27074
|
-
const runDir =
|
|
28296
|
+
const runDir = path4.join(subagentsRoot, runId);
|
|
27075
28297
|
let files;
|
|
27076
28298
|
try {
|
|
27077
28299
|
files = await fsp5.readdir(runDir);
|
|
@@ -27080,7 +28302,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27080
28302
|
}
|
|
27081
28303
|
for (const f of files) {
|
|
27082
28304
|
if (!f.endsWith(".jsonl")) continue;
|
|
27083
|
-
const full =
|
|
28305
|
+
const full = path4.join(runDir, f);
|
|
27084
28306
|
try {
|
|
27085
28307
|
const stat7 = await fsp5.stat(full);
|
|
27086
28308
|
found.push({
|
|
@@ -27181,7 +28403,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27181
28403
|
}
|
|
27182
28404
|
const dir = await multiAgentHost.ensureDirector();
|
|
27183
28405
|
if (!dir) return "Director is not available.";
|
|
27184
|
-
const dirStatePath =
|
|
28406
|
+
const dirStatePath = path4.join(fleetRootForPromotion, "director-state.json");
|
|
27185
28407
|
const prior = await loadDirectorState(dirStatePath);
|
|
27186
28408
|
if (!prior) {
|
|
27187
28409
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -27250,9 +28472,9 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27250
28472
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
27251
28473
|
toolRegistry.register(tool);
|
|
27252
28474
|
}
|
|
27253
|
-
const mp =
|
|
27254
|
-
const sp =
|
|
27255
|
-
const ss =
|
|
28475
|
+
const mp = path4.join(fleetRootForPromotion, "fleet.json");
|
|
28476
|
+
const sp = path4.join(fleetRootForPromotion, "shared");
|
|
28477
|
+
const ss = path4.join(fleetRootForPromotion, "subagents");
|
|
27256
28478
|
const lines = [
|
|
27257
28479
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
27258
28480
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -27553,6 +28775,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
27553
28775
|
tokenCounter,
|
|
27554
28776
|
config,
|
|
27555
28777
|
configStore,
|
|
28778
|
+
// Project-scoped mailbox — the AutonomousCoordinator in execution.ts
|
|
28779
|
+
// subscribes to it so goals/tasks/knowledge are shared with other
|
|
28780
|
+
// terminals working on the same project.
|
|
28781
|
+
mailbox: brainMailbox,
|
|
27556
28782
|
renderer,
|
|
27557
28783
|
reader,
|
|
27558
28784
|
session,
|
|
@@ -27592,6 +28818,41 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
27592
28818
|
return autonomyMode;
|
|
27593
28819
|
},
|
|
27594
28820
|
getNextPredict: () => nextPredictEnabled,
|
|
28821
|
+
applyLiveSettings: (s) => {
|
|
28822
|
+
try {
|
|
28823
|
+
if (s.yolo !== void 0) {
|
|
28824
|
+
container.resolve(TOKENS.PermissionPolicy).setYolo?.(s.yolo);
|
|
28825
|
+
config = patchConfig(config, { yolo: s.yolo });
|
|
28826
|
+
}
|
|
28827
|
+
if (s.nextPrediction !== void 0) {
|
|
28828
|
+
nextPredictEnabled = s.nextPrediction;
|
|
28829
|
+
config = patchConfig(config, { nextPrediction: s.nextPrediction });
|
|
28830
|
+
}
|
|
28831
|
+
if (s.enhanceEnabled !== void 0) {
|
|
28832
|
+
enhanceController?.setEnabled(s.enhanceEnabled);
|
|
28833
|
+
}
|
|
28834
|
+
if (s.maxIterations !== void 0) {
|
|
28835
|
+
agent.maxIterations = s.maxIterations;
|
|
28836
|
+
}
|
|
28837
|
+
if (s.logLevel !== void 0) {
|
|
28838
|
+
container.resolve(TOKENS.Logger).level = s.logLevel;
|
|
28839
|
+
}
|
|
28840
|
+
if (s.auditLevel !== void 0) {
|
|
28841
|
+
sessionBridge.setAuditLevel(s.auditLevel);
|
|
28842
|
+
}
|
|
28843
|
+
if (s.contextAutoCompact !== void 0) {
|
|
28844
|
+
autoCompactor?.setEnabled(s.contextAutoCompact);
|
|
28845
|
+
}
|
|
28846
|
+
if (s.restrictFsToRoot !== void 0) {
|
|
28847
|
+
context.allowOutsideProjectRoot = !s.restrictFsToRoot;
|
|
28848
|
+
config = patchConfig(config, {
|
|
28849
|
+
features: { ...config.features, allowOutsideProjectRoot: !s.restrictFsToRoot },
|
|
28850
|
+
tools: { ...config.tools, restrictToProjectRoot: s.restrictFsToRoot }
|
|
28851
|
+
});
|
|
28852
|
+
}
|
|
28853
|
+
} catch {
|
|
28854
|
+
}
|
|
28855
|
+
},
|
|
27595
28856
|
onSuggestionsParsed: (suggestions) => {
|
|
27596
28857
|
currentSuggestions = suggestions ?? [];
|
|
27597
28858
|
setSuggestions(suggestions ?? []);
|
|
@@ -27674,7 +28935,9 @@ Reply YES to auto-proceed, NO to wait for human input.`
|
|
|
27674
28935
|
getBrainLog: () => brainLog,
|
|
27675
28936
|
// Clean up SessionStats event listeners and all EventBus handlers when the REPL exits.
|
|
27676
28937
|
onDestroy: () => {
|
|
27677
|
-
teardownHandlers.forEach((fn) =>
|
|
28938
|
+
teardownHandlers.forEach((fn) => {
|
|
28939
|
+
fn();
|
|
28940
|
+
});
|
|
27678
28941
|
stats.destroy(events);
|
|
27679
28942
|
}
|
|
27680
28943
|
});
|