@wrongstack/cli 0.264.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 +1018 -429
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
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
14
|
import { toErrorMessage } from '@wrongstack/core/utils';
|
|
15
|
-
import { getProcessRegistry, builtinToolsPack,
|
|
15
|
+
import { getProcessRegistry, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, TIER2_TOOLS, TIER3_TOOLS, TIER1_TOOLS, resetIndexCircuitBreaker } from '@wrongstack/tools';
|
|
16
16
|
import { DefaultSessionStore } from '@wrongstack/core/storage';
|
|
17
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';
|
|
18
21
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
19
22
|
import { spawn, execFile, execFileSync } from 'child_process';
|
|
20
23
|
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
21
|
-
import * as fs2 from 'fs';
|
|
22
|
-
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
23
24
|
import { fileURLToPath } from 'url';
|
|
24
25
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
25
26
|
import * as readline from 'readline';
|
|
@@ -68,15 +69,57 @@ async function pathExists(file) {
|
|
|
68
69
|
return false;
|
|
69
70
|
}
|
|
70
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
|
+
}
|
|
71
114
|
async function detectPackageManager2(root, declared) {
|
|
72
115
|
if (declared) {
|
|
73
116
|
const name = declared.split("@")[0];
|
|
74
117
|
if (name) return name;
|
|
75
118
|
}
|
|
76
|
-
if (await pathExists(
|
|
77
|
-
if (await pathExists(
|
|
78
|
-
if (await pathExists(
|
|
79
|
-
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";
|
|
80
123
|
return "npm";
|
|
81
124
|
}
|
|
82
125
|
function hasUsableScript(scripts, name) {
|
|
@@ -94,10 +137,50 @@ function parseMakeTargets(makefile) {
|
|
|
94
137
|
}
|
|
95
138
|
return targets;
|
|
96
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
|
+
}
|
|
97
180
|
async function detectProjectFacts(root) {
|
|
98
181
|
const facts = { hints: [] };
|
|
99
182
|
try {
|
|
100
|
-
const pkg = JSON.parse(await fsp5.readFile(
|
|
183
|
+
const pkg = JSON.parse(await fsp5.readFile(path4.join(root, "package.json"), "utf8"));
|
|
101
184
|
const scripts = pkg.scripts ?? {};
|
|
102
185
|
const pm = await detectPackageManager2(root, pkg.packageManager);
|
|
103
186
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -111,14 +194,14 @@ async function detectProjectFacts(root) {
|
|
|
111
194
|
} catch {
|
|
112
195
|
}
|
|
113
196
|
try {
|
|
114
|
-
if (!await pathExists(
|
|
197
|
+
if (!await pathExists(path4.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
115
198
|
facts.test ??= "pytest";
|
|
116
199
|
facts.lint ??= "ruff check .";
|
|
117
200
|
facts.hints.push("pyproject.toml");
|
|
118
201
|
} catch {
|
|
119
202
|
}
|
|
120
203
|
try {
|
|
121
|
-
if (!await pathExists(
|
|
204
|
+
if (!await pathExists(path4.join(root, "go.mod"))) throw new Error("not go");
|
|
122
205
|
facts.build ??= "go build ./...";
|
|
123
206
|
facts.test ??= "go test ./...";
|
|
124
207
|
facts.run ??= "go run .";
|
|
@@ -126,7 +209,7 @@ async function detectProjectFacts(root) {
|
|
|
126
209
|
} catch {
|
|
127
210
|
}
|
|
128
211
|
try {
|
|
129
|
-
if (!await pathExists(
|
|
212
|
+
if (!await pathExists(path4.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
130
213
|
facts.build ??= "cargo build";
|
|
131
214
|
facts.test ??= "cargo test";
|
|
132
215
|
facts.lint ??= "cargo clippy";
|
|
@@ -135,7 +218,7 @@ async function detectProjectFacts(root) {
|
|
|
135
218
|
} catch {
|
|
136
219
|
}
|
|
137
220
|
try {
|
|
138
|
-
const makefile = await fsp5.readFile(
|
|
221
|
+
const makefile = await fsp5.readFile(path4.join(root, "Makefile"), "utf8");
|
|
139
222
|
const targets = parseMakeTargets(makefile);
|
|
140
223
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
141
224
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -145,6 +228,118 @@ async function detectProjectFacts(root) {
|
|
|
145
228
|
facts.hints.push("Makefile");
|
|
146
229
|
} catch {
|
|
147
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
|
+
}
|
|
148
343
|
return facts;
|
|
149
344
|
}
|
|
150
345
|
function renderAgentsTemplate(f) {
|
|
@@ -152,6 +347,16 @@ function renderAgentsTemplate(f) {
|
|
|
152
347
|
const hints = f.hints.length > 0 ? `
|
|
153
348
|
|
|
154
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.)_ |`;
|
|
155
360
|
return `# AGENTS.md
|
|
156
361
|
|
|
157
362
|
> **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
|
|
@@ -163,7 +368,7 @@ function renderAgentsTemplate(f) {
|
|
|
163
368
|
|
|
164
369
|
- **Purpose:** _What does this project do and why does it exist?_
|
|
165
370
|
- **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
|
|
166
|
-
- **Runtime / deployment:**
|
|
371
|
+
- **Runtime / deployment:** ${runtime}${hints}
|
|
167
372
|
|
|
168
373
|
## How to work safely
|
|
169
374
|
|
|
@@ -185,10 +390,7 @@ function renderAgentsTemplate(f) {
|
|
|
185
390
|
|
|
186
391
|
| File / directory | Role |
|
|
187
392
|
|---|---|
|
|
188
|
-
|
|
189
|
-
| _tests/_ | _Test root or convention_ |
|
|
190
|
-
| _docs/_ | _Architecture, runbooks, design notes_ |
|
|
191
|
-
| _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
|
|
393
|
+
${keyFiles}
|
|
192
394
|
|
|
193
395
|
## Architecture notes
|
|
194
396
|
|
|
@@ -247,8 +449,79 @@ function countToolResults(messages) {
|
|
|
247
449
|
function estimateTokens(messages) {
|
|
248
450
|
return estimateMessageTokens(messages);
|
|
249
451
|
}
|
|
452
|
+
var EXT_LANG, SCAN_IGNORE_DIRS, ENTRY_BASENAMES;
|
|
250
453
|
var init_helpers = __esm({
|
|
251
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__"]);
|
|
252
525
|
}
|
|
253
526
|
});
|
|
254
527
|
function normalizeKeys(cfg) {
|
|
@@ -269,11 +542,18 @@ function writeKeysBack(cfg, keys) {
|
|
|
269
542
|
}
|
|
270
543
|
cfg.apiKeys = keys;
|
|
271
544
|
const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
|
|
272
|
-
cfg.apiKey
|
|
545
|
+
delete cfg.apiKey;
|
|
273
546
|
if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
|
|
274
547
|
cfg.activeKey = active.label;
|
|
275
548
|
}
|
|
276
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
|
+
}
|
|
277
557
|
function activeLabel(cfg, keys) {
|
|
278
558
|
if (cfg.activeKey && keys.some((k) => k.label === cfg.activeKey)) return cfg.activeKey;
|
|
279
559
|
return keys[0]?.label;
|
|
@@ -678,7 +958,7 @@ async function findSpec(store, idOrTitle) {
|
|
|
678
958
|
async function gatherProjectContext2(projectRoot) {
|
|
679
959
|
const parts = [];
|
|
680
960
|
try {
|
|
681
|
-
const pkgPath =
|
|
961
|
+
const pkgPath = path4.join(projectRoot, "package.json");
|
|
682
962
|
const pkgRaw = await fsp5.readFile(pkgPath, "utf8");
|
|
683
963
|
const pkg = JSON.parse(pkgRaw);
|
|
684
964
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -696,13 +976,13 @@ async function gatherProjectContext2(projectRoot) {
|
|
|
696
976
|
} catch {
|
|
697
977
|
}
|
|
698
978
|
try {
|
|
699
|
-
const tsconfigPath =
|
|
979
|
+
const tsconfigPath = path4.join(projectRoot, "tsconfig.json");
|
|
700
980
|
await fsp5.access(tsconfigPath);
|
|
701
981
|
parts.push("Language: TypeScript");
|
|
702
982
|
} catch {
|
|
703
983
|
}
|
|
704
984
|
try {
|
|
705
|
-
const srcDir =
|
|
985
|
+
const srcDir = path4.join(projectRoot, "src");
|
|
706
986
|
const entries = await fsp5.readdir(srcDir, { withFileTypes: true });
|
|
707
987
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
708
988
|
if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -2127,12 +2407,12 @@ __export(project_utils_exports, {
|
|
|
2127
2407
|
touchProjectInManifest: () => touchProjectInManifest
|
|
2128
2408
|
});
|
|
2129
2409
|
function projectsJsonPath(globalConfigPath) {
|
|
2130
|
-
const base = globalConfigPath ?
|
|
2131
|
-
return
|
|
2410
|
+
const base = globalConfigPath ? path4.dirname(globalConfigPath) : wstackGlobalRoot();
|
|
2411
|
+
return path4.join(base, "projects.json");
|
|
2132
2412
|
}
|
|
2133
2413
|
function projectsDataDir(globalConfigPath) {
|
|
2134
|
-
const base = globalConfigPath ?
|
|
2135
|
-
return
|
|
2414
|
+
const base = globalConfigPath ? path4.dirname(globalConfigPath) : wstackGlobalRoot();
|
|
2415
|
+
return path4.join(base, "projects");
|
|
2136
2416
|
}
|
|
2137
2417
|
async function loadManifest(globalConfigPath) {
|
|
2138
2418
|
const file = projectsJsonPath(globalConfigPath);
|
|
@@ -2146,12 +2426,12 @@ async function loadManifest(globalConfigPath) {
|
|
|
2146
2426
|
}
|
|
2147
2427
|
async function saveManifest(manifest, globalConfigPath) {
|
|
2148
2428
|
const file = projectsJsonPath(globalConfigPath);
|
|
2149
|
-
await fsp5.mkdir(
|
|
2429
|
+
await fsp5.mkdir(path4.dirname(file), { recursive: true });
|
|
2150
2430
|
await fsp5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
2151
2431
|
}
|
|
2152
2432
|
function generateSlug(root) {
|
|
2153
|
-
const base =
|
|
2154
|
-
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);
|
|
2155
2435
|
return `${base}-${hash}`;
|
|
2156
2436
|
}
|
|
2157
2437
|
function findProject(manifest, query) {
|
|
@@ -2166,29 +2446,29 @@ function findProject(manifest, query) {
|
|
|
2166
2446
|
return found;
|
|
2167
2447
|
}
|
|
2168
2448
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
2169
|
-
const dir =
|
|
2449
|
+
const dir = path4.join(projectsDataDir(globalConfigPath), slug);
|
|
2170
2450
|
await fsp5.mkdir(dir, { recursive: true });
|
|
2171
2451
|
return dir;
|
|
2172
2452
|
}
|
|
2173
2453
|
async function touchProjectInManifest(opts) {
|
|
2174
|
-
const root =
|
|
2454
|
+
const root = path4.resolve(opts.projectRoot);
|
|
2175
2455
|
const file = projectsJsonPath(opts.globalConfigPath);
|
|
2176
2456
|
let entry;
|
|
2177
2457
|
await withFileLock(file, async () => {
|
|
2178
2458
|
const manifest = await loadManifest(opts.globalConfigPath);
|
|
2179
2459
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2180
|
-
entry = manifest.projects.find((p) =>
|
|
2460
|
+
entry = manifest.projects.find((p) => path4.resolve(p.root) === root);
|
|
2181
2461
|
if (entry) {
|
|
2182
2462
|
entry.lastSeen = now;
|
|
2183
|
-
if (opts.workingDir) entry.lastWorkingDir =
|
|
2463
|
+
if (opts.workingDir) entry.lastWorkingDir = path4.resolve(opts.workingDir);
|
|
2184
2464
|
} else {
|
|
2185
2465
|
entry = {
|
|
2186
|
-
name: opts.name ??
|
|
2466
|
+
name: opts.name ?? path4.basename(root),
|
|
2187
2467
|
root,
|
|
2188
2468
|
slug: generateSlug(root),
|
|
2189
2469
|
createdAt: now,
|
|
2190
2470
|
lastSeen: now,
|
|
2191
|
-
lastWorkingDir: opts.workingDir ?
|
|
2471
|
+
lastWorkingDir: opts.workingDir ? path4.resolve(opts.workingDir) : void 0
|
|
2192
2472
|
};
|
|
2193
2473
|
manifest.projects.push(entry);
|
|
2194
2474
|
}
|
|
@@ -2634,7 +2914,7 @@ __export(update_check_exports, {
|
|
|
2634
2914
|
getUpdateNotification: () => getUpdateNotification
|
|
2635
2915
|
});
|
|
2636
2916
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
2637
|
-
return
|
|
2917
|
+
return path4.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
2638
2918
|
}
|
|
2639
2919
|
function currentVersion() {
|
|
2640
2920
|
const req2 = createRequire(import.meta.url);
|
|
@@ -2671,7 +2951,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
2671
2951
|
}
|
|
2672
2952
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
2673
2953
|
try {
|
|
2674
|
-
const dir =
|
|
2954
|
+
const dir = path4.dirname(cachePath(homeFn));
|
|
2675
2955
|
await fsp5.mkdir(dir, { recursive: true });
|
|
2676
2956
|
await fsp5.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
2677
2957
|
} catch {
|
|
@@ -2757,7 +3037,7 @@ function registerWebuiInstance(p, deps = {}) {
|
|
|
2757
3037
|
wsPort: p.wsPort,
|
|
2758
3038
|
host: p.host,
|
|
2759
3039
|
projectRoot: p.projectRoot,
|
|
2760
|
-
projectName:
|
|
3040
|
+
projectName: path4.basename(p.projectRoot) || p.projectRoot,
|
|
2761
3041
|
startedAt: p.startedAt,
|
|
2762
3042
|
url: `http://${p.host}:${p.httpPort}`
|
|
2763
3043
|
},
|
|
@@ -2768,7 +3048,7 @@ function registerWebuiInstance(p, deps = {}) {
|
|
|
2768
3048
|
function announceWebuiReady(p) {
|
|
2769
3049
|
const log = p.log ?? ((m) => console.log(m));
|
|
2770
3050
|
const launch = p.openBrowserFn ?? openBrowser;
|
|
2771
|
-
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}`;
|
|
2772
3052
|
p.server.on("listening", () => {
|
|
2773
3053
|
log(
|
|
2774
3054
|
`
|
|
@@ -2821,7 +3101,7 @@ var init_lifecycle = __esm({
|
|
|
2821
3101
|
}
|
|
2822
3102
|
});
|
|
2823
3103
|
function getVault(globalConfigPath) {
|
|
2824
|
-
const keyFile =
|
|
3104
|
+
const keyFile = path4.join(path4.dirname(globalConfigPath ?? ""), ".key");
|
|
2825
3105
|
return new DefaultSecretVault({ keyFile });
|
|
2826
3106
|
}
|
|
2827
3107
|
async function loadSavedProviders(globalConfigPath) {
|
|
@@ -2855,7 +3135,7 @@ function resolveDistDir() {
|
|
|
2855
3135
|
try {
|
|
2856
3136
|
const requireFromHere = createRequire(import.meta.url);
|
|
2857
3137
|
const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
|
|
2858
|
-
return
|
|
3138
|
+
return path4.resolve(path4.dirname(serverEntry), "..");
|
|
2859
3139
|
} catch {
|
|
2860
3140
|
return null;
|
|
2861
3141
|
}
|
|
@@ -2869,7 +3149,9 @@ function startStaticServe(opts, deps = {}) {
|
|
|
2869
3149
|
host: opts.host,
|
|
2870
3150
|
distDir,
|
|
2871
3151
|
wsPort: opts.wsPort,
|
|
2872
|
-
globalRoot: opts.globalRoot
|
|
3152
|
+
globalRoot: opts.globalRoot,
|
|
3153
|
+
onFleetPing: opts.onFleetPing,
|
|
3154
|
+
apiToken: opts.apiToken
|
|
2873
3155
|
});
|
|
2874
3156
|
server.listen(opts.httpPort, opts.host);
|
|
2875
3157
|
return { server, port: opts.httpPort };
|
|
@@ -3540,8 +3822,8 @@ function sendResult6(ctx, ws, success, message) {
|
|
|
3540
3822
|
ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
3541
3823
|
}
|
|
3542
3824
|
async function handleProjectsList(ctx, ws) {
|
|
3543
|
-
const projectsBase = ctx.opts.globalConfigPath ?
|
|
3544
|
-
const manifestPath =
|
|
3825
|
+
const projectsBase = ctx.opts.globalConfigPath ? path4.resolve(path4.dirname(ctx.opts.globalConfigPath)) : wstackGlobalRoot();
|
|
3826
|
+
const manifestPath = path4.join(projectsBase, "projects.json");
|
|
3545
3827
|
try {
|
|
3546
3828
|
const raw = await fsp5.readFile(manifestPath, "utf8");
|
|
3547
3829
|
const manifest = JSON.parse(raw);
|
|
@@ -3554,22 +3836,22 @@ async function handleProjectsSelect(ctx, ws, payload) {
|
|
|
3554
3836
|
const { opts } = ctx;
|
|
3555
3837
|
const { root, name: projectName } = payload;
|
|
3556
3838
|
try {
|
|
3557
|
-
const resolved =
|
|
3839
|
+
const resolved = path4.resolve(root);
|
|
3558
3840
|
const stat7 = await fsp5.stat(resolved).catch(() => null);
|
|
3559
3841
|
if (!stat7?.isDirectory()) {
|
|
3560
3842
|
ctx.send(ws, {
|
|
3561
3843
|
type: "projects.selected",
|
|
3562
3844
|
payload: {
|
|
3563
3845
|
root,
|
|
3564
|
-
name: projectName ??
|
|
3846
|
+
name: projectName ?? path4.basename(root),
|
|
3565
3847
|
message: `Cannot switch: not a directory: ${resolved}`
|
|
3566
3848
|
}
|
|
3567
3849
|
});
|
|
3568
3850
|
return;
|
|
3569
3851
|
}
|
|
3570
3852
|
const manifest = await loadManifest(opts.globalConfigPath);
|
|
3571
|
-
const entry = manifest.projects.find((p) =>
|
|
3572
|
-
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);
|
|
3573
3855
|
if (entry) {
|
|
3574
3856
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
3575
3857
|
} else {
|
|
@@ -3615,8 +3897,8 @@ async function handleProjectsSelect(ctx, ws, payload) {
|
|
|
3615
3897
|
});
|
|
3616
3898
|
} catch {
|
|
3617
3899
|
}
|
|
3618
|
-
const globalRoot = opts.globalConfigPath ?
|
|
3619
|
-
const newSessionsDir =
|
|
3900
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : wstackGlobalRoot();
|
|
3901
|
+
const newSessionsDir = path4.join(resolveProjectDir(resolved, globalRoot), "sessions");
|
|
3620
3902
|
await fsp5.mkdir(newSessionsDir, { recursive: true });
|
|
3621
3903
|
const newStore = new DefaultSessionStore({ dir: newSessionsDir });
|
|
3622
3904
|
opts.sessionStore = newStore;
|
|
@@ -3646,11 +3928,11 @@ async function handleProjectsSelect(ctx, ws, payload) {
|
|
|
3646
3928
|
async function handleProjectsAdd(ctx, ws, payload) {
|
|
3647
3929
|
const { root: addRoot, name: addName } = payload;
|
|
3648
3930
|
try {
|
|
3649
|
-
const resolved =
|
|
3931
|
+
const resolved = path4.resolve(addRoot);
|
|
3650
3932
|
const stat7 = await fsp5.stat(resolved).catch(() => null);
|
|
3651
3933
|
if (!stat7?.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
3652
3934
|
const manifest = await loadManifest(ctx.opts.globalConfigPath);
|
|
3653
|
-
const existing = manifest.projects.find((p) =>
|
|
3935
|
+
const existing = manifest.projects.find((p) => path4.resolve(p.root) === resolved);
|
|
3654
3936
|
if (existing) {
|
|
3655
3937
|
ctx.send(ws, {
|
|
3656
3938
|
type: "projects.added",
|
|
@@ -3663,7 +3945,7 @@ async function handleProjectsAdd(ctx, ws, payload) {
|
|
|
3663
3945
|
});
|
|
3664
3946
|
return;
|
|
3665
3947
|
}
|
|
3666
|
-
const name = addName?.trim() ||
|
|
3948
|
+
const name = addName?.trim() || path4.basename(resolved);
|
|
3667
3949
|
const slug = projectSlug(resolved);
|
|
3668
3950
|
await ensureProjectDataDir(slug, ctx.opts.globalConfigPath);
|
|
3669
3951
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3677,7 +3959,7 @@ async function handleProjectsAdd(ctx, ws, payload) {
|
|
|
3677
3959
|
ctx.send(ws, {
|
|
3678
3960
|
type: "projects.added",
|
|
3679
3961
|
payload: {
|
|
3680
|
-
name:
|
|
3962
|
+
name: path4.basename(addRoot),
|
|
3681
3963
|
root: addRoot,
|
|
3682
3964
|
slug: "",
|
|
3683
3965
|
message: toErrorMessage(err)
|
|
@@ -3688,8 +3970,8 @@ async function handleProjectsAdd(ctx, ws, payload) {
|
|
|
3688
3970
|
async function handleWorkingDirSet(ctx, ws, newPath) {
|
|
3689
3971
|
try {
|
|
3690
3972
|
const wdRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
3691
|
-
const resolved =
|
|
3692
|
-
if (!resolved.startsWith(wdRoot +
|
|
3973
|
+
const resolved = path4.resolve(wdRoot, newPath);
|
|
3974
|
+
if (!resolved.startsWith(wdRoot + path4.sep) && resolved !== wdRoot) {
|
|
3693
3975
|
sendResult6(ctx, ws, false, `Path must stay inside the project root: ${wdRoot}`);
|
|
3694
3976
|
return;
|
|
3695
3977
|
}
|
|
@@ -4026,13 +4308,13 @@ function sendResult8(ctx, ws, success, message) {
|
|
|
4026
4308
|
}
|
|
4027
4309
|
function storeFor(opts) {
|
|
4028
4310
|
return opts.sessionStore ?? new DefaultSessionStore({
|
|
4029
|
-
dir:
|
|
4311
|
+
dir: path4.join(opts.projectRoot ?? opts.agent.ctx.projectRoot, ".wrongstack", "sessions")
|
|
4030
4312
|
});
|
|
4031
4313
|
}
|
|
4032
4314
|
async function handleGoalGet(ctx, _ws) {
|
|
4033
4315
|
const projectRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
|
|
4034
4316
|
try {
|
|
4035
|
-
const goalPath =
|
|
4317
|
+
const goalPath = path4.join(projectRoot, ".wrongstack", "goal.json");
|
|
4036
4318
|
const raw = await fsp5.readFile(goalPath, "utf8");
|
|
4037
4319
|
ctx.broadcast({ type: "goal.updated", payload: JSON.parse(raw) });
|
|
4038
4320
|
} catch {
|
|
@@ -4108,7 +4390,7 @@ async function handleSessionNew(ctx, _ws) {
|
|
|
4108
4390
|
function rewinderFor(opts) {
|
|
4109
4391
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
4110
4392
|
return new DefaultSessionRewinder(
|
|
4111
|
-
opts.sessionsDir ??
|
|
4393
|
+
opts.sessionsDir ?? path4.join(projectRoot, ".wrongstack", "sessions"),
|
|
4112
4394
|
projectRoot
|
|
4113
4395
|
);
|
|
4114
4396
|
}
|
|
@@ -4442,14 +4724,14 @@ async function runWebUI(opts) {
|
|
|
4442
4724
|
let customModeStoreP = null;
|
|
4443
4725
|
const getCustomModeStore = () => {
|
|
4444
4726
|
customModeStoreP ??= (async () => {
|
|
4445
|
-
const dir = opts.globalConfigPath ?
|
|
4727
|
+
const dir = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : wstackGlobalRoot();
|
|
4446
4728
|
const store = createCustomModeStore(dir);
|
|
4447
4729
|
await store.load();
|
|
4448
4730
|
return store;
|
|
4449
4731
|
})();
|
|
4450
4732
|
return customModeStoreP;
|
|
4451
4733
|
};
|
|
4452
|
-
const autoPhaseStoreDir = opts.projectRoot ?
|
|
4734
|
+
const autoPhaseStoreDir = opts.projectRoot ? path4.join(opts.projectRoot, ".wrongstack", "autophase") : path4.join(os.tmpdir(), ".wrongstack", "autophase");
|
|
4453
4735
|
const autoPhaseHandler = new AutoPhaseWebSocketHandler(
|
|
4454
4736
|
opts.agent,
|
|
4455
4737
|
opts.agent.ctx,
|
|
@@ -4480,7 +4762,12 @@ async function runWebUI(opts) {
|
|
|
4480
4762
|
"contextAutoCompact",
|
|
4481
4763
|
"contextStrategy",
|
|
4482
4764
|
"logLevel",
|
|
4483
|
-
"auditLevel"
|
|
4765
|
+
"auditLevel",
|
|
4766
|
+
// Telegram plugin notification settings (parity with the standalone server).
|
|
4767
|
+
"tgConfigured",
|
|
4768
|
+
"tgSessionEnd",
|
|
4769
|
+
"tgDelegate",
|
|
4770
|
+
"tgLongToolMs"
|
|
4484
4771
|
];
|
|
4485
4772
|
const prefSnapshot = () => {
|
|
4486
4773
|
const snapshot = {};
|
|
@@ -4519,6 +4806,12 @@ async function runWebUI(opts) {
|
|
|
4519
4806
|
meta["logLevel"] = cfg.log?.["level"] ?? "info";
|
|
4520
4807
|
meta["auditLevel"] = cfg.session?.["auditLevel"] ?? "standard";
|
|
4521
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;
|
|
4522
4815
|
} catch {
|
|
4523
4816
|
}
|
|
4524
4817
|
}
|
|
@@ -4540,7 +4833,7 @@ async function runWebUI(opts) {
|
|
|
4540
4833
|
return;
|
|
4541
4834
|
}
|
|
4542
4835
|
const vault = new DefaultSecretVault({
|
|
4543
|
-
keyFile:
|
|
4836
|
+
keyFile: path4.join(path4.dirname(configPath2), ".key")
|
|
4544
4837
|
});
|
|
4545
4838
|
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
4546
4839
|
const autonomyCfg = decrypted.autonomy ?? {};
|
|
@@ -4613,6 +4906,16 @@ async function runWebUI(opts) {
|
|
|
4613
4906
|
toolsCfg.maxIterations = payload["maxIterations"];
|
|
4614
4907
|
decrypted.tools = toolsCfg;
|
|
4615
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
|
+
}
|
|
4616
4919
|
const encrypted = encryptConfigSecrets$1(decrypted, vault);
|
|
4617
4920
|
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4618
4921
|
};
|
|
@@ -4634,7 +4937,6 @@ async function runWebUI(opts) {
|
|
|
4634
4937
|
);
|
|
4635
4938
|
}
|
|
4636
4939
|
};
|
|
4637
|
-
const authToken = crypto3.randomBytes(16).toString("hex");
|
|
4638
4940
|
const sessionStartedAt = Date.now();
|
|
4639
4941
|
async function buildSessionStartPayload(overrides, needsSetup = false) {
|
|
4640
4942
|
let maxContext = 0;
|
|
@@ -4663,7 +4965,7 @@ async function runWebUI(opts) {
|
|
|
4663
4965
|
model: opts.agent.ctx.model,
|
|
4664
4966
|
provider: opts.agent.ctx.provider.id,
|
|
4665
4967
|
mode: opts.modeId ?? "default",
|
|
4666
|
-
projectName: opts.projectRoot ?
|
|
4968
|
+
projectName: opts.projectRoot ? path4.basename(opts.projectRoot) : void 0,
|
|
4667
4969
|
// Frontend reads `projectRoot` from session.start (ws-handlers setEnv) —
|
|
4668
4970
|
// omitting it left the store's projectRoot empty after a project switch.
|
|
4669
4971
|
projectRoot: opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "",
|
|
@@ -4689,7 +4991,7 @@ async function runWebUI(opts) {
|
|
|
4689
4991
|
const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot());
|
|
4690
4992
|
const mailbox = new GlobalMailbox(projectDir, opts.events);
|
|
4691
4993
|
webuiClientId = `webui@${crypto3.randomUUID().slice(0, 8)}`;
|
|
4692
|
-
const projectName = opts.projectRoot ?
|
|
4994
|
+
const projectName = opts.projectRoot ? path4.basename(opts.projectRoot) : "unknown";
|
|
4693
4995
|
await mailbox.registerClient({
|
|
4694
4996
|
clientId: webuiClientId,
|
|
4695
4997
|
sessionId: opts.projectRoot,
|
|
@@ -4714,13 +5016,19 @@ async function runWebUI(opts) {
|
|
|
4714
5016
|
}
|
|
4715
5017
|
};
|
|
4716
5018
|
registerWebuiClient();
|
|
5019
|
+
const wsToken = generateAuthToken();
|
|
4717
5020
|
const wss = new WebSocketServer({ port, host, maxPayload: 1 * 1024 * 1024 });
|
|
4718
5021
|
console.log(`[WebUI] WebSocket server starting on ws://${host}:${port}`);
|
|
5022
|
+
let fleetBroadcastCli = null;
|
|
4719
5023
|
const httpServer = startStaticServe({
|
|
4720
5024
|
host,
|
|
4721
5025
|
httpPort,
|
|
4722
5026
|
wsPort,
|
|
4723
|
-
globalRoot:
|
|
5027
|
+
globalRoot: path4.dirname(opts.globalConfigPath ?? ""),
|
|
5028
|
+
onFleetPing: () => {
|
|
5029
|
+
void fleetBroadcastCli?.();
|
|
5030
|
+
},
|
|
5031
|
+
apiToken: wsToken
|
|
4724
5032
|
});
|
|
4725
5033
|
if (httpServer) {
|
|
4726
5034
|
announceWebuiReady({
|
|
@@ -4728,14 +5036,15 @@ async function runWebUI(opts) {
|
|
|
4728
5036
|
host,
|
|
4729
5037
|
httpPort,
|
|
4730
5038
|
wsPort,
|
|
4731
|
-
open: !!opts.open
|
|
5039
|
+
open: !!opts.open,
|
|
5040
|
+
wsToken
|
|
4732
5041
|
});
|
|
4733
5042
|
} else {
|
|
4734
5043
|
console.warn(
|
|
4735
5044
|
`[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`). WS bridge still active on ws://${host}:${wsPort}.`
|
|
4736
5045
|
);
|
|
4737
5046
|
}
|
|
4738
|
-
const registryBaseDir = opts.globalConfigPath ?
|
|
5047
|
+
const registryBaseDir = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : void 0;
|
|
4739
5048
|
if (opts.projectRoot) {
|
|
4740
5049
|
registerWebuiInstance({
|
|
4741
5050
|
pid: process.pid,
|
|
@@ -4770,9 +5079,13 @@ async function runWebUI(opts) {
|
|
|
4770
5079
|
emitConcurrency();
|
|
4771
5080
|
eventUnsubscribers.push(
|
|
4772
5081
|
opts.events.on("iteration.started", (e) => {
|
|
5082
|
+
const maxIt = opts.agent.ctx.meta["maxIterations"];
|
|
4773
5083
|
broadcast({
|
|
4774
5084
|
type: "iteration.started",
|
|
4775
|
-
payload: {
|
|
5085
|
+
payload: {
|
|
5086
|
+
index: e.index,
|
|
5087
|
+
...typeof maxIt === "number" ? { maxIterations: maxIt } : {}
|
|
5088
|
+
}
|
|
4776
5089
|
});
|
|
4777
5090
|
})
|
|
4778
5091
|
);
|
|
@@ -5033,6 +5346,18 @@ async function runWebUI(opts) {
|
|
|
5033
5346
|
broadcast,
|
|
5034
5347
|
log: (m) => console.log(m)
|
|
5035
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
|
+
};
|
|
5036
5361
|
const worklistCtx = {
|
|
5037
5362
|
agent: opts.agent,
|
|
5038
5363
|
sessionId: opts.session.id,
|
|
@@ -5097,20 +5422,22 @@ async function runWebUI(opts) {
|
|
|
5097
5422
|
console.log(`[WebUI] WebSocket server running on ws://${host}:${port}`);
|
|
5098
5423
|
setupEvents();
|
|
5099
5424
|
opts.onListening?.({ httpPort, wsPort, host });
|
|
5100
|
-
const globalRoot = opts.globalConfigPath ?
|
|
5425
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : void 0;
|
|
5101
5426
|
if (globalRoot) {
|
|
5102
|
-
const
|
|
5427
|
+
const broadcastSessions = async () => {
|
|
5103
5428
|
try {
|
|
5104
5429
|
const { SessionRegistry } = await import('@wrongstack/core');
|
|
5105
5430
|
const registry = new SessionRegistry(globalRoot);
|
|
5106
5431
|
const sessions = await registry.list();
|
|
5107
|
-
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) => ({
|
|
5108
5434
|
sessionId: s.sessionId,
|
|
5109
5435
|
projectName: s.projectName,
|
|
5110
5436
|
projectSlug: s.projectSlug,
|
|
5111
5437
|
projectRoot: s.projectRoot,
|
|
5112
5438
|
workingDir: s.workingDir,
|
|
5113
5439
|
gitBranch: s.gitBranch,
|
|
5440
|
+
clientType: s.clientType,
|
|
5114
5441
|
status: s.status,
|
|
5115
5442
|
pid: s.pid,
|
|
5116
5443
|
startedAt: s.startedAt,
|
|
@@ -5122,60 +5449,52 @@ async function runWebUI(opts) {
|
|
|
5122
5449
|
currentTool: a.currentTool,
|
|
5123
5450
|
iterations: a.iterations,
|
|
5124
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,
|
|
5125
5458
|
lastActivityAt: a.lastActivityAt
|
|
5126
5459
|
}))
|
|
5127
5460
|
}));
|
|
5128
5461
|
broadcast({ type: "sessions.status_update", payload: { sessions: live } });
|
|
5129
5462
|
} catch {
|
|
5130
5463
|
}
|
|
5131
|
-
}
|
|
5464
|
+
};
|
|
5465
|
+
fleetBroadcastCli = broadcastSessions;
|
|
5466
|
+
const statusInterval = setInterval(() => void broadcastSessions(), 5e3);
|
|
5132
5467
|
if (statusInterval.unref) statusInterval.unref();
|
|
5133
5468
|
eventUnsubscribers.push(() => clearInterval(statusInterval));
|
|
5134
|
-
|
|
5135
|
-
});
|
|
5136
|
-
wss.on("connection", async (ws, req2) => {
|
|
5137
|
-
const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
5138
|
-
const tokenMatches = (provided) => {
|
|
5139
|
-
if (!provided) return false;
|
|
5140
|
-
const a = Buffer.from(provided);
|
|
5141
|
-
const b = Buffer.from(authToken);
|
|
5142
|
-
return a.length === b.length && crypto3.timingSafeEqual(a, b);
|
|
5143
|
-
};
|
|
5144
|
-
try {
|
|
5145
|
-
const url = new URL(req2.url ?? "/", `http://localhost:${port}`);
|
|
5146
|
-
const token = url.searchParams.get("token");
|
|
5147
|
-
const tokenOk = tokenMatches(token);
|
|
5148
|
-
const hostHeader = (req2.headers.host ?? "").trim();
|
|
5149
|
-
let hostOk = false;
|
|
5469
|
+
let regDebounce;
|
|
5150
5470
|
try {
|
|
5151
|
-
|
|
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
|
+
});
|
|
5152
5481
|
} catch {
|
|
5153
|
-
hostOk = false;
|
|
5154
|
-
}
|
|
5155
|
-
if (!hostOk) {
|
|
5156
|
-
ws.close(4003, "Forbidden: non-loopback Host header");
|
|
5157
|
-
return;
|
|
5158
|
-
}
|
|
5159
|
-
const origin = req2.headers.origin;
|
|
5160
|
-
if (origin) {
|
|
5161
|
-
try {
|
|
5162
|
-
const { hostname } = new URL(origin);
|
|
5163
|
-
if (!isLoopback(hostname) && !tokenOk) {
|
|
5164
|
-
ws.close(4003, "Forbidden: non-loopback origin requires auth token");
|
|
5165
|
-
return;
|
|
5166
|
-
}
|
|
5167
|
-
} catch {
|
|
5168
|
-
ws.close(4003, "Forbidden: invalid origin");
|
|
5169
|
-
return;
|
|
5170
|
-
}
|
|
5171
|
-
} else {
|
|
5172
|
-
if (!tokenOk) {
|
|
5173
|
-
ws.close(4003, "Forbidden: auth token required for non-browser clients");
|
|
5174
|
-
return;
|
|
5175
|
-
}
|
|
5176
5482
|
}
|
|
5177
|
-
|
|
5178
|
-
|
|
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");
|
|
5179
5498
|
return;
|
|
5180
5499
|
}
|
|
5181
5500
|
const client = { ws, sessionId: opts.session.id };
|
|
@@ -5500,10 +5819,84 @@ async function runWebUI(opts) {
|
|
|
5500
5819
|
}
|
|
5501
5820
|
return handleMemoryForget(ws, msg, opts.memoryStore);
|
|
5502
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
|
+
}
|
|
5503
5868
|
case "skills.list": {
|
|
5504
5869
|
await handleSkillsList(introspectionCtx, ws);
|
|
5505
5870
|
break;
|
|
5506
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
|
+
}
|
|
5507
5900
|
case "modes.list": {
|
|
5508
5901
|
await handleModesList(agentConfigCtx, ws);
|
|
5509
5902
|
break;
|
|
@@ -5676,7 +6069,7 @@ async function runWebUI(opts) {
|
|
|
5676
6069
|
// ── Mailbox operations — project-level inter-agent messaging ────
|
|
5677
6070
|
case "mailbox.messages": {
|
|
5678
6071
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5679
|
-
const globalRoot = opts.globalConfigPath ?
|
|
6072
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
5680
6073
|
if (!projectRoot || !globalRoot) {
|
|
5681
6074
|
send(ws, {
|
|
5682
6075
|
type: "mailbox.messages",
|
|
@@ -5725,7 +6118,7 @@ async function runWebUI(opts) {
|
|
|
5725
6118
|
}
|
|
5726
6119
|
case "mailbox.agents": {
|
|
5727
6120
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5728
|
-
const globalRoot = opts.globalConfigPath ?
|
|
6121
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
5729
6122
|
if (!projectRoot || !globalRoot) {
|
|
5730
6123
|
send(ws, {
|
|
5731
6124
|
type: "mailbox.agents",
|
|
@@ -5768,7 +6161,7 @@ async function runWebUI(opts) {
|
|
|
5768
6161
|
}
|
|
5769
6162
|
case "mailbox.clear": {
|
|
5770
6163
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5771
|
-
const globalRoot = opts.globalConfigPath ?
|
|
6164
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
5772
6165
|
if (!projectRoot || !globalRoot) {
|
|
5773
6166
|
send(ws, { type: "mailbox.cleared", payload: { error: "No project root available" } });
|
|
5774
6167
|
break;
|
|
@@ -5788,7 +6181,7 @@ async function runWebUI(opts) {
|
|
|
5788
6181
|
}
|
|
5789
6182
|
case "mailbox.purge": {
|
|
5790
6183
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5791
|
-
const globalRoot = opts.globalConfigPath ?
|
|
6184
|
+
const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
|
|
5792
6185
|
if (!projectRoot || !globalRoot) {
|
|
5793
6186
|
send(ws, { type: "mailbox.purged", payload: { error: "No project root available" } });
|
|
5794
6187
|
break;
|
|
@@ -5809,32 +6202,7 @@ async function runWebUI(opts) {
|
|
|
5809
6202
|
}
|
|
5810
6203
|
case "git.info": {
|
|
5811
6204
|
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
5812
|
-
|
|
5813
|
-
const { execFile: ef } = await import('child_process');
|
|
5814
|
-
const git = (args) => new Promise((resolve11) => {
|
|
5815
|
-
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
5816
|
-
resolve11(err ? "" : stdout.trim());
|
|
5817
|
-
});
|
|
5818
|
-
});
|
|
5819
|
-
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
5820
|
-
git(["branch", "--show-current"]),
|
|
5821
|
-
git(["diff", "--stat"]),
|
|
5822
|
-
git(["status", "--porcelain"]),
|
|
5823
|
-
git(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"])
|
|
5824
|
-
]);
|
|
5825
|
-
const branch = branchRaw || "(detached)";
|
|
5826
|
-
const addMatch = /(\d+)\s+insertion/i.exec(diffRaw);
|
|
5827
|
-
const delMatch = /(\d+)\s+deletion/i.exec(diffRaw);
|
|
5828
|
-
const added = addMatch ? Number(addMatch[1]) : 0;
|
|
5829
|
-
const deleted = delMatch ? Number(delMatch[1]) : 0;
|
|
5830
|
-
const untracked = statusRaw.split("\n").filter((l) => l.startsWith("??")).length;
|
|
5831
|
-
const [behindRaw, aheadRaw] = (upstreamRaw || "0 0").split(" ");
|
|
5832
|
-
const behind = Number(behindRaw) || 0;
|
|
5833
|
-
const ahead = Number(aheadRaw) || 0;
|
|
5834
|
-
send(ws, {
|
|
5835
|
-
type: "git.info",
|
|
5836
|
-
payload: { branch, added, deleted, untracked, ahead, behind }
|
|
5837
|
-
});
|
|
6205
|
+
await handleGitInfo(ws, projectRoot);
|
|
5838
6206
|
break;
|
|
5839
6207
|
}
|
|
5840
6208
|
default: {
|
|
@@ -6449,7 +6817,7 @@ var ReadlineInputReader = class {
|
|
|
6449
6817
|
history = [];
|
|
6450
6818
|
pending = false;
|
|
6451
6819
|
constructor(opts = {}) {
|
|
6452
|
-
this.historyFile = opts.historyFile ??
|
|
6820
|
+
this.historyFile = opts.historyFile ?? path4.join(wstackGlobalRoot(), "history");
|
|
6453
6821
|
}
|
|
6454
6822
|
async loadHistory() {
|
|
6455
6823
|
try {
|
|
@@ -6461,7 +6829,7 @@ var ReadlineInputReader = class {
|
|
|
6461
6829
|
}
|
|
6462
6830
|
async saveHistory() {
|
|
6463
6831
|
try {
|
|
6464
|
-
await fsp5.mkdir(
|
|
6832
|
+
await fsp5.mkdir(path4.dirname(this.historyFile), { recursive: true });
|
|
6465
6833
|
await fsp5.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
6466
6834
|
} catch {
|
|
6467
6835
|
}
|
|
@@ -6793,7 +7161,7 @@ function pickGroupIndex(opts) {
|
|
|
6793
7161
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
6794
7162
|
} catch {
|
|
6795
7163
|
}
|
|
6796
|
-
fs2.mkdirSync(
|
|
7164
|
+
fs2.mkdirSync(path4.dirname(opts.cursorFile), { recursive: true });
|
|
6797
7165
|
fs2.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
6798
7166
|
return current;
|
|
6799
7167
|
} catch {
|
|
@@ -6846,11 +7214,11 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
6846
7214
|
throw new FsError({
|
|
6847
7215
|
message: `Refusing to delete protected file: ${filename}`,
|
|
6848
7216
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
6849
|
-
path:
|
|
7217
|
+
path: path4.join(parentDir, filename),
|
|
6850
7218
|
context: { reason: "protected_basename" }
|
|
6851
7219
|
});
|
|
6852
7220
|
}
|
|
6853
|
-
if (filename !==
|
|
7221
|
+
if (filename !== path4.basename(filename)) {
|
|
6854
7222
|
throw new FsError({
|
|
6855
7223
|
message: `Refusing to delete path with traversal: ${filename}`,
|
|
6856
7224
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
@@ -6862,11 +7230,11 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
6862
7230
|
throw new FsError({
|
|
6863
7231
|
message: `Refusing to delete unknown file: ${filename}`,
|
|
6864
7232
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
6865
|
-
path:
|
|
7233
|
+
path: path4.join(parentDir, filename),
|
|
6866
7234
|
context: { reason: "unknown_file_pattern" }
|
|
6867
7235
|
});
|
|
6868
7236
|
}
|
|
6869
|
-
const resolvedParent =
|
|
7237
|
+
const resolvedParent = path4.resolve(parentDir);
|
|
6870
7238
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
6871
7239
|
throw new FsError({
|
|
6872
7240
|
message: `Unexpected parent directory for bak prune: ${resolvedParent}`,
|
|
@@ -6877,8 +7245,8 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
6877
7245
|
}
|
|
6878
7246
|
}
|
|
6879
7247
|
async function safeDelete(filePath) {
|
|
6880
|
-
const dir =
|
|
6881
|
-
const filename =
|
|
7248
|
+
const dir = path4.dirname(filePath);
|
|
7249
|
+
const filename = path4.basename(filePath);
|
|
6882
7250
|
try {
|
|
6883
7251
|
assertSafeToDelete(filename, dir);
|
|
6884
7252
|
await fsp5.unlink(filePath);
|
|
@@ -6923,16 +7291,16 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
6923
7291
|
}
|
|
6924
7292
|
var defaultHomeDir = () => os__default.homedir();
|
|
6925
7293
|
function historyDir(homeFn = defaultHomeDir) {
|
|
6926
|
-
return
|
|
7294
|
+
return path4.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
6927
7295
|
}
|
|
6928
7296
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
6929
|
-
return
|
|
7297
|
+
return path4.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
6930
7298
|
}
|
|
6931
7299
|
function configPath(homeFn = defaultHomeDir) {
|
|
6932
|
-
return
|
|
7300
|
+
return path4.join(homeFn(), ".wrongstack", "config.json");
|
|
6933
7301
|
}
|
|
6934
7302
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
6935
|
-
return
|
|
7303
|
+
return path4.join(homeFn(), ".wrongstack", "config.json.last");
|
|
6936
7304
|
}
|
|
6937
7305
|
function entryId(ts) {
|
|
6938
7306
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -6990,7 +7358,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
6990
7358
|
}
|
|
6991
7359
|
if (content !== void 0) {
|
|
6992
7360
|
try {
|
|
6993
|
-
const bakPath =
|
|
7361
|
+
const bakPath = path4.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
6994
7362
|
await atomicWrite(bakPath, content);
|
|
6995
7363
|
} catch (err) {
|
|
6996
7364
|
writeErr(
|
|
@@ -6999,11 +7367,11 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
6999
7367
|
}
|
|
7000
7368
|
}
|
|
7001
7369
|
try {
|
|
7002
|
-
const dir =
|
|
7370
|
+
const dir = path4.join(homeFn(), ".wrongstack");
|
|
7003
7371
|
const files = await fsp5.readdir(dir);
|
|
7004
7372
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
7005
7373
|
for (const f of baks.slice(10)) {
|
|
7006
|
-
await safeDelete(
|
|
7374
|
+
await safeDelete(path4.join(dir, f));
|
|
7007
7375
|
}
|
|
7008
7376
|
} catch (err) {
|
|
7009
7377
|
writeErr(
|
|
@@ -7024,7 +7392,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
7024
7392
|
};
|
|
7025
7393
|
try {
|
|
7026
7394
|
await fsp5.writeFile(
|
|
7027
|
-
|
|
7395
|
+
path4.join(historyDir(homeFn), `${id}.json`),
|
|
7028
7396
|
JSON.stringify(entry, null, 2),
|
|
7029
7397
|
"utf8"
|
|
7030
7398
|
);
|
|
@@ -7032,7 +7400,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
7032
7400
|
throw new FsError({
|
|
7033
7401
|
message: toErrorMessage(err),
|
|
7034
7402
|
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
7035
|
-
path:
|
|
7403
|
+
path: path4.join(historyDir(homeFn), `${id}.json`),
|
|
7036
7404
|
cause: err
|
|
7037
7405
|
});
|
|
7038
7406
|
}
|
|
@@ -7047,7 +7415,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
7047
7415
|
}
|
|
7048
7416
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
7049
7417
|
try {
|
|
7050
|
-
const raw = await fsp5.readFile(
|
|
7418
|
+
const raw = await fsp5.readFile(path4.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
7051
7419
|
return JSON.parse(raw);
|
|
7052
7420
|
} catch {
|
|
7053
7421
|
return null;
|
|
@@ -7161,11 +7529,11 @@ async function buildPickableProviders(modelsRegistry, config) {
|
|
|
7161
7529
|
var theme = { primary: color.amber };
|
|
7162
7530
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os__default.homedir()) {
|
|
7163
7531
|
try {
|
|
7164
|
-
const { atomicWrite:
|
|
7165
|
-
const
|
|
7532
|
+
const { atomicWrite: atomicWrite16 } = await import('@wrongstack/core');
|
|
7533
|
+
const fs38 = await import('fs/promises');
|
|
7166
7534
|
let existing = {};
|
|
7167
7535
|
try {
|
|
7168
|
-
const raw = await
|
|
7536
|
+
const raw = await fs38.readFile(configPath2, "utf8");
|
|
7169
7537
|
existing = JSON.parse(raw);
|
|
7170
7538
|
} catch {
|
|
7171
7539
|
}
|
|
@@ -7184,7 +7552,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
|
|
|
7184
7552
|
})
|
|
7185
7553
|
);
|
|
7186
7554
|
}
|
|
7187
|
-
await
|
|
7555
|
+
await atomicWrite16(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
7188
7556
|
try {
|
|
7189
7557
|
await appendHistory(
|
|
7190
7558
|
oldCfg,
|
|
@@ -7506,7 +7874,8 @@ function buildAuthCommand(opts) {
|
|
|
7506
7874
|
{
|
|
7507
7875
|
encrypt: (v) => v,
|
|
7508
7876
|
decrypt: (v) => v,
|
|
7509
|
-
isEncrypted: () => false
|
|
7877
|
+
isEncrypted: () => false,
|
|
7878
|
+
keyVersion: 1
|
|
7510
7879
|
}
|
|
7511
7880
|
);
|
|
7512
7881
|
} catch {
|
|
@@ -7847,7 +8216,7 @@ function formatPhaseList(graph) {
|
|
|
7847
8216
|
}
|
|
7848
8217
|
async function gatherProjectContext(projectRoot) {
|
|
7849
8218
|
try {
|
|
7850
|
-
const raw = await fsp5.readFile(
|
|
8219
|
+
const raw = await fsp5.readFile(path4.join(projectRoot, "package.json"), "utf8");
|
|
7851
8220
|
const pkg = JSON.parse(raw);
|
|
7852
8221
|
const parts = [
|
|
7853
8222
|
`Project: ${String(pkg.name ?? "unknown")}`,
|
|
@@ -9273,17 +9642,17 @@ function diagnoseConfig(cfg, plugins = []) {
|
|
|
9273
9642
|
function scanPlaintextSecrets(node, prefix, findings) {
|
|
9274
9643
|
if (!isPlainObject(node)) return;
|
|
9275
9644
|
for (const [key, value] of Object.entries(node)) {
|
|
9276
|
-
const
|
|
9645
|
+
const path39 = prefix ? `${prefix}.${key}` : key;
|
|
9277
9646
|
if (typeof value === "string") {
|
|
9278
9647
|
if (value.length > 0 && isSecretField$1(key) && !value.startsWith(ENC_PREFIX)) {
|
|
9279
9648
|
findings.push({
|
|
9280
|
-
path:
|
|
9649
|
+
path: path39,
|
|
9281
9650
|
problem: "looks like a plaintext secret (not vault-encrypted) \u2014 it will be encrypted on next boot",
|
|
9282
9651
|
severity: "warning"
|
|
9283
9652
|
});
|
|
9284
9653
|
}
|
|
9285
9654
|
} else if (isPlainObject(value)) {
|
|
9286
|
-
scanPlaintextSecrets(value,
|
|
9655
|
+
scanPlaintextSecrets(value, path39, findings);
|
|
9287
9656
|
}
|
|
9288
9657
|
}
|
|
9289
9658
|
}
|
|
@@ -9304,7 +9673,7 @@ function resolvePersistPath(deps) {
|
|
|
9304
9673
|
return deps.globalConfigPath;
|
|
9305
9674
|
}
|
|
9306
9675
|
async function ensureProjectDir(filePath) {
|
|
9307
|
-
const dir =
|
|
9676
|
+
const dir = path4.dirname(filePath);
|
|
9308
9677
|
try {
|
|
9309
9678
|
await fsp5.mkdir(dir, { recursive: true });
|
|
9310
9679
|
} catch {
|
|
@@ -9328,7 +9697,8 @@ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
|
|
|
9328
9697
|
"session",
|
|
9329
9698
|
"indexing",
|
|
9330
9699
|
"tools",
|
|
9331
|
-
"launch"
|
|
9700
|
+
"launch",
|
|
9701
|
+
"circuitBreaker"
|
|
9332
9702
|
]);
|
|
9333
9703
|
function filterSafeForProject(cfg) {
|
|
9334
9704
|
const out = {};
|
|
@@ -9508,8 +9878,8 @@ function buildDoctorCommand(opts) {
|
|
|
9508
9878
|
return ` ${icon} ${color.cyan(f.path)} \u2014 ${f.problem}${fix}`;
|
|
9509
9879
|
}
|
|
9510
9880
|
async function findParsableBackup(file) {
|
|
9511
|
-
const dir =
|
|
9512
|
-
const base =
|
|
9881
|
+
const dir = path4.dirname(file);
|
|
9882
|
+
const base = path4.basename(file);
|
|
9513
9883
|
const candidates = [`${base}.last`];
|
|
9514
9884
|
try {
|
|
9515
9885
|
const siblings = await fsp5.readdir(dir);
|
|
@@ -9520,7 +9890,7 @@ function buildDoctorCommand(opts) {
|
|
|
9520
9890
|
}
|
|
9521
9891
|
for (const name of candidates) {
|
|
9522
9892
|
try {
|
|
9523
|
-
const raw = await fsp5.readFile(
|
|
9893
|
+
const raw = await fsp5.readFile(path4.join(dir, name), "utf8");
|
|
9524
9894
|
JSON.parse(raw);
|
|
9525
9895
|
return { name, raw };
|
|
9526
9896
|
} catch {
|
|
@@ -9637,7 +10007,7 @@ function buildDoctorCommand(opts) {
|
|
|
9637
10007
|
await atomicWrite(target.file, JSON.stringify(report.fixed, null, 2));
|
|
9638
10008
|
if (!target.isProject) {
|
|
9639
10009
|
try {
|
|
9640
|
-
const homeFn = () =>
|
|
10010
|
+
const homeFn = () => path4.dirname(path4.dirname(target.file));
|
|
9641
10011
|
await appendHistory(parsed, report.fixed, "config doctor auto-fix", homeFn);
|
|
9642
10012
|
} catch {
|
|
9643
10013
|
}
|
|
@@ -11817,8 +12187,8 @@ function buildInitCommand(opts) {
|
|
|
11817
12187
|
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
11818
12188
|
async run(_args, ctx) {
|
|
11819
12189
|
const root = ctx?.projectRoot ?? opts.projectRoot ?? process.cwd();
|
|
11820
|
-
const dir =
|
|
11821
|
-
const file =
|
|
12190
|
+
const dir = path4.join(root, ".wrongstack");
|
|
12191
|
+
const file = path4.join(dir, "AGENTS.md");
|
|
11822
12192
|
const isFirstInit = !await fileExists(file);
|
|
11823
12193
|
const detected = await detectProjectFacts(root);
|
|
11824
12194
|
const body = renderAgentsTemplate(detected);
|
|
@@ -11826,7 +12196,7 @@ function buildInitCommand(opts) {
|
|
|
11826
12196
|
await fsp5.writeFile(file, body, "utf8");
|
|
11827
12197
|
let nodePkg = false;
|
|
11828
12198
|
try {
|
|
11829
|
-
await fsp5.access(
|
|
12199
|
+
await fsp5.access(path4.join(root, "package.json"));
|
|
11830
12200
|
nodePkg = true;
|
|
11831
12201
|
} catch {
|
|
11832
12202
|
}
|
|
@@ -12520,9 +12890,9 @@ function stateBadge(state) {
|
|
|
12520
12890
|
return color.dim(state);
|
|
12521
12891
|
}
|
|
12522
12892
|
}
|
|
12523
|
-
async function readConfig(
|
|
12893
|
+
async function readConfig(path39) {
|
|
12524
12894
|
try {
|
|
12525
|
-
return JSON.parse(await fsp5.readFile(
|
|
12895
|
+
return JSON.parse(await fsp5.readFile(path39, "utf8"));
|
|
12526
12896
|
} catch {
|
|
12527
12897
|
return {};
|
|
12528
12898
|
}
|
|
@@ -12530,11 +12900,11 @@ async function readConfig(path40) {
|
|
|
12530
12900
|
function isMcpServerRecord(value) {
|
|
12531
12901
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
12532
12902
|
}
|
|
12533
|
-
async function writeConfig(
|
|
12903
|
+
async function writeConfig(path39, cfg) {
|
|
12534
12904
|
const raw = JSON.stringify(cfg, null, 2);
|
|
12535
|
-
const tmp =
|
|
12905
|
+
const tmp = path39 + ".tmp";
|
|
12536
12906
|
await fsp5.writeFile(tmp, raw, "utf8");
|
|
12537
|
-
await fsp5.rename(tmp,
|
|
12907
|
+
await fsp5.rename(tmp, path39);
|
|
12538
12908
|
}
|
|
12539
12909
|
|
|
12540
12910
|
// src/slash-commands/mcp.ts
|
|
@@ -14653,6 +15023,73 @@ function formatSuggestions(suggestions) {
|
|
|
14653
15023
|
return lines.join("\n");
|
|
14654
15024
|
}
|
|
14655
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
|
+
|
|
14656
15093
|
// src/slash-commands/mouse.ts
|
|
14657
15094
|
function buildMouseCommand(_opts) {
|
|
14658
15095
|
return {
|
|
@@ -14833,7 +15270,7 @@ async function listProjectsCommand(opts, ctx) {
|
|
|
14833
15270
|
return { message: lines.join("\n") };
|
|
14834
15271
|
}
|
|
14835
15272
|
async function addProjectCommand(opts, ctx, targetPath, displayName) {
|
|
14836
|
-
const resolved =
|
|
15273
|
+
const resolved = path4.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), targetPath);
|
|
14837
15274
|
try {
|
|
14838
15275
|
await fsp5.access(resolved);
|
|
14839
15276
|
} catch {
|
|
@@ -14850,7 +15287,7 @@ async function addProjectCommand(opts, ctx, targetPath, displayName) {
|
|
|
14850
15287
|
message: color.yellow(`Project already registered: "${existing.name}" (${existing.slug})`)
|
|
14851
15288
|
};
|
|
14852
15289
|
}
|
|
14853
|
-
const name = displayName?.trim() ||
|
|
15290
|
+
const name = displayName?.trim() || path4.basename(resolved);
|
|
14854
15291
|
const slug = generateSlug(resolved);
|
|
14855
15292
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14856
15293
|
await ensureProjectDataDir(slug, opts.paths?.globalConfig);
|
|
@@ -14903,7 +15340,7 @@ async function removeProjectCommand(opts, _ctx, slugOrName) {
|
|
|
14903
15340
|
};
|
|
14904
15341
|
}
|
|
14905
15342
|
async function switchProjectCommand(opts, ctx, target, displayName) {
|
|
14906
|
-
const resolved =
|
|
15343
|
+
const resolved = path4.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), target);
|
|
14907
15344
|
try {
|
|
14908
15345
|
await fsp5.access(resolved);
|
|
14909
15346
|
} catch {
|
|
@@ -14917,8 +15354,8 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
|
|
|
14917
15354
|
try {
|
|
14918
15355
|
const req2 = createRequire(import.meta.url);
|
|
14919
15356
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
14920
|
-
const pkgDir =
|
|
14921
|
-
cliPath =
|
|
15357
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
15358
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
14922
15359
|
await fsp5.access(cliPath);
|
|
14923
15360
|
} catch {
|
|
14924
15361
|
cliPath = process.argv[1] ?? "";
|
|
@@ -14935,13 +15372,13 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
|
|
|
14935
15372
|
if (existing) {
|
|
14936
15373
|
existing.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
14937
15374
|
} else {
|
|
14938
|
-
const name = displayName?.trim() ||
|
|
15375
|
+
const name = displayName?.trim() || path4.basename(resolved);
|
|
14939
15376
|
const slug = generateSlug(resolved);
|
|
14940
15377
|
manifest.projects.push({ name, root: resolved, slug, lastSeen: (/* @__PURE__ */ new Date()).toISOString() });
|
|
14941
15378
|
await ensureProjectDataDir(slug, opts.paths?.globalConfig);
|
|
14942
15379
|
}
|
|
14943
15380
|
await saveManifest(manifest, opts.paths?.globalConfig);
|
|
14944
|
-
const targetName = displayName?.trim() ||
|
|
15381
|
+
const targetName = displayName?.trim() || path4.basename(resolved);
|
|
14945
15382
|
const canSwitch = await confirmProjectSwitch(opts, targetName);
|
|
14946
15383
|
if (!canSwitch) return { message: "" };
|
|
14947
15384
|
const nodeExe = process.execPath;
|
|
@@ -15057,8 +15494,8 @@ async function spawnInProject(opts, _ctx, root, projectName) {
|
|
|
15057
15494
|
try {
|
|
15058
15495
|
const req2 = createRequire(import.meta.url);
|
|
15059
15496
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
15060
|
-
const pkgDir =
|
|
15061
|
-
cliPath =
|
|
15497
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
15498
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
15062
15499
|
await fsp5.access(cliPath);
|
|
15063
15500
|
} catch {
|
|
15064
15501
|
cliPath = process.argv[1] ?? "";
|
|
@@ -15075,7 +15512,7 @@ async function spawnInProject(opts, _ctx, root, projectName) {
|
|
|
15075
15512
|
if (existing) {
|
|
15076
15513
|
existing.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
15077
15514
|
} else {
|
|
15078
|
-
const name = projectName ||
|
|
15515
|
+
const name = projectName || path4.basename(root);
|
|
15079
15516
|
const slug = generateSlug(root);
|
|
15080
15517
|
manifest.projects.push({ name, root, slug, lastSeen: (/* @__PURE__ */ new Date()).toISOString() });
|
|
15081
15518
|
await ensureProjectDataDir(slug, opts.paths?.globalConfig);
|
|
@@ -15107,8 +15544,8 @@ async function handleNewSession(_opts, _ctx) {
|
|
|
15107
15544
|
try {
|
|
15108
15545
|
const req2 = createRequire(import.meta.url);
|
|
15109
15546
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
15110
|
-
const pkgDir =
|
|
15111
|
-
cliPath =
|
|
15547
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
15548
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
15112
15549
|
await fsp5.access(cliPath);
|
|
15113
15550
|
} catch {
|
|
15114
15551
|
cliPath = process.argv[1] ?? "";
|
|
@@ -15219,7 +15656,7 @@ function buildReviewCommand(opts) {
|
|
|
15219
15656
|
for (const f of allChanged) {
|
|
15220
15657
|
if (f.path.startsWith(".wrongstack/")) continue;
|
|
15221
15658
|
try {
|
|
15222
|
-
await fsp5.access(
|
|
15659
|
+
await fsp5.access(path4.join(cwd, f.path));
|
|
15223
15660
|
existing.push(f);
|
|
15224
15661
|
} catch {
|
|
15225
15662
|
}
|
|
@@ -15230,7 +15667,7 @@ function buildReviewCommand(opts) {
|
|
|
15230
15667
|
const filesWithContent = [];
|
|
15231
15668
|
for (const f of existing.slice(0, 30)) {
|
|
15232
15669
|
try {
|
|
15233
|
-
const content = await fsp5.readFile(
|
|
15670
|
+
const content = await fsp5.readFile(path4.join(cwd, f.path), "utf8");
|
|
15234
15671
|
filesWithContent.push({ ...f, content });
|
|
15235
15672
|
} catch {
|
|
15236
15673
|
}
|
|
@@ -15264,10 +15701,13 @@ function buildSettingsCommand(opts) {
|
|
|
15264
15701
|
" /settings hints on|off Show or suppress rotating launch hints",
|
|
15265
15702
|
" /settings debug-stream on|off Raw SSE hex-dump to stderr for debugging",
|
|
15266
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)",
|
|
15267
15705
|
" /settings refine on|off Enable/disable prompt refinement",
|
|
15268
15706
|
" /settings refine-delay <seconds> Countdown duration for refine preview",
|
|
15269
15707
|
" /settings refine-language original|english Default language for refinement",
|
|
15270
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)",
|
|
15271
15711
|
" /settings defaults Show built-in default values",
|
|
15272
15712
|
"",
|
|
15273
15713
|
"Settings are persisted to ~/.wrongstack/config.json."
|
|
@@ -15279,10 +15719,14 @@ function buildSettingsCommand(opts) {
|
|
|
15279
15719
|
const hints = opts.configStore.get().hints !== false;
|
|
15280
15720
|
const debugStream = opts.configStore.get().debugStream === true;
|
|
15281
15721
|
const configScope = opts.configStore.get().configScope ?? "global";
|
|
15722
|
+
const fsAccess = opts.configStore.get().tools?.restrictToProjectRoot === true ? "project" : "unrestricted";
|
|
15282
15723
|
const enhanceEnabled = autonomy?.enhance ?? true;
|
|
15283
15724
|
const enhanceDelay = autonomy?.enhanceDelayMs ?? 6e4;
|
|
15284
15725
|
const enhanceLanguage = autonomy?.enhanceLanguage ?? "original";
|
|
15285
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;
|
|
15286
15730
|
return [
|
|
15287
15731
|
`${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
|
|
15288
15732
|
"",
|
|
@@ -15291,10 +15735,12 @@ function buildSettingsCommand(opts) {
|
|
|
15291
15735
|
` launch hints: ${hints ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings hints on|off")}`,
|
|
15292
15736
|
` debug stream: ${debugStream ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings debug-stream on|off")}`,
|
|
15293
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")}`,
|
|
15294
15739
|
` refine: ${enhanceEnabled ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings refine on|off")}`,
|
|
15295
15740
|
` refine-delay: ${color.cyan(formatDelay(enhanceDelay))} ${color.dim("change: /settings refine-delay <seconds>")}`,
|
|
15296
15741
|
` refine-language: ${color.cyan(enhanceLanguage)} ${color.dim("change: /settings refine-language original|english")}`,
|
|
15297
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")}`,
|
|
15298
15744
|
"",
|
|
15299
15745
|
color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
|
|
15300
15746
|
].join("\n");
|
|
@@ -15407,6 +15853,25 @@ function buildSettingsCommand(opts) {
|
|
|
15407
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`;
|
|
15408
15854
|
return { message: `${color.green("\u2713")} config scope \u2192 ${label}` };
|
|
15409
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
|
+
}
|
|
15410
15875
|
if (sub === "refine") {
|
|
15411
15876
|
const raw = (rest[0] ?? "").toLowerCase();
|
|
15412
15877
|
if (!["on", "off"].includes(raw)) {
|
|
@@ -15467,8 +15932,46 @@ function buildSettingsCommand(opts) {
|
|
|
15467
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")}`
|
|
15468
15933
|
};
|
|
15469
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
|
+
}
|
|
15470
15973
|
return {
|
|
15471
|
-
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")}`
|
|
15472
15975
|
};
|
|
15473
15976
|
} catch (err) {
|
|
15474
15977
|
return {
|
|
@@ -15601,7 +16104,7 @@ var DEFAULTS = {
|
|
|
15601
16104
|
working_dir: true
|
|
15602
16105
|
};
|
|
15603
16106
|
function resolveConfigPath() {
|
|
15604
|
-
return process.env[CONFIG_ENV] ??
|
|
16107
|
+
return process.env[CONFIG_ENV] ?? path4.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
15605
16108
|
}
|
|
15606
16109
|
async function loadStatuslineConfig() {
|
|
15607
16110
|
const p = resolveConfigPath();
|
|
@@ -15615,7 +16118,7 @@ async function loadStatuslineConfig() {
|
|
|
15615
16118
|
async function saveStatuslineConfig(cfg) {
|
|
15616
16119
|
const p = resolveConfigPath();
|
|
15617
16120
|
try {
|
|
15618
|
-
await fsp5.mkdir(
|
|
16121
|
+
await fsp5.mkdir(path4.dirname(p), { recursive: true });
|
|
15619
16122
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
15620
16123
|
} catch (err) {
|
|
15621
16124
|
throw new FsError({
|
|
@@ -15780,7 +16283,7 @@ function buildTasksCommand(_opts) {
|
|
|
15780
16283
|
return {
|
|
15781
16284
|
name: "tasks",
|
|
15782
16285
|
category: "Inspect",
|
|
15783
|
-
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.',
|
|
15784
16287
|
help: [
|
|
15785
16288
|
"Usage:",
|
|
15786
16289
|
" /tasks Show task progress + list",
|
|
@@ -15797,7 +16300,8 @@ function buildTasksCommand(_opts) {
|
|
|
15797
16300
|
" /tasks clear Remove all tasks",
|
|
15798
16301
|
"",
|
|
15799
16302
|
"Types: feature, bugfix, refactor, docs, test, chore",
|
|
15800
|
-
"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'
|
|
15801
16305
|
].join("\n"),
|
|
15802
16306
|
async run(args, ctx) {
|
|
15803
16307
|
if (!ctx) {
|
|
@@ -15849,9 +16353,23 @@ ${formatPlan(updated)}`;
|
|
|
15849
16353
|
return file;
|
|
15850
16354
|
}
|
|
15851
16355
|
const parts = restJoined.split(/\s+/);
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
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(" ");
|
|
15855
16373
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15856
16374
|
file.tasks.push({
|
|
15857
16375
|
id: `task_${randomUUID()}`,
|
|
@@ -16016,7 +16534,7 @@ ${formatTaskProgress(file.tasks)}`;
|
|
|
16016
16534
|
"clear"
|
|
16017
16535
|
],
|
|
16018
16536
|
"tasks"
|
|
16019
|
-
);
|
|
16537
|
+
) + "\n\nRelated: /plan (session-persistent roadmap) | /todos (per-turn list)";
|
|
16020
16538
|
return file;
|
|
16021
16539
|
}
|
|
16022
16540
|
return file;
|
|
@@ -16027,13 +16545,13 @@ ${formatTaskProgress(file.tasks)}`;
|
|
|
16027
16545
|
}
|
|
16028
16546
|
async function discoverPackageFiles(projectRoot) {
|
|
16029
16547
|
const files = [];
|
|
16030
|
-
const rootPkg =
|
|
16548
|
+
const rootPkg = path4.join(projectRoot, "package.json");
|
|
16031
16549
|
try {
|
|
16032
16550
|
await fsp5.access(rootPkg);
|
|
16033
16551
|
files.push(rootPkg);
|
|
16034
16552
|
} catch {
|
|
16035
16553
|
}
|
|
16036
|
-
const workspaceFile =
|
|
16554
|
+
const workspaceFile = path4.join(projectRoot, "pnpm-workspace.yaml");
|
|
16037
16555
|
try {
|
|
16038
16556
|
await fsp5.access(workspaceFile);
|
|
16039
16557
|
const content = await fsp5.readFile(workspaceFile, "utf8");
|
|
@@ -16043,12 +16561,12 @@ async function discoverPackageFiles(projectRoot) {
|
|
|
16043
16561
|
const globs = rawGlobs.split(/[\s,]+/).filter(Boolean).map((g) => g.replace(/['"]/g, ""));
|
|
16044
16562
|
for (const g of globs) {
|
|
16045
16563
|
const dirPrefix = g.replace(/\/?\*$/, "").replace(/\/\*$/, "");
|
|
16046
|
-
const dir =
|
|
16564
|
+
const dir = path4.join(projectRoot, dirPrefix);
|
|
16047
16565
|
try {
|
|
16048
16566
|
const entries = await fsp5.readdir(dir, { withFileTypes: true });
|
|
16049
16567
|
for (const e of entries) {
|
|
16050
16568
|
if (!e.isDirectory()) continue;
|
|
16051
|
-
const subPkg =
|
|
16569
|
+
const subPkg = path4.join(dir, e.name, "package.json");
|
|
16052
16570
|
try {
|
|
16053
16571
|
await fsp5.access(subPkg);
|
|
16054
16572
|
files.push(subPkg);
|
|
@@ -16063,7 +16581,7 @@ async function discoverPackageFiles(projectRoot) {
|
|
|
16063
16581
|
return files;
|
|
16064
16582
|
}
|
|
16065
16583
|
function buildTechStackTask(opts) {
|
|
16066
|
-
const pkgList = opts.packageFiles.map((f) => ` - ${
|
|
16584
|
+
const pkgList = opts.packageFiles.map((f) => ` - ${path4.relative(opts.projectRoot, f)}`).join("\n");
|
|
16067
16585
|
const header = opts.isInit ? [
|
|
16068
16586
|
"## Tech Stack Audit \u2014 First-Time Project Init",
|
|
16069
16587
|
"",
|
|
@@ -16548,7 +17066,7 @@ function buildTodosCommand(opts) {
|
|
|
16548
17066
|
return {
|
|
16549
17067
|
name: "todos",
|
|
16550
17068
|
category: "Inspect",
|
|
16551
|
-
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).",
|
|
16552
17070
|
async run(args) {
|
|
16553
17071
|
const ctx = opts.context;
|
|
16554
17072
|
if (!ctx) return { message: "No active context." };
|
|
@@ -16602,7 +17120,7 @@ function buildTodosCommand(opts) {
|
|
|
16602
17120
|
}
|
|
16603
17121
|
default:
|
|
16604
17122
|
return {
|
|
16605
|
-
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)"
|
|
16606
17124
|
};
|
|
16607
17125
|
}
|
|
16608
17126
|
}
|
|
@@ -16648,7 +17166,7 @@ function buildWorkingDirCommand(_opts) {
|
|
|
16648
17166
|
}
|
|
16649
17167
|
const trimmed = args.trim();
|
|
16650
17168
|
if (!trimmed) {
|
|
16651
|
-
const rel2 =
|
|
17169
|
+
const rel2 = path4.relative(ctx.projectRoot, ctx.workingDir) || ".";
|
|
16652
17170
|
return {
|
|
16653
17171
|
message: [
|
|
16654
17172
|
`Working directory: ${color.bold(ctx.workingDir)}`,
|
|
@@ -16657,10 +17175,10 @@ function buildWorkingDirCommand(_opts) {
|
|
|
16657
17175
|
].join("\n")
|
|
16658
17176
|
};
|
|
16659
17177
|
}
|
|
16660
|
-
const resolved =
|
|
16661
|
-
const root =
|
|
16662
|
-
const rel =
|
|
16663
|
-
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)) {
|
|
16664
17182
|
return {
|
|
16665
17183
|
message: color.red(
|
|
16666
17184
|
`Directory "${trimmed}" is outside the project root.
|
|
@@ -16685,8 +17203,8 @@ function buildWorkingDirCommand(_opts) {
|
|
|
16685
17203
|
message: color.red(toErrorMessage(err))
|
|
16686
17204
|
};
|
|
16687
17205
|
}
|
|
16688
|
-
const prevRel =
|
|
16689
|
-
const newRel =
|
|
17206
|
+
const prevRel = path4.relative(ctx.projectRoot, previous) || ".";
|
|
17207
|
+
const newRel = path4.relative(ctx.projectRoot, resolved) || ".";
|
|
16690
17208
|
return {
|
|
16691
17209
|
message: [
|
|
16692
17210
|
color.green(` \u2713 ${prevRel} \u2192 ${color.bold(newRel)}`),
|
|
@@ -16840,6 +17358,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
16840
17358
|
buildMouseCommand(),
|
|
16841
17359
|
buildAutonomyCommand(opts),
|
|
16842
17360
|
buildGoalCommand(opts),
|
|
17361
|
+
buildCoordinatorCommand(opts),
|
|
16843
17362
|
buildBrainCommand(opts),
|
|
16844
17363
|
buildBtwCommand(opts),
|
|
16845
17364
|
buildNextCommand(opts),
|
|
@@ -16886,13 +17405,13 @@ var MANIFESTS = [
|
|
|
16886
17405
|
];
|
|
16887
17406
|
async function detectProjectKind(projectRoot) {
|
|
16888
17407
|
try {
|
|
16889
|
-
await fsp5.access(
|
|
17408
|
+
await fsp5.access(path4.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
16890
17409
|
return "initialized";
|
|
16891
17410
|
} catch {
|
|
16892
17411
|
}
|
|
16893
17412
|
for (const m of MANIFESTS) {
|
|
16894
17413
|
try {
|
|
16895
|
-
await fsp5.access(
|
|
17414
|
+
await fsp5.access(path4.join(projectRoot, m));
|
|
16896
17415
|
return "project";
|
|
16897
17416
|
} catch {
|
|
16898
17417
|
}
|
|
@@ -16900,8 +17419,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
16900
17419
|
return "empty";
|
|
16901
17420
|
}
|
|
16902
17421
|
async function scaffoldAgentsMd(projectRoot) {
|
|
16903
|
-
const dir =
|
|
16904
|
-
const file =
|
|
17422
|
+
const dir = path4.join(projectRoot, ".wrongstack");
|
|
17423
|
+
const file = path4.join(dir, "AGENTS.md");
|
|
16905
17424
|
const facts = await detectProjectFacts(projectRoot);
|
|
16906
17425
|
const body = renderAgentsTemplate(facts);
|
|
16907
17426
|
await fsp5.mkdir(dir, { recursive: true });
|
|
@@ -16914,7 +17433,7 @@ async function runProjectCheck(opts) {
|
|
|
16914
17433
|
if (kind === "initialized") {
|
|
16915
17434
|
renderer.write(
|
|
16916
17435
|
`
|
|
16917
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
17436
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path4.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
16918
17437
|
`
|
|
16919
17438
|
);
|
|
16920
17439
|
return true;
|
|
@@ -16945,7 +17464,7 @@ async function runProjectCheck(opts) {
|
|
|
16945
17464
|
}
|
|
16946
17465
|
return true;
|
|
16947
17466
|
}
|
|
16948
|
-
const gitDir =
|
|
17467
|
+
const gitDir = path4.join(projectRoot, ".git");
|
|
16949
17468
|
let hasGit = false;
|
|
16950
17469
|
try {
|
|
16951
17470
|
await fsp5.access(gitDir);
|
|
@@ -17180,11 +17699,11 @@ async function countProjectFiles(projectRoot, threshold) {
|
|
|
17180
17699
|
for (const e of entries) {
|
|
17181
17700
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
17182
17701
|
if (count >= threshold) return;
|
|
17183
|
-
const full =
|
|
17702
|
+
const full = path4.join(dir, e.name);
|
|
17184
17703
|
if (e.isDirectory()) {
|
|
17185
17704
|
await walk(full);
|
|
17186
17705
|
} else if (e.isFile()) {
|
|
17187
|
-
if (INDEXABLE_EXTS.has(
|
|
17706
|
+
if (INDEXABLE_EXTS.has(path4.extname(e.name))) {
|
|
17188
17707
|
count++;
|
|
17189
17708
|
}
|
|
17190
17709
|
}
|
|
@@ -17525,14 +18044,14 @@ function summarize(value, name) {
|
|
|
17525
18044
|
if (typeof v === "object" && v !== null) {
|
|
17526
18045
|
const o = v;
|
|
17527
18046
|
if (name === "edit") {
|
|
17528
|
-
const
|
|
18047
|
+
const path39 = typeof o["path"] === "string" ? o["path"] : "";
|
|
17529
18048
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
17530
|
-
return `${
|
|
18049
|
+
return `${path39} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
17531
18050
|
}
|
|
17532
18051
|
if (name === "write") {
|
|
17533
|
-
const
|
|
18052
|
+
const path39 = typeof o["path"] === "string" ? o["path"] : "";
|
|
17534
18053
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
17535
|
-
return bytes !== void 0 ? `${
|
|
18054
|
+
return bytes !== void 0 ? `${path39} ${bytes}B` : path39;
|
|
17536
18055
|
}
|
|
17537
18056
|
if (typeof o["count"] === "number") {
|
|
17538
18057
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -17869,14 +18388,14 @@ var auditCmd = async (args, deps) => {
|
|
|
17869
18388
|
return verify.ok ? 0 : 1;
|
|
17870
18389
|
};
|
|
17871
18390
|
async function listAudits(log, dir, deps) {
|
|
17872
|
-
const
|
|
17873
|
-
const
|
|
18391
|
+
const fs38 = await import('fs/promises');
|
|
18392
|
+
const path39 = await import('path');
|
|
17874
18393
|
const out = [];
|
|
17875
18394
|
let foundRoot = true;
|
|
17876
18395
|
const scan = async (scanDir, prefix, depth) => {
|
|
17877
18396
|
let entries;
|
|
17878
18397
|
try {
|
|
17879
|
-
entries = await
|
|
18398
|
+
entries = await fs38.readdir(scanDir, { withFileTypes: true });
|
|
17880
18399
|
} catch {
|
|
17881
18400
|
if (depth === 0) foundRoot = false;
|
|
17882
18401
|
return;
|
|
@@ -17884,7 +18403,7 @@ async function listAudits(log, dir, deps) {
|
|
|
17884
18403
|
for (const entry of entries) {
|
|
17885
18404
|
if (entry.name.startsWith(".")) continue;
|
|
17886
18405
|
if (entry.isDirectory()) {
|
|
17887
|
-
if (depth === 0) await scan(
|
|
18406
|
+
if (depth === 0) await scan(path39.join(scanDir, entry.name), entry.name, depth + 1);
|
|
17888
18407
|
continue;
|
|
17889
18408
|
}
|
|
17890
18409
|
if (!entry.isFile() || !entry.name.endsWith(".audit.jsonl")) continue;
|
|
@@ -18878,7 +19397,7 @@ async function resolveWstackEntry() {
|
|
|
18878
19397
|
try {
|
|
18879
19398
|
const req2 = createRequire(import.meta.url);
|
|
18880
19399
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
18881
|
-
const entry =
|
|
19400
|
+
const entry = path4.join(path4.dirname(pkgPath), "dist", "index.js");
|
|
18882
19401
|
await fsp5.access(entry);
|
|
18883
19402
|
return entry;
|
|
18884
19403
|
} catch {
|
|
@@ -18893,7 +19412,7 @@ async function benchRun(_args, deps) {
|
|
|
18893
19412
|
const outBase = flagStr(deps, "out") ?? "bench-results";
|
|
18894
19413
|
let config;
|
|
18895
19414
|
try {
|
|
18896
|
-
config = await loadBenchConfig(
|
|
19415
|
+
config = await loadBenchConfig(path4.resolve(deps.cwd, modelsPath));
|
|
18897
19416
|
} catch (err) {
|
|
18898
19417
|
deps.renderer.writeError(toErrorMessage(err));
|
|
18899
19418
|
return 1;
|
|
@@ -18904,8 +19423,8 @@ async function benchRun(_args, deps) {
|
|
|
18904
19423
|
if (c > 0) config.concurrency = c;
|
|
18905
19424
|
}
|
|
18906
19425
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
18907
|
-
const outDir =
|
|
18908
|
-
const predictionsDir =
|
|
19426
|
+
const outDir = path4.resolve(deps.cwd, outBase, stamp);
|
|
19427
|
+
const predictionsDir = path4.join(outDir, "predictions");
|
|
18909
19428
|
let suite;
|
|
18910
19429
|
let grade;
|
|
18911
19430
|
let isSwebench = false;
|
|
@@ -18917,14 +19436,14 @@ async function benchRun(_args, deps) {
|
|
|
18917
19436
|
}
|
|
18918
19437
|
const languagesRaw = flagStr(deps, "languages");
|
|
18919
19438
|
const languages = languagesRaw ? languagesRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
18920
|
-
suite = createPolyglotSuite({ polyglotDir:
|
|
19439
|
+
suite = createPolyglotSuite({ polyglotDir: path4.resolve(deps.cwd, polyglotDir), languages });
|
|
18921
19440
|
grade = (a) => gradePolyglot(a);
|
|
18922
19441
|
} else if (suiteId === "swebench") {
|
|
18923
19442
|
isSwebench = true;
|
|
18924
19443
|
const datasetDir = flagStr(deps, "dataset-dir");
|
|
18925
19444
|
const docker = flagBool(deps, "docker");
|
|
18926
19445
|
suite = createSwebenchSuite({
|
|
18927
|
-
datasetDir: datasetDir ?
|
|
19446
|
+
datasetDir: datasetDir ? path4.resolve(deps.cwd, datasetDir) : void 0,
|
|
18928
19447
|
docker
|
|
18929
19448
|
});
|
|
18930
19449
|
grade = (a) => gradeSwebench({ ...a, predictionsDir });
|
|
@@ -18954,7 +19473,7 @@ async function benchRun(_args, deps) {
|
|
|
18954
19473
|
}
|
|
18955
19474
|
await writeJsonArtifacts(outDir, report);
|
|
18956
19475
|
const md = renderMarkdownReport(report);
|
|
18957
|
-
await fsp5.writeFile(
|
|
19476
|
+
await fsp5.writeFile(path4.join(outDir, "report.md"), md, "utf8");
|
|
18958
19477
|
deps.renderer.write("\n" + md + "\n");
|
|
18959
19478
|
if (isSwebench) {
|
|
18960
19479
|
for (const cell of config.cells) {
|
|
@@ -18967,7 +19486,7 @@ async function benchRun(_args, deps) {
|
|
|
18967
19486
|
"Grade with the official SWE-bench harness: python -m swebench.harness.run_evaluation --predictions_path <file> --run_id <id>"
|
|
18968
19487
|
);
|
|
18969
19488
|
}
|
|
18970
|
-
deps.renderer.writeInfo(`Report written to ${
|
|
19489
|
+
deps.renderer.writeInfo(`Report written to ${path4.join(outDir, "report.md")}`);
|
|
18971
19490
|
return 0;
|
|
18972
19491
|
}
|
|
18973
19492
|
async function benchReport(args, deps) {
|
|
@@ -18976,7 +19495,7 @@ async function benchReport(args, deps) {
|
|
|
18976
19495
|
deps.renderer.writeError("Usage: wstack bench report <run-directory>");
|
|
18977
19496
|
return 1;
|
|
18978
19497
|
}
|
|
18979
|
-
const outDir =
|
|
19498
|
+
const outDir = path4.resolve(deps.cwd, dir);
|
|
18980
19499
|
let summary;
|
|
18981
19500
|
try {
|
|
18982
19501
|
summary = await readSummary(outDir);
|
|
@@ -18987,7 +19506,7 @@ async function benchReport(args, deps) {
|
|
|
18987
19506
|
return 1;
|
|
18988
19507
|
}
|
|
18989
19508
|
const md = renderMarkdownReport(summary);
|
|
18990
|
-
await fsp5.writeFile(
|
|
19509
|
+
await fsp5.writeFile(path4.join(outDir, "report.md"), md, "utf8");
|
|
18991
19510
|
deps.renderer.write("\n" + md + "\n");
|
|
18992
19511
|
return 0;
|
|
18993
19512
|
}
|
|
@@ -19002,7 +19521,7 @@ async function benchList(_args, deps) {
|
|
|
19002
19521
|
const modelsPath = flagStr(deps, "models");
|
|
19003
19522
|
if (modelsPath) {
|
|
19004
19523
|
try {
|
|
19005
|
-
const config = await loadBenchConfig(
|
|
19524
|
+
const config = await loadBenchConfig(path4.resolve(deps.cwd, modelsPath));
|
|
19006
19525
|
deps.renderer.write("\n" + color.bold("Model cells\n"));
|
|
19007
19526
|
for (const cell of config.cells) {
|
|
19008
19527
|
deps.renderer.write(
|
|
@@ -19057,14 +19576,14 @@ var doctorCmd = async (_args, deps) => {
|
|
|
19057
19576
|
checks.push({
|
|
19058
19577
|
name: "provider",
|
|
19059
19578
|
status: "fail",
|
|
19060
|
-
detail: "no provider configured \u2014 run `wstack
|
|
19579
|
+
detail: "no provider configured \u2014 run `wstack auth` to set up"
|
|
19061
19580
|
});
|
|
19062
19581
|
else checks.push({ name: "provider", status: "ok", detail: cfg.provider });
|
|
19063
19582
|
if (!cfg.model)
|
|
19064
19583
|
checks.push({
|
|
19065
19584
|
name: "model",
|
|
19066
19585
|
status: "fail",
|
|
19067
|
-
detail: "no model configured \u2014 run `wstack
|
|
19586
|
+
detail: "no model configured \u2014 run `wstack auth` to configure"
|
|
19068
19587
|
});
|
|
19069
19588
|
else checks.push({ name: "model", status: "ok", detail: cfg.model });
|
|
19070
19589
|
if (cfg.provider) {
|
|
@@ -19119,7 +19638,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
19119
19638
|
}
|
|
19120
19639
|
try {
|
|
19121
19640
|
await fsp5.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
19122
|
-
const probe =
|
|
19641
|
+
const probe = path4.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
19123
19642
|
await fsp5.writeFile(probe, "");
|
|
19124
19643
|
await fsp5.unlink(probe);
|
|
19125
19644
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -19222,8 +19741,8 @@ var exportCmd = async (args, deps) => {
|
|
|
19222
19741
|
return 1;
|
|
19223
19742
|
}
|
|
19224
19743
|
if (output) {
|
|
19225
|
-
await fsp5.mkdir(
|
|
19226
|
-
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");
|
|
19227
19746
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
19228
19747
|
`);
|
|
19229
19748
|
} else {
|
|
@@ -19232,80 +19751,23 @@ var exportCmd = async (args, deps) => {
|
|
|
19232
19751
|
}
|
|
19233
19752
|
return 0;
|
|
19234
19753
|
};
|
|
19235
|
-
|
|
19236
|
-
// src/subcommands/handlers/init.ts
|
|
19237
|
-
init_helpers();
|
|
19238
19754
|
var initCmd = async (_args, deps) => {
|
|
19239
|
-
deps.renderer.write(color.bold("WrongStack init\n"));
|
|
19240
|
-
deps.renderer.
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
|
|
19253
|
-
deps.renderer.write(
|
|
19254
|
-
`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.
|
|
19255
19769
|
`
|
|
19256
|
-
|
|
19257
|
-
const defaultId = ranked[0]?.id ?? "anthropic";
|
|
19258
|
-
const providerAnswer = (await deps.reader.readLine(`Provider [${defaultId}]: `)).trim();
|
|
19259
|
-
if (providerAnswer === "q") {
|
|
19260
|
-
deps.renderer.write(color.dim("Cancelled.\n"));
|
|
19261
|
-
return 0;
|
|
19262
|
-
}
|
|
19263
|
-
const providerId = providerAnswer || defaultId;
|
|
19264
|
-
const provider = await deps.modelsRegistry.getProvider(providerId);
|
|
19265
|
-
if (!provider) {
|
|
19266
|
-
deps.renderer.writeError(`Provider "${providerId}" not found in models.dev catalog.`);
|
|
19267
|
-
return 1;
|
|
19268
|
-
}
|
|
19269
|
-
if (provider.family === "unsupported") {
|
|
19270
|
-
deps.renderer.writeError(
|
|
19271
|
-
`Provider "${providerId}" uses ${provider.npm} which has no built-in transport. Install a plugin to enable it.`
|
|
19272
|
-
);
|
|
19273
|
-
return 1;
|
|
19274
|
-
}
|
|
19275
|
-
const suggestedModel = await deps.modelsRegistry.suggestModel(providerId) ?? "";
|
|
19276
|
-
const modelHint = suggestedModel ? ` [${suggestedModel}]` : "";
|
|
19277
|
-
const modelAnswer = (await deps.reader.readLine(`Model${modelHint}: `)).trim();
|
|
19278
|
-
if (modelAnswer === "q") {
|
|
19279
|
-
deps.renderer.write(color.dim("Cancelled.\n"));
|
|
19280
|
-
return 0;
|
|
19281
|
-
}
|
|
19282
|
-
const modelId = modelAnswer || suggestedModel;
|
|
19283
|
-
if (!modelId) {
|
|
19284
|
-
deps.renderer.writeError("No model selected. Aborting.");
|
|
19285
|
-
return 1;
|
|
19286
|
-
}
|
|
19287
|
-
const envHit = provider.envVars.map((v) => process.env[v]).find(Boolean);
|
|
19288
|
-
let apiKey = "";
|
|
19289
|
-
if (!envHit) {
|
|
19290
|
-
apiKey = (await deps.reader.readLine(
|
|
19291
|
-
`API key (stored in ${deps.paths.globalConfig}; empty = expect ${provider.envVars[0] ?? "env var"}): `
|
|
19292
|
-
)).trim();
|
|
19293
|
-
} else {
|
|
19294
|
-
deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
|
|
19295
|
-
}
|
|
19296
|
-
await fsp5.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
19297
|
-
const config = { version: 1, provider: providerId, model: modelId };
|
|
19298
|
-
if (apiKey) config.apiKey = apiKey;
|
|
19299
|
-
const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
|
|
19300
|
-
const encrypted = encryptConfigSecrets$1(config, vault);
|
|
19301
|
-
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
19302
|
-
await fsp5.mkdir(path39.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
19303
|
-
const agentsFile = path39.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
19304
|
-
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
19305
|
-
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
19306
|
-
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
19307
|
-
deps.renderer.writeInfo(`Project state lives in ${deps.paths.projectDir}`);
|
|
19308
|
-
deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
|
|
19770
|
+
);
|
|
19309
19771
|
return 0;
|
|
19310
19772
|
};
|
|
19311
19773
|
var AllowAllPermissionPolicy = class extends AutoApprovePermissionPolicy {
|
|
@@ -19321,7 +19783,7 @@ function parseToolsFlag(flags) {
|
|
|
19321
19783
|
);
|
|
19322
19784
|
return set.size > 0 ? set : null;
|
|
19323
19785
|
}
|
|
19324
|
-
function makeServeContext(cwd, projectRoot, signal) {
|
|
19786
|
+
function makeServeContext(cwd, projectRoot, signal, restrictFsToRoot = true) {
|
|
19325
19787
|
const provider = {
|
|
19326
19788
|
id: "mcp-serve",
|
|
19327
19789
|
capabilities: { maxContext: 0 },
|
|
@@ -19348,6 +19810,7 @@ function makeServeContext(cwd, projectRoot, signal) {
|
|
|
19348
19810
|
tokenCounter,
|
|
19349
19811
|
cwd,
|
|
19350
19812
|
projectRoot,
|
|
19813
|
+
allowOutsideProjectRoot: !restrictFsToRoot,
|
|
19351
19814
|
model: "mcp-serve",
|
|
19352
19815
|
tools: []
|
|
19353
19816
|
});
|
|
@@ -19373,7 +19836,12 @@ async function serveMcpStdio(deps) {
|
|
|
19373
19836
|
registry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
19374
19837
|
}
|
|
19375
19838
|
const controller = new AbortController();
|
|
19376
|
-
const ctx = makeServeContext(
|
|
19839
|
+
const ctx = makeServeContext(
|
|
19840
|
+
deps.cwd,
|
|
19841
|
+
deps.projectRoot,
|
|
19842
|
+
controller.signal,
|
|
19843
|
+
deps.config.tools?.restrictToProjectRoot ?? false
|
|
19844
|
+
);
|
|
19377
19845
|
const permissionPolicy = yolo ? new AllowAllPermissionPolicy() : new AutoApprovePermissionPolicy();
|
|
19378
19846
|
const executor = new ToolExecutor(registry, {
|
|
19379
19847
|
permissionPolicy,
|
|
@@ -20663,7 +21131,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
20663
21131
|
return 0;
|
|
20664
21132
|
};
|
|
20665
21133
|
var projectsCmd = async (_args, deps) => {
|
|
20666
|
-
const projectsRoot =
|
|
21134
|
+
const projectsRoot = path4.join(deps.paths.globalRoot, "projects");
|
|
20667
21135
|
try {
|
|
20668
21136
|
const entries = await fsp5.readdir(projectsRoot);
|
|
20669
21137
|
if (entries.length === 0) {
|
|
@@ -20673,7 +21141,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
20673
21141
|
for (const hash of entries) {
|
|
20674
21142
|
try {
|
|
20675
21143
|
const meta = JSON.parse(
|
|
20676
|
-
await fsp5.readFile(
|
|
21144
|
+
await fsp5.readFile(path4.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
20677
21145
|
);
|
|
20678
21146
|
deps.renderer.write(
|
|
20679
21147
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -21122,7 +21590,7 @@ function findSessionId(args) {
|
|
|
21122
21590
|
var rewindCmd = async (args, deps) => {
|
|
21123
21591
|
const flags = parseRewindFlags(args);
|
|
21124
21592
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
21125
|
-
const sessionsDir =
|
|
21593
|
+
const sessionsDir = path4.join(wpaths.globalRoot, "sessions");
|
|
21126
21594
|
const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
|
|
21127
21595
|
let sessionId = findSessionId(args);
|
|
21128
21596
|
if (!sessionId) {
|
|
@@ -21267,7 +21735,7 @@ async function listFleetRuns(deps) {
|
|
|
21267
21735
|
}
|
|
21268
21736
|
const runs = [];
|
|
21269
21737
|
for (const id of entries) {
|
|
21270
|
-
const runDir =
|
|
21738
|
+
const runDir = path4.join(deps.paths.projectSessions, id);
|
|
21271
21739
|
let stat7;
|
|
21272
21740
|
try {
|
|
21273
21741
|
stat7 = await fsp5.stat(runDir);
|
|
@@ -21280,17 +21748,17 @@ async function listFleetRuns(deps) {
|
|
|
21280
21748
|
let subagentCount = 0;
|
|
21281
21749
|
let subagentsDir;
|
|
21282
21750
|
try {
|
|
21283
|
-
await fsp5.access(
|
|
21751
|
+
await fsp5.access(path4.join(runDir, "fleet.json"));
|
|
21284
21752
|
manifest = true;
|
|
21285
21753
|
} catch {
|
|
21286
21754
|
}
|
|
21287
21755
|
try {
|
|
21288
|
-
await fsp5.access(
|
|
21756
|
+
await fsp5.access(path4.join(runDir, "checkpoint.json"));
|
|
21289
21757
|
checkpoint = true;
|
|
21290
21758
|
} catch {
|
|
21291
21759
|
}
|
|
21292
21760
|
try {
|
|
21293
|
-
subagentsDir =
|
|
21761
|
+
subagentsDir = path4.join(runDir, "subagents");
|
|
21294
21762
|
const files = await fsp5.readdir(subagentsDir);
|
|
21295
21763
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
21296
21764
|
} catch {
|
|
@@ -21317,7 +21785,7 @@ async function listFleetRuns(deps) {
|
|
|
21317
21785
|
return 0;
|
|
21318
21786
|
}
|
|
21319
21787
|
async function showFleetRun(runId, deps) {
|
|
21320
|
-
const runDir =
|
|
21788
|
+
const runDir = path4.join(deps.paths.projectSessions, runId);
|
|
21321
21789
|
let stat7;
|
|
21322
21790
|
try {
|
|
21323
21791
|
stat7 = await fsp5.stat(runDir);
|
|
@@ -21334,7 +21802,7 @@ async function showFleetRun(runId, deps) {
|
|
|
21334
21802
|
deps.renderer.write(color.bold(`
|
|
21335
21803
|
Fleet Run: ${runId}
|
|
21336
21804
|
`) + "\n");
|
|
21337
|
-
const manifestPath =
|
|
21805
|
+
const manifestPath = path4.join(runDir, "fleet.json");
|
|
21338
21806
|
let manifestData = null;
|
|
21339
21807
|
try {
|
|
21340
21808
|
manifestData = await fsp5.readFile(manifestPath, "utf8");
|
|
@@ -21352,7 +21820,7 @@ Fleet Run: ${runId}
|
|
|
21352
21820
|
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
21353
21821
|
`);
|
|
21354
21822
|
}
|
|
21355
|
-
const checkpointPath =
|
|
21823
|
+
const checkpointPath = path4.join(runDir, "checkpoint.json");
|
|
21356
21824
|
let checkpointData = null;
|
|
21357
21825
|
try {
|
|
21358
21826
|
checkpointData = await fsp5.readFile(checkpointPath, "utf8");
|
|
@@ -21399,7 +21867,7 @@ Fleet Run: ${runId}
|
|
|
21399
21867
|
} catch {
|
|
21400
21868
|
}
|
|
21401
21869
|
}
|
|
21402
|
-
const subagentsDir =
|
|
21870
|
+
const subagentsDir = path4.join(runDir, "subagents");
|
|
21403
21871
|
let subagentFiles = [];
|
|
21404
21872
|
try {
|
|
21405
21873
|
subagentFiles = await fsp5.readdir(subagentsDir);
|
|
@@ -21411,7 +21879,7 @@ Fleet Run: ${runId}
|
|
|
21411
21879
|
Subagent transcripts (${subagentFiles.length}):
|
|
21412
21880
|
`);
|
|
21413
21881
|
for (const f of subagentFiles.sort()) {
|
|
21414
|
-
const filePath =
|
|
21882
|
+
const filePath = path4.join(subagentsDir, f);
|
|
21415
21883
|
let size;
|
|
21416
21884
|
try {
|
|
21417
21885
|
const s = await fsp5.stat(filePath);
|
|
@@ -21428,7 +21896,7 @@ Fleet Run: ${runId}
|
|
|
21428
21896
|
${color.dim("\u25CB")} No subagent transcripts
|
|
21429
21897
|
`);
|
|
21430
21898
|
}
|
|
21431
|
-
const sharedDir =
|
|
21899
|
+
const sharedDir = path4.join(runDir, "shared");
|
|
21432
21900
|
try {
|
|
21433
21901
|
const files = await fsp5.readdir(sharedDir);
|
|
21434
21902
|
deps.renderer.write(`
|
|
@@ -21669,8 +22137,8 @@ var helpCmd = async (_args, deps) => {
|
|
|
21669
22137
|
' wstack --eternal "<mission>" Launch eternal-autonomy loop against a goal \u2014 Ctrl+C to stop',
|
|
21670
22138
|
" wstack resume [<id>] Resume a session",
|
|
21671
22139
|
" wstack sessions List recent sessions",
|
|
21672
|
-
" wstack
|
|
21673
|
-
" wstack auth
|
|
22140
|
+
" wstack auth Interactive setup + key manager (add/edit/delete)",
|
|
22141
|
+
" wstack auth <provider> Add API key for a provider",
|
|
21674
22142
|
" wstack auth list Quick listing of saved providers and keys",
|
|
21675
22143
|
" wstack auth status <id> Detailed view of one provider",
|
|
21676
22144
|
" wstack auth remove <id> Delete a provider (asks for confirmation)",
|
|
@@ -21755,7 +22223,7 @@ function resolveBundledSkillsDir() {
|
|
|
21755
22223
|
try {
|
|
21756
22224
|
const req2 = createRequire(import.meta.url);
|
|
21757
22225
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
21758
|
-
return
|
|
22226
|
+
return path4.join(path4.dirname(corePkg), "skills");
|
|
21759
22227
|
} catch {
|
|
21760
22228
|
return void 0;
|
|
21761
22229
|
}
|
|
@@ -21976,7 +22444,7 @@ async function boot(argv) {
|
|
|
21976
22444
|
const created = await registerProjectAtBoot({ projectRoot, cwd, wpaths });
|
|
21977
22445
|
if (created && isInteractiveTTY) {
|
|
21978
22446
|
renderer.write(
|
|
21979
|
-
color.dim(` \u2713 Registered "${
|
|
22447
|
+
color.dim(` \u2713 Registered "${path4.basename(projectRoot)}" in projects.json.
|
|
21980
22448
|
`)
|
|
21981
22449
|
);
|
|
21982
22450
|
}
|
|
@@ -22056,7 +22524,7 @@ async function boot(argv) {
|
|
|
22056
22524
|
} catch {
|
|
22057
22525
|
}
|
|
22058
22526
|
printLaunchHints(renderer, flags, {
|
|
22059
|
-
cursorFile:
|
|
22527
|
+
cursorFile: path4.join(wpaths.cacheDir, "hint-cursor")
|
|
22060
22528
|
});
|
|
22061
22529
|
} else {
|
|
22062
22530
|
const effectiveChoices = config.launch ? {
|
|
@@ -22094,7 +22562,7 @@ async function boot(argv) {
|
|
|
22094
22562
|
}
|
|
22095
22563
|
async function checkGitInCwd(opts) {
|
|
22096
22564
|
const { cwd, renderer, reader } = opts;
|
|
22097
|
-
const cwdGit =
|
|
22565
|
+
const cwdGit = path4.join(cwd, ".git");
|
|
22098
22566
|
let hasCwdGit = false;
|
|
22099
22567
|
try {
|
|
22100
22568
|
await fsp5.access(cwdGit);
|
|
@@ -22134,10 +22602,10 @@ async function checkGitInCwd(opts) {
|
|
|
22134
22602
|
}
|
|
22135
22603
|
}
|
|
22136
22604
|
}
|
|
22137
|
-
const parentDir =
|
|
22605
|
+
const parentDir = path4.dirname(cwd);
|
|
22138
22606
|
if (parentDir !== cwd) {
|
|
22139
22607
|
try {
|
|
22140
|
-
await fsp5.access(
|
|
22608
|
+
await fsp5.access(path4.join(parentDir, ".git"));
|
|
22141
22609
|
renderer.write(
|
|
22142
22610
|
` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
|
|
22143
22611
|
`
|
|
@@ -22149,7 +22617,7 @@ async function checkGitInCwd(opts) {
|
|
|
22149
22617
|
async function registerProjectAtBoot(opts) {
|
|
22150
22618
|
const { projectRoot, cwd, wpaths } = opts;
|
|
22151
22619
|
const manifest = await loadManifest(wpaths.globalConfig);
|
|
22152
|
-
const existed = manifest.projects.some((p) =>
|
|
22620
|
+
const existed = manifest.projects.some((p) => path4.resolve(p.root) === path4.resolve(projectRoot));
|
|
22153
22621
|
await touchProjectInManifest({
|
|
22154
22622
|
projectRoot,
|
|
22155
22623
|
globalConfigPath: wpaths.globalConfig,
|
|
@@ -22157,9 +22625,26 @@ async function registerProjectAtBoot(opts) {
|
|
|
22157
22625
|
});
|
|
22158
22626
|
return !existed;
|
|
22159
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
|
+
}
|
|
22160
22644
|
function registerBuiltinTools(deps) {
|
|
22645
|
+
const tier = normalizeTokenSavingTier(deps.config.features.tokenSavingMode);
|
|
22161
22646
|
const allTools = builtinToolsPack.tools ?? [];
|
|
22162
|
-
const toolsToRegister =
|
|
22647
|
+
const toolsToRegister = toolsForTier(tier, allTools);
|
|
22163
22648
|
deps.toolRegistry.registerAllOrThrow([...toolsToRegister], builtinToolsPack.name);
|
|
22164
22649
|
deps.toolRegistry.registerDefault(
|
|
22165
22650
|
createContextManagerTool({ compactor: deps.compactor })
|
|
@@ -22350,7 +22835,7 @@ function resolveBundledSkillsDir2() {
|
|
|
22350
22835
|
try {
|
|
22351
22836
|
const req2 = createRequire(import.meta.url);
|
|
22352
22837
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
22353
|
-
return
|
|
22838
|
+
return path4.join(path4.dirname(corePkg), "skills");
|
|
22354
22839
|
} catch {
|
|
22355
22840
|
return void 0;
|
|
22356
22841
|
}
|
|
@@ -22838,7 +23323,7 @@ async function runRepl(opts) {
|
|
|
22838
23323
|
clientMailbox.registerClient({
|
|
22839
23324
|
clientId,
|
|
22840
23325
|
sessionId: replProjectRoot,
|
|
22841
|
-
name: `REPL [${
|
|
23326
|
+
name: `REPL [${path4.basename(replProjectRoot)}]`,
|
|
22842
23327
|
source: "repl",
|
|
22843
23328
|
pid: process.pid
|
|
22844
23329
|
}).then(() => {
|
|
@@ -23655,6 +24140,7 @@ function printBanner(renderer, projectName) {
|
|
|
23655
24140
|
}
|
|
23656
24141
|
|
|
23657
24142
|
// src/execution.ts
|
|
24143
|
+
init_provider_config_utils();
|
|
23658
24144
|
init_utils();
|
|
23659
24145
|
async function execute(deps) {
|
|
23660
24146
|
const {
|
|
@@ -23874,8 +24360,8 @@ async function execute(deps) {
|
|
|
23874
24360
|
result = await agent.run(query, { signal: ctrl.signal });
|
|
23875
24361
|
} finally {
|
|
23876
24362
|
process.off("SIGINT", onSigint);
|
|
23877
|
-
const { getProcessRegistry:
|
|
23878
|
-
|
|
24363
|
+
const { getProcessRegistry: getProcessRegistry3 } = await import('@wrongstack/tools');
|
|
24364
|
+
getProcessRegistry3().killAll();
|
|
23879
24365
|
}
|
|
23880
24366
|
const after = tokenCounter.total();
|
|
23881
24367
|
const costAfter = tokenCounter.estimateCost().total;
|
|
@@ -23934,7 +24420,7 @@ async function execute(deps) {
|
|
|
23934
24420
|
const { runTui } = await import('@wrongstack/tui');
|
|
23935
24421
|
renderer.setSilent(true);
|
|
23936
24422
|
const banneredFamily = savedProviderCfg?.family ?? resolvedProvider?.family;
|
|
23937
|
-
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);
|
|
23938
24424
|
const banneredKeyTail = banneredKey && banneredKey.length >= 3 ? banneredKey.slice(-3) : void 0;
|
|
23939
24425
|
const autoPhaseHandlers = /* @__PURE__ */ new Map();
|
|
23940
24426
|
const subscribeAutoPhase = (handler) => {
|
|
@@ -23987,7 +24473,7 @@ async function execute(deps) {
|
|
|
23987
24473
|
const onDirectorReady = (dir) => {
|
|
23988
24474
|
if (autonomousCoordinator) return;
|
|
23989
24475
|
const transcript = context.session.transcriptPath;
|
|
23990
|
-
const sessionDir = transcript ?
|
|
24476
|
+
const sessionDir = transcript ? path4.dirname(transcript) : wpaths.projectDir;
|
|
23991
24477
|
const llmProvider = {
|
|
23992
24478
|
decide: async (prompt) => {
|
|
23993
24479
|
const sysPrompt = [
|
|
@@ -24041,8 +24527,12 @@ Reply with ONLY the JSON object.`
|
|
|
24041
24527
|
mailbox,
|
|
24042
24528
|
selfAgentId: `leader@${context.session.id ?? "unknown"}`,
|
|
24043
24529
|
selfAgentName: "Leader",
|
|
24044
|
-
llmProvider
|
|
24530
|
+
llmProvider,
|
|
24531
|
+
onCoordinatorEvent: (event) => {
|
|
24532
|
+
for (const fn of coordinatorEvents) fn(event);
|
|
24533
|
+
}
|
|
24045
24534
|
});
|
|
24535
|
+
deps.onCoordinatorStop = () => autonomousCoordinator?.stop();
|
|
24046
24536
|
};
|
|
24047
24537
|
if (director) onDirectorReady(director);
|
|
24048
24538
|
const offDirectorSpawned = events.onPattern("subagent.spawned", () => {
|
|
@@ -24135,7 +24625,7 @@ Reply with ONLY the JSON object.`
|
|
|
24135
24625
|
featureMemory: cfg.features?.memory !== false,
|
|
24136
24626
|
featureSkills: cfg.features?.skills !== false,
|
|
24137
24627
|
featureModelsRegistry: cfg.features?.modelsRegistry !== false,
|
|
24138
|
-
featureTokenSaving: cfg.features?.tokenSavingMode
|
|
24628
|
+
featureTokenSaving: normalizeTokenSavingTier(cfg.features?.tokenSavingMode),
|
|
24139
24629
|
allowOutsideProjectRoot: cfg.features?.allowOutsideProjectRoot ?? true,
|
|
24140
24630
|
contextAutoCompact: cfg.context?.autoCompact !== false,
|
|
24141
24631
|
contextStrategy: cfg.context?.strategy ?? "hybrid",
|
|
@@ -24143,6 +24633,7 @@ Reply with ONLY the JSON object.`
|
|
|
24143
24633
|
auditLevel: cfg.session?.auditLevel ?? "standard",
|
|
24144
24634
|
indexOnStart: cfg.indexing?.onSessionStart !== false,
|
|
24145
24635
|
maxIterations: cfg.tools?.maxIterations ?? 500,
|
|
24636
|
+
restrictFsToRoot: cfg.tools?.restrictToProjectRoot ?? false,
|
|
24146
24637
|
autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
|
|
24147
24638
|
debugStream: cfg.debugStream ?? false,
|
|
24148
24639
|
configScope: cfg.configScope ?? "global",
|
|
@@ -24150,7 +24641,9 @@ Reply with ONLY the JSON object.`
|
|
|
24150
24641
|
enhanceEnabled: cfg.autonomy?.enhance ?? true,
|
|
24151
24642
|
enhanceLanguage: cfg.autonomy?.enhanceLanguage === "english" ? "english" : "original",
|
|
24152
24643
|
mouseMode: autonomy?.mouseMode ?? false,
|
|
24153
|
-
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
|
|
24154
24647
|
};
|
|
24155
24648
|
},
|
|
24156
24649
|
async saveSettings(s) {
|
|
@@ -24179,7 +24672,7 @@ Reply with ONLY the JSON object.`
|
|
|
24179
24672
|
a["autoProceedMaxIterations"] = s.autoProceedMaxIterations;
|
|
24180
24673
|
}
|
|
24181
24674
|
);
|
|
24182
|
-
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.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) {
|
|
24183
24676
|
const configScope = s.configScope ?? configStore.get().configScope ?? "global";
|
|
24184
24677
|
const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
|
|
24185
24678
|
let raw;
|
|
@@ -24231,11 +24724,18 @@ Reply with ONLY the JSON object.`
|
|
|
24231
24724
|
idx.onSessionStart = s.indexOnStart;
|
|
24232
24725
|
decrypted.indexing = idx;
|
|
24233
24726
|
}
|
|
24234
|
-
if (s.maxIterations !== void 0) {
|
|
24727
|
+
if (s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0) {
|
|
24235
24728
|
const tools = decrypted.tools ?? {};
|
|
24236
|
-
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;
|
|
24237
24732
|
decrypted.tools = tools;
|
|
24238
24733
|
}
|
|
24734
|
+
if (s.restrictFsToRoot !== void 0) {
|
|
24735
|
+
const features = decrypted.features ?? {};
|
|
24736
|
+
features.allowOutsideProjectRoot = !s.restrictFsToRoot;
|
|
24737
|
+
decrypted.features = features;
|
|
24738
|
+
}
|
|
24239
24739
|
if (s.debugStream !== void 0) {
|
|
24240
24740
|
decrypted.debugStream = s.debugStream;
|
|
24241
24741
|
const { setDebugStreamEnabled } = await import('@wrongstack/providers');
|
|
@@ -24257,7 +24757,7 @@ Reply with ONLY the JSON object.`
|
|
|
24257
24757
|
const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
|
|
24258
24758
|
const encrypted = encryptConfigSecrets(toWrite, noOpVault);
|
|
24259
24759
|
if (targetPath !== wpaths.globalConfig) {
|
|
24260
|
-
await fsp5.mkdir(
|
|
24760
|
+
await fsp5.mkdir(path4.dirname(targetPath), { recursive: true });
|
|
24261
24761
|
}
|
|
24262
24762
|
await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
24263
24763
|
configStore.update({
|
|
@@ -24267,7 +24767,7 @@ Reply with ONLY the JSON object.`
|
|
|
24267
24767
|
...s.logLevel !== void 0 ? { log: decrypted.log } : {},
|
|
24268
24768
|
...s.auditLevel !== void 0 ? { session: decrypted.session } : {},
|
|
24269
24769
|
...s.indexOnStart !== void 0 ? { indexing: decrypted.indexing } : {},
|
|
24270
|
-
...s.maxIterations !== void 0 ? { tools: decrypted.tools } : {},
|
|
24770
|
+
...s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 ? { tools: decrypted.tools } : {},
|
|
24271
24771
|
...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
|
|
24272
24772
|
...s.configScope !== void 0 ? { configScope: s.configScope } : {},
|
|
24273
24773
|
...s.enhanceDelayMs !== void 0 ? {
|
|
@@ -24329,6 +24829,18 @@ Reply with ONLY the JSON object.`
|
|
|
24329
24829
|
coordinatorEvents.delete(fn);
|
|
24330
24830
|
};
|
|
24331
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
|
+
},
|
|
24332
24844
|
// /clear: signal the TUI to wipe entries and reset fleet/leader stats
|
|
24333
24845
|
// AND bump the context chip version — so the display reflects a
|
|
24334
24846
|
// completely fresh session after the backend has been cleared.
|
|
@@ -24346,7 +24858,7 @@ Reply with ONLY the JSON object.`
|
|
|
24346
24858
|
agentsMonitorController,
|
|
24347
24859
|
getLiveSessions: async () => {
|
|
24348
24860
|
const { SessionRegistry } = await import('@wrongstack/core');
|
|
24349
|
-
const globalRoot =
|
|
24861
|
+
const globalRoot = path4.dirname(wpaths.globalConfig);
|
|
24350
24862
|
const registry = new SessionRegistry(globalRoot);
|
|
24351
24863
|
const sessions = await registry.list();
|
|
24352
24864
|
return sessions.filter((s) => s.status !== "stale").map((s) => ({
|
|
@@ -24475,7 +24987,7 @@ Reply with ONLY the JSON object.`
|
|
|
24475
24987
|
if (!sessionStore) return null;
|
|
24476
24988
|
try {
|
|
24477
24989
|
const { SessionRegistry } = await import('@wrongstack/core');
|
|
24478
|
-
const registry = new SessionRegistry(
|
|
24990
|
+
const registry = new SessionRegistry(path4.dirname(wpaths.globalConfig));
|
|
24479
24991
|
const live = (await registry.list()).find(
|
|
24480
24992
|
(s) => s.sessionId === sessionId && s.status !== "stale" && s.pid !== process.pid
|
|
24481
24993
|
);
|
|
@@ -24613,7 +25125,7 @@ Reply with ONLY the JSON object.`
|
|
|
24613
25125
|
if (slug === "new-session") {
|
|
24614
25126
|
pendingProjectSwitch = {
|
|
24615
25127
|
root: projectRoot,
|
|
24616
|
-
name:
|
|
25128
|
+
name: path4.basename(projectRoot) || projectRoot
|
|
24617
25129
|
};
|
|
24618
25130
|
}
|
|
24619
25131
|
return;
|
|
@@ -24667,7 +25179,7 @@ ${parts.join("\n")}
|
|
|
24667
25179
|
},
|
|
24668
25180
|
// `wrongstack quick` sets flags.quick — open the F3 agents monitor by default.
|
|
24669
25181
|
initialAgentsMonitorOpen: !!flags.quick,
|
|
24670
|
-
tokenSavingMode: config.features.tokenSavingMode,
|
|
25182
|
+
tokenSavingMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off",
|
|
24671
25183
|
toolCount: agent.tools.list().length
|
|
24672
25184
|
});
|
|
24673
25185
|
if (code === PROJECT_SWITCH_EXIT_CODE && pendingProjectSwitch) {
|
|
@@ -24679,8 +25191,8 @@ ${parts.join("\n")}
|
|
|
24679
25191
|
try {
|
|
24680
25192
|
const req2 = createRequire8(import.meta.url);
|
|
24681
25193
|
const pkgPath = req2.resolve("@wrongstack/cli/package.json");
|
|
24682
|
-
const pkgDir =
|
|
24683
|
-
cliPath =
|
|
25194
|
+
const pkgDir = path4.dirname(pkgPath);
|
|
25195
|
+
cliPath = path4.join(pkgDir, "dist", "index.js");
|
|
24684
25196
|
await fsp5.access(cliPath);
|
|
24685
25197
|
} catch {
|
|
24686
25198
|
cliPath = process.argv[1] ?? "";
|
|
@@ -24741,6 +25253,23 @@ ${parts.join("\n")}
|
|
|
24741
25253
|
modeStore,
|
|
24742
25254
|
modeId,
|
|
24743
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
|
+
},
|
|
24744
25273
|
// Make autonomy.switch from the browser flip the CLI's real
|
|
24745
25274
|
// autonomy mode — context.meta alone never reaches the run loop.
|
|
24746
25275
|
onAutonomySwitch: (mode) => {
|
|
@@ -24749,12 +25278,6 @@ ${parts.join("\n")}
|
|
|
24749
25278
|
}
|
|
24750
25279
|
}
|
|
24751
25280
|
});
|
|
24752
|
-
renderer.writeInfo(
|
|
24753
|
-
color.green(
|
|
24754
|
-
` \u2726 WebUI running \u2192 ${color.bold(`http://localhost:${Number.parseInt(String(flags.port ?? "3457"), 10)}`)}`
|
|
24755
|
-
)
|
|
24756
|
-
);
|
|
24757
|
-
renderer.writeInfo(color.dim(" Press Ctrl+C in this terminal to stop the WebUI server.\n"));
|
|
24758
25281
|
const webuiExit = new Promise((resolve11) => {
|
|
24759
25282
|
const onSigint = () => {
|
|
24760
25283
|
renderer.setSilent(false);
|
|
@@ -24789,7 +25312,7 @@ ${parts.join("\n")}
|
|
|
24789
25312
|
supportsVision,
|
|
24790
25313
|
attachments,
|
|
24791
25314
|
effectiveMaxContext,
|
|
24792
|
-
projectName:
|
|
25315
|
+
projectName: path4.basename(projectRoot) || void 0,
|
|
24793
25316
|
getAutonomy,
|
|
24794
25317
|
onAutonomy,
|
|
24795
25318
|
getNextPredict,
|
|
@@ -24827,6 +25350,7 @@ ${parts.join("\n")}
|
|
|
24827
25350
|
}
|
|
24828
25351
|
} finally {
|
|
24829
25352
|
fleetStatusLine?.stop();
|
|
25353
|
+
deps.onCoordinatorStop?.();
|
|
24830
25354
|
try {
|
|
24831
25355
|
stats.render(renderer);
|
|
24832
25356
|
} catch (_err) {
|
|
@@ -25011,7 +25535,7 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
25011
25535
|
doneCondition: { type: "all_tasks_done" },
|
|
25012
25536
|
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
25013
25537
|
};
|
|
25014
|
-
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);
|
|
25015
25539
|
this.director = new Director({
|
|
25016
25540
|
config: coordinatorConfig,
|
|
25017
25541
|
manifestPath: this.opts.manifestPath,
|
|
@@ -25210,6 +25734,8 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
25210
25734
|
tokenCounter: this.deps.tokenCounter,
|
|
25211
25735
|
cwd: subCwd,
|
|
25212
25736
|
projectRoot: this.deps.projectRoot,
|
|
25737
|
+
// Subagents inherit the leader's filesystem-access scope.
|
|
25738
|
+
allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
|
|
25213
25739
|
model: effModel,
|
|
25214
25740
|
tools: this.filterTools(tools),
|
|
25215
25741
|
// Distinct mailbox identity: without these, every subagent fell back
|
|
@@ -25568,16 +26094,16 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
25568
26094
|
if (this.director) return this.director;
|
|
25569
26095
|
this.opts.directorMode = true;
|
|
25570
26096
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
25571
|
-
this.opts.manifestPath =
|
|
26097
|
+
this.opts.manifestPath = path4.join(this.opts.fleetRoot, "fleet.json");
|
|
25572
26098
|
}
|
|
25573
26099
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
25574
|
-
this.opts.sharedScratchpadPath =
|
|
26100
|
+
this.opts.sharedScratchpadPath = path4.join(this.opts.fleetRoot, "shared");
|
|
25575
26101
|
}
|
|
25576
26102
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
25577
|
-
this.opts.sessionsRoot =
|
|
26103
|
+
this.opts.sessionsRoot = path4.join(this.opts.fleetRoot, "subagents");
|
|
25578
26104
|
}
|
|
25579
26105
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
25580
|
-
this.opts.stateCheckpointPath =
|
|
26106
|
+
this.opts.stateCheckpointPath = path4.join(this.opts.fleetRoot, "director-state.json");
|
|
25581
26107
|
}
|
|
25582
26108
|
await this.ensureDirector();
|
|
25583
26109
|
return this.director ?? null;
|
|
@@ -25706,11 +26232,11 @@ var SessionStats = class {
|
|
|
25706
26232
|
if (tool.name === "bash") this.bashCommands++;
|
|
25707
26233
|
else if (tool.name === "fetch") this.fetches++;
|
|
25708
26234
|
if (!tool.ok) return;
|
|
25709
|
-
const
|
|
25710
|
-
if (tool.name === "read" &&
|
|
25711
|
-
else if (tool.name === "edit" &&
|
|
25712
|
-
else if (tool.name === "write" &&
|
|
25713
|
-
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);
|
|
25714
26240
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
25715
26241
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
25716
26242
|
}
|
|
@@ -26022,7 +26548,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
26022
26548
|
if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
|
|
26023
26549
|
const fp = payload.toolUse.input?.file_path;
|
|
26024
26550
|
if (typeof fp === "string" && fp.length > 0) {
|
|
26025
|
-
const abs =
|
|
26551
|
+
const abs = path4.resolve(payload.ctx.cwd, fp);
|
|
26026
26552
|
if (isIndexableFile(abs)) {
|
|
26027
26553
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
26028
26554
|
}
|
|
@@ -26041,7 +26567,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
26041
26567
|
if (!filename) return;
|
|
26042
26568
|
const rel = filename.toString();
|
|
26043
26569
|
if (isIgnored(rel)) return;
|
|
26044
|
-
const abs =
|
|
26570
|
+
const abs = path4.resolve(projectRoot, rel);
|
|
26045
26571
|
if (!isIndexableFile(abs)) return;
|
|
26046
26572
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
26047
26573
|
});
|
|
@@ -26095,7 +26621,7 @@ function setupMetrics(params) {
|
|
|
26095
26621
|
const dumpMetrics = () => {
|
|
26096
26622
|
if (!metricsSink) return;
|
|
26097
26623
|
try {
|
|
26098
|
-
const out =
|
|
26624
|
+
const out = path4.join(wpaths.projectSessions, "metrics.json");
|
|
26099
26625
|
const snap = metricsSink.snapshot();
|
|
26100
26626
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
26101
26627
|
} catch {
|
|
@@ -26335,7 +26861,7 @@ async function setupSession(params) {
|
|
|
26335
26861
|
);
|
|
26336
26862
|
});
|
|
26337
26863
|
const attachments = new DefaultAttachmentStore({
|
|
26338
|
-
spoolDir:
|
|
26864
|
+
spoolDir: path4.join(wpaths.projectSessions, session?.id, "attachments")
|
|
26339
26865
|
});
|
|
26340
26866
|
const ctxSignal = new AbortController().signal;
|
|
26341
26867
|
const traceId = randomBytes(16).toString("hex");
|
|
@@ -26347,11 +26873,15 @@ async function setupSession(params) {
|
|
|
26347
26873
|
tokenCounter,
|
|
26348
26874
|
cwd,
|
|
26349
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),
|
|
26350
26881
|
model: config.model,
|
|
26351
26882
|
agentId: "leader",
|
|
26352
26883
|
agentName: "Leader Agent",
|
|
26353
|
-
traceId
|
|
26354
|
-
allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? true
|
|
26884
|
+
traceId
|
|
26355
26885
|
});
|
|
26356
26886
|
context.meta["packageTrackerOpts"] = {
|
|
26357
26887
|
storageDir: wpaths.projectDir,
|
|
@@ -26359,11 +26889,11 @@ async function setupSession(params) {
|
|
|
26359
26889
|
};
|
|
26360
26890
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
26361
26891
|
const queueStore = new QueueStore({
|
|
26362
|
-
dir:
|
|
26892
|
+
dir: path4.join(wpaths.projectSessions, session?.id),
|
|
26363
26893
|
...eventsBus ? { events: eventsBus } : {},
|
|
26364
26894
|
...traceId ? { traceId } : {}
|
|
26365
26895
|
});
|
|
26366
|
-
const todosCheckpointPath =
|
|
26896
|
+
const todosCheckpointPath = path4.join(wpaths.projectSessions, `${session?.id}.todos.json`);
|
|
26367
26897
|
if (resumeId) {
|
|
26368
26898
|
try {
|
|
26369
26899
|
const restoredTodos = await loadTodosCheckpoint(
|
|
@@ -26387,15 +26917,15 @@ async function setupSession(params) {
|
|
|
26387
26917
|
eventsBus,
|
|
26388
26918
|
traceId
|
|
26389
26919
|
);
|
|
26390
|
-
const planPath =
|
|
26920
|
+
const planPath = path4.join(wpaths.projectSessions, `${session?.id}.plan.json`);
|
|
26391
26921
|
context.state.setMeta("plan.path", planPath);
|
|
26392
|
-
const taskPath =
|
|
26922
|
+
const taskPath = path4.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
|
|
26393
26923
|
context.state.setMeta("task.path", taskPath);
|
|
26394
26924
|
let dirState;
|
|
26395
26925
|
if (resumeId) {
|
|
26396
26926
|
try {
|
|
26397
|
-
const fleetRoot =
|
|
26398
|
-
dirState = await loadDirectorState(
|
|
26927
|
+
const fleetRoot = path4.join(wpaths.projectSessions, session?.id);
|
|
26928
|
+
dirState = await loadDirectorState(path4.join(fleetRoot, "director-state.json"));
|
|
26399
26929
|
if (dirState) {
|
|
26400
26930
|
const tCounts = {};
|
|
26401
26931
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -26642,7 +27172,7 @@ async function main(argv) {
|
|
|
26642
27172
|
projectGoal: wpaths.projectGoal,
|
|
26643
27173
|
projectSessions: wpaths.projectSessions
|
|
26644
27174
|
},
|
|
26645
|
-
pathJoiner: { join: (a, b) =>
|
|
27175
|
+
pathJoiner: { join: (a, b) => path4.join(a, b) },
|
|
26646
27176
|
systemPromptBuilderToken: TOKENS.SystemPromptBuilder
|
|
26647
27177
|
});
|
|
26648
27178
|
const toolRegistry = new ToolRegistry();
|
|
@@ -26737,6 +27267,49 @@ async function main(argv) {
|
|
|
26737
27267
|
writeErr(color.red(` \u2717 ${p.description}
|
|
26738
27268
|
`));
|
|
26739
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();
|
|
26740
27313
|
const promptBuilder = container.resolve(TOKENS.SystemPromptBuilder);
|
|
26741
27314
|
let onlineAgents = [];
|
|
26742
27315
|
try {
|
|
@@ -26780,10 +27353,10 @@ async function main(argv) {
|
|
|
26780
27353
|
memoryStore.withTraceId(sessResult.traceId);
|
|
26781
27354
|
let tracker;
|
|
26782
27355
|
try {
|
|
26783
|
-
const { getSessionRegistry, AgentStatusTracker } = await import('@wrongstack/core');
|
|
27356
|
+
const { getSessionRegistry, AgentStatusTracker, FleetNotifier } = await import('@wrongstack/core');
|
|
26784
27357
|
const registry = getSessionRegistry(wpaths.globalRoot);
|
|
26785
|
-
const projectSlug2 =
|
|
26786
|
-
const projectName =
|
|
27358
|
+
const projectSlug2 = path4.basename(wpaths.projectDir);
|
|
27359
|
+
const projectName = path4.basename(projectRoot);
|
|
26787
27360
|
let gitBranch;
|
|
26788
27361
|
try {
|
|
26789
27362
|
const { execSync } = await import('child_process');
|
|
@@ -26803,13 +27376,22 @@ async function main(argv) {
|
|
|
26803
27376
|
projectName,
|
|
26804
27377
|
workingDir: context.workingDir,
|
|
26805
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",
|
|
26806
27382
|
pid: process.pid,
|
|
26807
27383
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26808
27384
|
});
|
|
26809
|
-
|
|
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() });
|
|
26810
27391
|
tracker.start();
|
|
26811
27392
|
const cleanup = async () => {
|
|
26812
27393
|
try {
|
|
27394
|
+
fleetNotifier.dispose();
|
|
26813
27395
|
await registry.markClosing();
|
|
26814
27396
|
tracker?.stop();
|
|
26815
27397
|
} catch {
|
|
@@ -26882,7 +27464,7 @@ async function main(argv) {
|
|
|
26882
27464
|
if (e.ok && (e.name === "write" || e.name === "edit" || e.name === "replace" || e.name === "patch")) {
|
|
26883
27465
|
const filePath = e.input?.path;
|
|
26884
27466
|
if (filePath) {
|
|
26885
|
-
const projectDir =
|
|
27467
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
26886
27468
|
void recordFileAction(
|
|
26887
27469
|
{ storageDir: projectDir, projectRoot },
|
|
26888
27470
|
{
|
|
@@ -27012,7 +27594,7 @@ async function main(argv) {
|
|
|
27012
27594
|
toolRegistry,
|
|
27013
27595
|
events,
|
|
27014
27596
|
log: logger,
|
|
27015
|
-
lazyMode: config.features.tokenSavingMode
|
|
27597
|
+
lazyMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off"
|
|
27016
27598
|
});
|
|
27017
27599
|
if (config.features.mcp) {
|
|
27018
27600
|
for (const cfg of Object.values(config.mcpServers ?? {})) {
|
|
@@ -27048,7 +27630,7 @@ async function main(argv) {
|
|
|
27048
27630
|
let depWatcherDispose;
|
|
27049
27631
|
if (dwCfg?.["enabled"] === true) {
|
|
27050
27632
|
try {
|
|
27051
|
-
const projectDir =
|
|
27633
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
27052
27634
|
const dwMailbox = new GlobalMailbox(projectDir, events);
|
|
27053
27635
|
depWatcherDispose = attachDepWatcherBridge({
|
|
27054
27636
|
events,
|
|
@@ -27147,12 +27729,12 @@ async function main(argv) {
|
|
|
27147
27729
|
}
|
|
27148
27730
|
}
|
|
27149
27731
|
};
|
|
27150
|
-
const fleetRoot = directorMode ?
|
|
27151
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
27152
|
-
const sharedScratchpadPath = directorMode ?
|
|
27153
|
-
const subagentSessionsRoot = directorMode ?
|
|
27154
|
-
const stateCheckpointPath = directorMode ?
|
|
27155
|
-
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);
|
|
27156
27738
|
const brainSettings = {
|
|
27157
27739
|
maxAutoRisk: "medium"
|
|
27158
27740
|
};
|
|
@@ -27295,7 +27877,7 @@ async function main(argv) {
|
|
|
27295
27877
|
let techStackConsumerDispose;
|
|
27296
27878
|
if (dwCfg?.["enabled"] === true) {
|
|
27297
27879
|
try {
|
|
27298
|
-
const projectDir =
|
|
27880
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
27299
27881
|
const tsMailbox = new GlobalMailbox(projectDir, events);
|
|
27300
27882
|
const fileAuthorOpts = {
|
|
27301
27883
|
storageDir: projectDir,
|
|
@@ -27328,7 +27910,7 @@ async function main(argv) {
|
|
|
27328
27910
|
let pkgOutdatedDispose;
|
|
27329
27911
|
if (dwCfg?.["enabled"] === true) {
|
|
27330
27912
|
try {
|
|
27331
|
-
const projectDir =
|
|
27913
|
+
const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
|
|
27332
27914
|
const pkgMailbox = new GlobalMailbox(projectDir, events);
|
|
27333
27915
|
const pkgTrackerOpts = {
|
|
27334
27916
|
storageDir: projectDir,
|
|
@@ -27702,7 +28284,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27702
28284
|
return director.spawn(cfg);
|
|
27703
28285
|
},
|
|
27704
28286
|
onFleetLog: async (subagentId, mode) => {
|
|
27705
|
-
const subagentsRoot =
|
|
28287
|
+
const subagentsRoot = path4.join(fleetRootForPromotion, "subagents");
|
|
27706
28288
|
let runDirs;
|
|
27707
28289
|
try {
|
|
27708
28290
|
runDirs = await fsp5.readdir(subagentsRoot);
|
|
@@ -27711,7 +28293,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27711
28293
|
}
|
|
27712
28294
|
const found = [];
|
|
27713
28295
|
for (const runId of runDirs) {
|
|
27714
|
-
const runDir =
|
|
28296
|
+
const runDir = path4.join(subagentsRoot, runId);
|
|
27715
28297
|
let files;
|
|
27716
28298
|
try {
|
|
27717
28299
|
files = await fsp5.readdir(runDir);
|
|
@@ -27720,7 +28302,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27720
28302
|
}
|
|
27721
28303
|
for (const f of files) {
|
|
27722
28304
|
if (!f.endsWith(".jsonl")) continue;
|
|
27723
|
-
const full =
|
|
28305
|
+
const full = path4.join(runDir, f);
|
|
27724
28306
|
try {
|
|
27725
28307
|
const stat7 = await fsp5.stat(full);
|
|
27726
28308
|
found.push({
|
|
@@ -27821,7 +28403,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27821
28403
|
}
|
|
27822
28404
|
const dir = await multiAgentHost.ensureDirector();
|
|
27823
28405
|
if (!dir) return "Director is not available.";
|
|
27824
|
-
const dirStatePath =
|
|
28406
|
+
const dirStatePath = path4.join(fleetRootForPromotion, "director-state.json");
|
|
27825
28407
|
const prior = await loadDirectorState(dirStatePath);
|
|
27826
28408
|
if (!prior) {
|
|
27827
28409
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -27890,9 +28472,9 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
27890
28472
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
27891
28473
|
toolRegistry.register(tool);
|
|
27892
28474
|
}
|
|
27893
|
-
const mp =
|
|
27894
|
-
const sp =
|
|
27895
|
-
const ss =
|
|
28475
|
+
const mp = path4.join(fleetRootForPromotion, "fleet.json");
|
|
28476
|
+
const sp = path4.join(fleetRootForPromotion, "shared");
|
|
28477
|
+
const ss = path4.join(fleetRootForPromotion, "subagents");
|
|
27896
28478
|
const lines = [
|
|
27897
28479
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
27898
28480
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -28261,6 +28843,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
28261
28843
|
if (s.contextAutoCompact !== void 0) {
|
|
28262
28844
|
autoCompactor?.setEnabled(s.contextAutoCompact);
|
|
28263
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
|
+
}
|
|
28264
28853
|
} catch {
|
|
28265
28854
|
}
|
|
28266
28855
|
},
|