@wrongstack/cli 0.148.0 → 0.155.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1750 -627
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens } from '@wrongstack/core';
|
|
3
2
|
import * as fsp4 from 'fs/promises';
|
|
4
|
-
import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
|
|
5
3
|
import * as path10 from 'path';
|
|
6
4
|
import { join } from 'path';
|
|
5
|
+
import { color, writeErr, expectDefined, atomicWrite, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, resolveContextWindowPolicy, DEFAULT_CONTEXT_WINDOW_MODE_ID, listContextWindowModes, repairToolUseAdjacency, TOKENS, DefaultPathResolver, EventBus, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, noOpVault, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore as DefaultSessionStore$1, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens, AGENTS_BY_PHASE } from '@wrongstack/core';
|
|
6
|
+
import { decryptConfigSecrets, encryptConfigSecrets, DefaultSecretVault, isSecretField } from '@wrongstack/core/security';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os2 from 'os';
|
|
9
9
|
import os2__default from 'os';
|
|
10
10
|
import * as crypto2 from 'crypto';
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
import { findFreePort, createHttpServer, openBrowser, registerInstance, unregisterInstance } from '@wrongstack/webui/server';
|
|
13
|
+
import { DefaultSessionStore } from '@wrongstack/core/storage';
|
|
13
14
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
14
15
|
import { spawn, exec, execFileSync } from 'child_process';
|
|
15
16
|
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
@@ -36,6 +37,218 @@ var __export = (target, all) => {
|
|
|
36
37
|
for (var name in all)
|
|
37
38
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
38
39
|
};
|
|
40
|
+
function parseSubcommand(args) {
|
|
41
|
+
const parts = args.trim().split(/\s+/);
|
|
42
|
+
return { cmd: (parts[0] ?? "").toLowerCase(), rest: parts.slice(1) };
|
|
43
|
+
}
|
|
44
|
+
function unknownSubcommand(cmd, valid, name) {
|
|
45
|
+
const list = valid.join(", ");
|
|
46
|
+
return name ? `Unknown subcommand "${cmd}" for /${name}. Valid: ${list}.` : `Unknown subcommand "${cmd}". Valid: ${list}.`;
|
|
47
|
+
}
|
|
48
|
+
async function pathExists(file) {
|
|
49
|
+
try {
|
|
50
|
+
await fsp4.access(file);
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function detectPackageManager(root, declared) {
|
|
57
|
+
if (declared) {
|
|
58
|
+
const name = declared.split("@")[0];
|
|
59
|
+
if (name) return name;
|
|
60
|
+
}
|
|
61
|
+
if (await pathExists(path10.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
62
|
+
if (await pathExists(path10.join(root, "bun.lockb"))) return "bun";
|
|
63
|
+
if (await pathExists(path10.join(root, "bun.lock"))) return "bun";
|
|
64
|
+
if (await pathExists(path10.join(root, "yarn.lock"))) return "yarn";
|
|
65
|
+
return "npm";
|
|
66
|
+
}
|
|
67
|
+
function hasUsableScript(scripts, name) {
|
|
68
|
+
const script = scripts[name];
|
|
69
|
+
if (typeof script !== "string" || script.trim() === "") return false;
|
|
70
|
+
if (name === "test" && /no test specified/i.test(script)) return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function parseMakeTargets(makefile) {
|
|
74
|
+
const targets = /* @__PURE__ */ new Set();
|
|
75
|
+
for (const line of makefile.split(/\r?\n/)) {
|
|
76
|
+
if (line.startsWith(" ") || line.trimStart().startsWith("#")) continue;
|
|
77
|
+
const match = /^([A-Za-z0-9_.-]+)\s*:(?![=])/.exec(line);
|
|
78
|
+
if (match?.[1]) targets.add(match[1]);
|
|
79
|
+
}
|
|
80
|
+
return targets;
|
|
81
|
+
}
|
|
82
|
+
async function detectProjectFacts(root) {
|
|
83
|
+
const facts = { hints: [] };
|
|
84
|
+
try {
|
|
85
|
+
const pkg = JSON.parse(await fsp4.readFile(path10.join(root, "package.json"), "utf8"));
|
|
86
|
+
const scripts = pkg.scripts ?? {};
|
|
87
|
+
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
88
|
+
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
89
|
+
if (hasUsableScript(scripts, "test")) facts.test = `${pm} test`;
|
|
90
|
+
if (hasUsableScript(scripts, "lint")) facts.lint = `${pm} run lint`;
|
|
91
|
+
const runScript = ["dev", "start", "serve", "preview"].find(
|
|
92
|
+
(name) => hasUsableScript(scripts, name)
|
|
93
|
+
);
|
|
94
|
+
if (runScript) facts.run = `${pm} run ${runScript}`;
|
|
95
|
+
facts.hints.push(Object.keys(scripts).length > 0 ? "package.json scripts" : "package.json");
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
if (!await pathExists(path10.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
100
|
+
facts.test ??= "pytest";
|
|
101
|
+
facts.lint ??= "ruff check .";
|
|
102
|
+
facts.hints.push("pyproject.toml");
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
if (!await pathExists(path10.join(root, "go.mod"))) throw new Error("not go");
|
|
107
|
+
facts.build ??= "go build ./...";
|
|
108
|
+
facts.test ??= "go test ./...";
|
|
109
|
+
facts.run ??= "go run .";
|
|
110
|
+
facts.hints.push("go.mod");
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
if (!await pathExists(path10.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
115
|
+
facts.build ??= "cargo build";
|
|
116
|
+
facts.test ??= "cargo test";
|
|
117
|
+
facts.lint ??= "cargo clippy";
|
|
118
|
+
facts.run ??= "cargo run";
|
|
119
|
+
facts.hints.push("Cargo.toml");
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const makefile = await fsp4.readFile(path10.join(root, "Makefile"), "utf8");
|
|
124
|
+
const targets = parseMakeTargets(makefile);
|
|
125
|
+
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
126
|
+
if (targets.has("test")) facts.test ??= "make test";
|
|
127
|
+
if (targets.has("lint")) facts.lint ??= "make lint";
|
|
128
|
+
const runTarget = ["run", "dev", "start", "serve"].find((name) => targets.has(name));
|
|
129
|
+
if (runTarget) facts.run ??= `make ${runTarget}`;
|
|
130
|
+
facts.hints.push("Makefile");
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
return facts;
|
|
134
|
+
}
|
|
135
|
+
function renderAgentsTemplate(f) {
|
|
136
|
+
const cmd = (s) => s ? `\`${s}\`` : "_TODO_";
|
|
137
|
+
const hints = f.hints.length > 0 ? `
|
|
138
|
+
|
|
139
|
+
> Auto-detected: ${f.hints.join(", ")}` : "";
|
|
140
|
+
return `# AGENTS.md
|
|
141
|
+
|
|
142
|
+
> **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
|
|
143
|
+
> persistent project context. Previous content here may contain decisions,
|
|
144
|
+
> architecture notes, domain knowledge, or verification history that should be
|
|
145
|
+
> preserved. Merge additions rather than replacing.
|
|
146
|
+
|
|
147
|
+
## Project brief
|
|
148
|
+
|
|
149
|
+
- **Purpose:** _What does this project do and why does it exist?_
|
|
150
|
+
- **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
|
|
151
|
+
- **Runtime / deployment:** _CLI, server, browser, worker, library, package?_${hints}
|
|
152
|
+
|
|
153
|
+
## How to work safely
|
|
154
|
+
|
|
155
|
+
- _Project-specific rules the agent should always follow._
|
|
156
|
+
- _Files, generated artifacts, migrations, or config the agent should not edit without asking._
|
|
157
|
+
- _Preferred style or architecture choices not obvious from the code._
|
|
158
|
+
- _Known fragile areas or historical bugs that deserve extra caution._
|
|
159
|
+
|
|
160
|
+
## Commands
|
|
161
|
+
|
|
162
|
+
| Command | Script |
|
|
163
|
+
|---------|--------|
|
|
164
|
+
| Build | ${cmd(f.build)} |
|
|
165
|
+
| Test | ${cmd(f.test)} |
|
|
166
|
+
| Lint | ${cmd(f.lint)} |
|
|
167
|
+
| Run locally | ${cmd(f.run)} |
|
|
168
|
+
|
|
169
|
+
## Key files and entry points
|
|
170
|
+
|
|
171
|
+
| File / directory | Role |
|
|
172
|
+
|---|---|
|
|
173
|
+
| _src/_ | _Main source entry point(s)_ |
|
|
174
|
+
| _tests/_ | _Test root or convention_ |
|
|
175
|
+
| _docs/_ | _Architecture, runbooks, design notes_ |
|
|
176
|
+
| _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
|
|
177
|
+
|
|
178
|
+
## Architecture notes
|
|
179
|
+
|
|
180
|
+
_Summarize the important modules, data flow, boundaries, and ownership rules.
|
|
181
|
+
Mention anything a newcomer might misread or that looks unusual but is intentional._
|
|
182
|
+
|
|
183
|
+
### Dependency layers
|
|
184
|
+
|
|
185
|
+
_Describe the key dependency direction or layered structure, e.g.: "core has no
|
|
186
|
+
runtime deps; cli assembles everything above it."_
|
|
187
|
+
|
|
188
|
+
### Extension points
|
|
189
|
+
|
|
190
|
+
_Plugin, MCP, extension hooks, custom tools \u2014 what's wired up and how._
|
|
191
|
+
|
|
192
|
+
## Domain knowledge
|
|
193
|
+
|
|
194
|
+
_Business rules, acronyms, invariants, external services, and notes where the
|
|
195
|
+
code looks unusual but is intentional. E.g.: "IDs are ULIDs, not UUIDs", "the
|
|
196
|
+
\`draft\` flag means uncommitted billing metadata", "MCP servers are restarted
|
|
197
|
+
on disconnect with exponential backoff, up to 3 attempts"._
|
|
198
|
+
|
|
199
|
+
## Verification checklist
|
|
200
|
+
|
|
201
|
+
- _What should be run after code changes?_
|
|
202
|
+
- _What manual smoke test proves the common path still works?_
|
|
203
|
+
- _What failure modes deserve extra attention?_
|
|
204
|
+
- _Any known flaky tests or environment-dependent behavior?_
|
|
205
|
+
|
|
206
|
+
## Useful pointers
|
|
207
|
+
|
|
208
|
+
- _Docs, dashboards, runbooks, issue trackers, design notes, owner contacts._
|
|
209
|
+
- _Related projects or repositories._`;
|
|
210
|
+
}
|
|
211
|
+
function countTurnPairs(messages) {
|
|
212
|
+
let count = 0;
|
|
213
|
+
for (const m of messages) {
|
|
214
|
+
if (m.role === "user" || m.role === "assistant") count++;
|
|
215
|
+
}
|
|
216
|
+
return Math.floor(count / 2);
|
|
217
|
+
}
|
|
218
|
+
function countToolUses(messages) {
|
|
219
|
+
let count = 0;
|
|
220
|
+
for (const m of messages) {
|
|
221
|
+
if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_use").length;
|
|
222
|
+
}
|
|
223
|
+
return count;
|
|
224
|
+
}
|
|
225
|
+
function countToolResults(messages) {
|
|
226
|
+
let count = 0;
|
|
227
|
+
for (const m of messages) {
|
|
228
|
+
if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_result").length;
|
|
229
|
+
}
|
|
230
|
+
return count;
|
|
231
|
+
}
|
|
232
|
+
function estimateTokens(messages) {
|
|
233
|
+
return estimateMessageTokens(messages);
|
|
234
|
+
}
|
|
235
|
+
var init_helpers = __esm({
|
|
236
|
+
"src/slash-commands/helpers.ts"() {
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// src/provider-config-utils.ts
|
|
241
|
+
var provider_config_utils_exports = {};
|
|
242
|
+
__export(provider_config_utils_exports, {
|
|
243
|
+
activeLabel: () => activeLabel,
|
|
244
|
+
expectDefined: () => expectDefined,
|
|
245
|
+
loadConfigProviders: () => loadConfigProviders,
|
|
246
|
+
maskedKey: () => maskedKey,
|
|
247
|
+
mutateConfigProviders: () => mutateConfigProviders,
|
|
248
|
+
normalizeKeys: () => normalizeKeys,
|
|
249
|
+
nowIso: () => nowIso,
|
|
250
|
+
writeKeysBack: () => writeKeysBack
|
|
251
|
+
});
|
|
39
252
|
function normalizeKeys(cfg) {
|
|
40
253
|
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
41
254
|
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
@@ -655,17 +868,17 @@ function buildSddCommand(opts) {
|
|
|
655
868
|
const specsDir = opts.paths.projectSpecs;
|
|
656
869
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
657
870
|
const versioning = sddState.getVersioning();
|
|
658
|
-
const
|
|
659
|
-
const restJoined =
|
|
660
|
-
switch (
|
|
871
|
+
const { cmd, rest: restArgs } = parseSubcommand(args);
|
|
872
|
+
const restJoined = restArgs.join(" ").trim();
|
|
873
|
+
switch (cmd) {
|
|
661
874
|
case "":
|
|
662
875
|
case "help":
|
|
663
876
|
return { message: sddHelp() };
|
|
664
877
|
// ── AI-Driven Spec Session ─────────────────────────────────────────
|
|
665
878
|
case "new":
|
|
666
879
|
case "create": {
|
|
667
|
-
const forceFlag =
|
|
668
|
-
const title =
|
|
880
|
+
const forceFlag = restArgs.includes("--force") || restArgs.includes("-f");
|
|
881
|
+
const title = restArgs.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
669
882
|
if (!sessionState.getBuilder() && !forceFlag) {
|
|
670
883
|
const sessionPath = opts.paths.projectSddSession;
|
|
671
884
|
try {
|
|
@@ -1571,7 +1784,7 @@ ${lines.join("\n")}`
|
|
|
1571
1784
|
}
|
|
1572
1785
|
default:
|
|
1573
1786
|
return {
|
|
1574
|
-
message:
|
|
1787
|
+
message: `${unknownSubcommand(cmd, ["new", "approve", "execute", "cancel", "status", "list", "show", "templates", "resume"], "sdd")}
|
|
1575
1788
|
|
|
1576
1789
|
${sddHelp()}`
|
|
1577
1790
|
};
|
|
@@ -1581,6 +1794,7 @@ ${sddHelp()}`
|
|
|
1581
1794
|
}
|
|
1582
1795
|
var init_sdd = __esm({
|
|
1583
1796
|
"src/slash-commands/sdd.ts"() {
|
|
1797
|
+
init_helpers();
|
|
1584
1798
|
init_state();
|
|
1585
1799
|
init_task_manager();
|
|
1586
1800
|
init_project_context();
|
|
@@ -1722,6 +1936,91 @@ var webui_server_exports = {};
|
|
|
1722
1936
|
__export(webui_server_exports, {
|
|
1723
1937
|
runWebUI: () => runWebUI
|
|
1724
1938
|
});
|
|
1939
|
+
function isHiddenEntry(name) {
|
|
1940
|
+
return name.startsWith(".") && !KEEP_DOTFILES.has(name);
|
|
1941
|
+
}
|
|
1942
|
+
function rankFiles(paths, query, limit) {
|
|
1943
|
+
const q = query.toLowerCase();
|
|
1944
|
+
const scored = [];
|
|
1945
|
+
for (const p of paths) {
|
|
1946
|
+
if (!q) {
|
|
1947
|
+
scored.push({ path: p, score: 0 });
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
const lower = p.toLowerCase();
|
|
1951
|
+
const base = lower.split("/").pop() ?? lower;
|
|
1952
|
+
let score = 0;
|
|
1953
|
+
if (base === q) score = 100;
|
|
1954
|
+
else if (base.startsWith(q)) score = 60;
|
|
1955
|
+
else if (lower.includes(q)) score = 20;
|
|
1956
|
+
else continue;
|
|
1957
|
+
score -= p.split("/").length;
|
|
1958
|
+
scored.push({ path: p, score });
|
|
1959
|
+
}
|
|
1960
|
+
scored.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
1961
|
+
return scored.slice(0, limit).map((s) => s.path);
|
|
1962
|
+
}
|
|
1963
|
+
function estimateTokens2(s) {
|
|
1964
|
+
return Math.ceil(s.length / 4);
|
|
1965
|
+
}
|
|
1966
|
+
function stringifyContent(c) {
|
|
1967
|
+
if (typeof c === "string") return c;
|
|
1968
|
+
try {
|
|
1969
|
+
return JSON.stringify(c);
|
|
1970
|
+
} catch {
|
|
1971
|
+
return String(c);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
function messageTokens(content) {
|
|
1975
|
+
if (typeof content === "string") return estimateTokens2(content);
|
|
1976
|
+
if (!Array.isArray(content)) return 0;
|
|
1977
|
+
let tk = 0;
|
|
1978
|
+
for (const b of content) {
|
|
1979
|
+
if (b.type === "text") tk += estimateTokens2(b.text ?? "");
|
|
1980
|
+
else if (b.type === "tool_use") tk += estimateTokens2(stringifyContent(b.input));
|
|
1981
|
+
else if (b.type === "tool_result") tk += estimateTokens2(stringifyContent(b.content));
|
|
1982
|
+
else tk += estimateTokens2(stringifyContent(b));
|
|
1983
|
+
}
|
|
1984
|
+
return tk;
|
|
1985
|
+
}
|
|
1986
|
+
function messagePreview(content) {
|
|
1987
|
+
if (typeof content === "string") return content.slice(0, 60);
|
|
1988
|
+
if (!Array.isArray(content)) return "";
|
|
1989
|
+
return content.map((b) => b.type === "text" ? (b.text ?? "").slice(0, 40) : b.type === "tool_use" ? `[tool_use: ${b.name}]` : b.type === "tool_result" ? "[tool_result]" : `[${b.type}]`).join(" ").slice(0, 60);
|
|
1990
|
+
}
|
|
1991
|
+
function estimateContextBreakdown(input) {
|
|
1992
|
+
const sysTokens = input.systemPrompt.reduce((acc, b) => acc + estimateTokens2(b.text ?? ""), 0);
|
|
1993
|
+
const toolBreakdown = input.tools.map((t) => {
|
|
1994
|
+
const schema = t.inputSchema ?? {};
|
|
1995
|
+
const desc = t.description ?? "";
|
|
1996
|
+
return { name: t.name, tokens: estimateTokens2(t.name) + estimateTokens2(desc) + estimateTokens2(stringifyContent(schema)) };
|
|
1997
|
+
});
|
|
1998
|
+
const toolTokens = toolBreakdown.reduce((a, b) => a + b.tokens, 0);
|
|
1999
|
+
const messageBreakdown = input.messages.map((m, i) => ({
|
|
2000
|
+
index: i,
|
|
2001
|
+
role: m.role,
|
|
2002
|
+
tokens: messageTokens(m.content),
|
|
2003
|
+
preview: messagePreview(m.content)
|
|
2004
|
+
}));
|
|
2005
|
+
const msgTokens = messageBreakdown.reduce((a, b) => a + b.tokens, 0);
|
|
2006
|
+
return {
|
|
2007
|
+
total: sysTokens + toolTokens + msgTokens,
|
|
2008
|
+
systemPrompt: sysTokens,
|
|
2009
|
+
tools: { total: toolTokens, count: input.tools.length, breakdown: toolBreakdown },
|
|
2010
|
+
messages: { total: msgTokens, count: input.messages.length, breakdown: messageBreakdown }
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
function getCostRates(model) {
|
|
2014
|
+
const cost = model?.cost;
|
|
2015
|
+
return {
|
|
2016
|
+
input: cost?.input ?? 0,
|
|
2017
|
+
output: cost?.output ?? 0,
|
|
2018
|
+
cacheRead: cost?.cache_read ?? 0
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
function computeUsageCost(usage, rates) {
|
|
2022
|
+
return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
|
|
2023
|
+
}
|
|
1725
2024
|
async function runWebUI(opts) {
|
|
1726
2025
|
const host = "127.0.0.1";
|
|
1727
2026
|
const requestedWsPort = opts.port ?? 3457;
|
|
@@ -2069,7 +2368,9 @@ async function runWebUI(opts) {
|
|
|
2069
2368
|
sessionId: opts.session.id,
|
|
2070
2369
|
model: opts.agent.ctx.model,
|
|
2071
2370
|
provider: opts.agent.ctx.provider.id,
|
|
2072
|
-
wsToken: authToken
|
|
2371
|
+
wsToken: authToken,
|
|
2372
|
+
mode: opts.modeId ?? "default",
|
|
2373
|
+
projectName: opts.projectRoot ? path10.basename(opts.projectRoot) : void 0
|
|
2073
2374
|
}
|
|
2074
2375
|
});
|
|
2075
2376
|
});
|
|
@@ -2159,50 +2460,729 @@ async function runWebUI(opts) {
|
|
|
2159
2460
|
await handleProviderRemove(ws, m.payload.providerId);
|
|
2160
2461
|
break;
|
|
2161
2462
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2463
|
+
case "todos.get": {
|
|
2464
|
+
send(ws, {
|
|
2465
|
+
type: "todos.updated",
|
|
2466
|
+
payload: { todos: [...opts.agent.ctx.todos] }
|
|
2467
|
+
});
|
|
2166
2468
|
break;
|
|
2167
2469
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
}
|
|
2178
|
-
abortController = new AbortController();
|
|
2179
|
-
try {
|
|
2180
|
-
const result = await opts.agent.run(content, {
|
|
2181
|
-
signal: abortController.signal
|
|
2182
|
-
});
|
|
2183
|
-
send(ws, {
|
|
2184
|
-
type: "run.result",
|
|
2185
|
-
payload: {
|
|
2186
|
-
status: result.status,
|
|
2187
|
-
iterations: result.iterations,
|
|
2188
|
-
finalText: result.finalText,
|
|
2189
|
-
error: result.error ? {
|
|
2190
|
-
code: result.error.code,
|
|
2191
|
-
message: result.error.message,
|
|
2192
|
-
recoverable: result.error.recoverable
|
|
2193
|
-
} : void 0
|
|
2194
|
-
}
|
|
2195
|
-
});
|
|
2196
|
-
} catch (err) {
|
|
2197
|
-
send(ws, {
|
|
2198
|
-
type: "error",
|
|
2199
|
-
payload: {
|
|
2200
|
-
phase: "agent.run",
|
|
2201
|
-
message: err instanceof Error ? err.message : String(err)
|
|
2470
|
+
case "goal.get": {
|
|
2471
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
2472
|
+
try {
|
|
2473
|
+
const goalPath = path10.join(projectRoot, ".wrongstack", "goal.json");
|
|
2474
|
+
const raw = await fsp4.readFile(goalPath, "utf8");
|
|
2475
|
+
const goal = JSON.parse(raw);
|
|
2476
|
+
broadcast({ type: "goal.updated", payload: goal });
|
|
2477
|
+
} catch {
|
|
2478
|
+
broadcast({ type: "goal.updated", payload: null });
|
|
2202
2479
|
}
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2480
|
+
break;
|
|
2481
|
+
}
|
|
2482
|
+
case "sessions.list": {
|
|
2483
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
2484
|
+
const sessionsDir = path10.join(projectRoot, ".wrongstack", "sessions");
|
|
2485
|
+
const limit = msg.payload?.limit ?? 50;
|
|
2486
|
+
try {
|
|
2487
|
+
const store = new DefaultSessionStore({ dir: sessionsDir });
|
|
2488
|
+
const list = await store.list(limit);
|
|
2489
|
+
send(ws, {
|
|
2490
|
+
type: "sessions.list",
|
|
2491
|
+
payload: {
|
|
2492
|
+
sessions: list.map((s) => ({
|
|
2493
|
+
id: s.id,
|
|
2494
|
+
title: s.title,
|
|
2495
|
+
startedAt: s.startedAt,
|
|
2496
|
+
model: s.model,
|
|
2497
|
+
provider: s.provider,
|
|
2498
|
+
tokenTotal: s.tokenTotal,
|
|
2499
|
+
isCurrent: s.id === opts.session.id
|
|
2500
|
+
}))
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
} catch (err) {
|
|
2504
|
+
send(ws, {
|
|
2505
|
+
type: "sessions.list",
|
|
2506
|
+
payload: { sessions: [], error: err instanceof Error ? err.message : String(err) }
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
break;
|
|
2510
|
+
}
|
|
2511
|
+
case "session.new": {
|
|
2512
|
+
const ctx = opts.agent.ctx;
|
|
2513
|
+
ctx.state.replaceMessages([]);
|
|
2514
|
+
ctx.state.replaceTodos([]);
|
|
2515
|
+
ctx.readFiles.clear();
|
|
2516
|
+
ctx.fileMtimes.clear();
|
|
2517
|
+
broadcast({
|
|
2518
|
+
type: "session.start",
|
|
2519
|
+
payload: {
|
|
2520
|
+
sessionId: opts.session.id,
|
|
2521
|
+
model: ctx.model,
|
|
2522
|
+
provider: opts.agent.ctx.provider.id,
|
|
2523
|
+
reset: true
|
|
2524
|
+
}
|
|
2525
|
+
});
|
|
2526
|
+
break;
|
|
2527
|
+
}
|
|
2528
|
+
case "todos.clear": {
|
|
2529
|
+
opts.agent.ctx.state.replaceTodos([]);
|
|
2530
|
+
sendResult(ws, true, "Todos cleared");
|
|
2531
|
+
broadcast({ type: "todos.updated", payload: { todos: [] } });
|
|
2532
|
+
break;
|
|
2533
|
+
}
|
|
2534
|
+
case "todos.remove": {
|
|
2535
|
+
const payload = msg.payload;
|
|
2536
|
+
if (!payload) {
|
|
2537
|
+
sendResult(ws, false, "Missing id or index");
|
|
2538
|
+
break;
|
|
2539
|
+
}
|
|
2540
|
+
const { id, index } = payload;
|
|
2541
|
+
const todos = opts.agent.ctx.todos;
|
|
2542
|
+
let targetIdx = -1;
|
|
2543
|
+
if (typeof id === "string") {
|
|
2544
|
+
targetIdx = todos.findIndex((t) => t.id === id);
|
|
2545
|
+
} else if (typeof index === "number" && index > 0) {
|
|
2546
|
+
targetIdx = index - 1;
|
|
2547
|
+
}
|
|
2548
|
+
if (targetIdx < 0 || !todos[targetIdx]) {
|
|
2549
|
+
sendResult(ws, false, "Todo not found");
|
|
2550
|
+
break;
|
|
2551
|
+
}
|
|
2552
|
+
const removed = expectDefined(todos[targetIdx]);
|
|
2553
|
+
const next = [...todos.slice(0, targetIdx), ...todos.slice(targetIdx + 1)];
|
|
2554
|
+
opts.agent.ctx.state.replaceTodos(next);
|
|
2555
|
+
sendResult(ws, true, `Removed: ${removed.content}`);
|
|
2556
|
+
broadcast({ type: "todos.updated", payload: { todos: next } });
|
|
2557
|
+
break;
|
|
2558
|
+
}
|
|
2559
|
+
case "context.clear": {
|
|
2560
|
+
const ctx = opts.agent.ctx;
|
|
2561
|
+
ctx.state.replaceMessages([]);
|
|
2562
|
+
ctx.state.replaceTodos([]);
|
|
2563
|
+
ctx.readFiles.clear();
|
|
2564
|
+
ctx.fileMtimes.clear();
|
|
2565
|
+
sendResult(ws, true, "Context cleared");
|
|
2566
|
+
broadcast({
|
|
2567
|
+
type: "session.start",
|
|
2568
|
+
payload: {
|
|
2569
|
+
sessionId: opts.session.id,
|
|
2570
|
+
model: ctx.model,
|
|
2571
|
+
provider: ctx.provider.id,
|
|
2572
|
+
reset: true
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2577
|
+
case "process.list": {
|
|
2578
|
+
try {
|
|
2579
|
+
const { getProcessRegistry } = await import('@wrongstack/tools');
|
|
2580
|
+
const procs = getProcessRegistry().list();
|
|
2581
|
+
send(ws, {
|
|
2582
|
+
type: "process.list",
|
|
2583
|
+
payload: {
|
|
2584
|
+
processes: procs.map((p) => ({
|
|
2585
|
+
pid: p.pid,
|
|
2586
|
+
command: p.command,
|
|
2587
|
+
tool: p.name,
|
|
2588
|
+
startedAt: p.startedAt,
|
|
2589
|
+
status: p.killed ? "killed" : "running",
|
|
2590
|
+
protected: p.protected
|
|
2591
|
+
}))
|
|
2592
|
+
}
|
|
2593
|
+
});
|
|
2594
|
+
} catch {
|
|
2595
|
+
send(ws, { type: "process.list", payload: { processes: [] } });
|
|
2596
|
+
}
|
|
2597
|
+
break;
|
|
2598
|
+
}
|
|
2599
|
+
case "process.kill": {
|
|
2600
|
+
const { pid } = msg.payload;
|
|
2601
|
+
try {
|
|
2602
|
+
const { getProcessRegistry } = await import('@wrongstack/tools');
|
|
2603
|
+
const proc = getProcessRegistry().get(pid);
|
|
2604
|
+
if (proc?.protected) {
|
|
2605
|
+
sendResult(ws, false, `Cannot kill protected process (PID ${pid})`);
|
|
2606
|
+
break;
|
|
2607
|
+
}
|
|
2608
|
+
getProcessRegistry().kill(pid);
|
|
2609
|
+
sendResult(ws, true, `Killed PID ${pid}`);
|
|
2610
|
+
} catch (err) {
|
|
2611
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
2612
|
+
}
|
|
2613
|
+
break;
|
|
2614
|
+
}
|
|
2615
|
+
case "process.killAll": {
|
|
2616
|
+
try {
|
|
2617
|
+
const { getProcessRegistry } = await import('@wrongstack/tools');
|
|
2618
|
+
getProcessRegistry().killAll();
|
|
2619
|
+
sendResult(ws, true, "All processes killed");
|
|
2620
|
+
} catch (err) {
|
|
2621
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
2622
|
+
}
|
|
2623
|
+
break;
|
|
2624
|
+
}
|
|
2625
|
+
case "diag.get": {
|
|
2626
|
+
const ctx = opts.agent.ctx;
|
|
2627
|
+
const tools = opts.agent.tools.list();
|
|
2628
|
+
send(ws, {
|
|
2629
|
+
type: "diag.get",
|
|
2630
|
+
payload: {
|
|
2631
|
+
provider: ctx.provider.id,
|
|
2632
|
+
model: ctx.model,
|
|
2633
|
+
cwd: opts.projectRoot ?? ctx.projectRoot,
|
|
2634
|
+
sessionId: opts.session.id,
|
|
2635
|
+
tools: {
|
|
2636
|
+
count: tools.length,
|
|
2637
|
+
names: tools.map((t) => t.name)
|
|
2638
|
+
},
|
|
2639
|
+
features: {},
|
|
2640
|
+
mode: "default",
|
|
2641
|
+
usage: ctx.tokenCounter.total(),
|
|
2642
|
+
messages: ctx.messages.length,
|
|
2643
|
+
todos: ctx.todos.length
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
break;
|
|
2647
|
+
}
|
|
2648
|
+
case "stats.get": {
|
|
2649
|
+
const ctx = opts.agent.ctx;
|
|
2650
|
+
const usage = ctx.tokenCounter.total();
|
|
2651
|
+
const cacheStats = ctx.tokenCounter.cacheStats();
|
|
2652
|
+
let cost = null;
|
|
2653
|
+
try {
|
|
2654
|
+
if (opts.modelsRegistry) {
|
|
2655
|
+
const model = await opts.modelsRegistry.getModel(
|
|
2656
|
+
ctx.provider.id,
|
|
2657
|
+
ctx.model
|
|
2658
|
+
);
|
|
2659
|
+
const rates = getCostRates(model);
|
|
2660
|
+
cost = computeUsageCost(
|
|
2661
|
+
{ input: usage.input, output: usage.output, cacheRead: cacheStats.readTokens },
|
|
2662
|
+
rates
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
} catch {
|
|
2666
|
+
}
|
|
2667
|
+
send(ws, {
|
|
2668
|
+
type: "stats.get",
|
|
2669
|
+
payload: {
|
|
2670
|
+
sessionId: opts.session.id,
|
|
2671
|
+
provider: ctx.provider.id,
|
|
2672
|
+
model: ctx.model,
|
|
2673
|
+
usage,
|
|
2674
|
+
cache: cacheStats,
|
|
2675
|
+
cost,
|
|
2676
|
+
messages: ctx.messages.length,
|
|
2677
|
+
readFiles: ctx.readFiles.size,
|
|
2678
|
+
tools: opts.agent.tools.list().length,
|
|
2679
|
+
elapsedMs: 0
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
break;
|
|
2683
|
+
}
|
|
2684
|
+
case "autonomy.switch": {
|
|
2685
|
+
const { mode } = msg.payload;
|
|
2686
|
+
opts.agent.ctx.meta["autonomy"] = mode;
|
|
2687
|
+
sendResult(ws, true, `Autonomy mode set to "${mode}"`);
|
|
2688
|
+
break;
|
|
2689
|
+
}
|
|
2690
|
+
case "tools.list": {
|
|
2691
|
+
const list = opts.agent.tools.list().map((t) => {
|
|
2692
|
+
const schema = t.inputSchema ?? {};
|
|
2693
|
+
const params = schema.properties ? Object.keys(schema.properties) : [];
|
|
2694
|
+
return {
|
|
2695
|
+
name: t.name,
|
|
2696
|
+
description: t.description ?? "",
|
|
2697
|
+
params
|
|
2698
|
+
};
|
|
2699
|
+
});
|
|
2700
|
+
send(ws, { type: "tools.list", payload: { tools: list } });
|
|
2701
|
+
break;
|
|
2702
|
+
}
|
|
2703
|
+
case "session.checkpoints": {
|
|
2704
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
2705
|
+
try {
|
|
2706
|
+
const { DefaultSessionRewinder: DefaultSessionRewinder2 } = await import('@wrongstack/core');
|
|
2707
|
+
const rewinder = new DefaultSessionRewinder2(
|
|
2708
|
+
path10.join(projectRoot, ".wrongstack", "sessions"),
|
|
2709
|
+
projectRoot
|
|
2710
|
+
);
|
|
2711
|
+
const checkpoints = await rewinder.listCheckpoints(opts.session.id);
|
|
2712
|
+
send(ws, {
|
|
2713
|
+
type: "session.checkpoints",
|
|
2714
|
+
payload: { checkpoints }
|
|
2715
|
+
});
|
|
2716
|
+
} catch {
|
|
2717
|
+
send(ws, {
|
|
2718
|
+
type: "session.checkpoints",
|
|
2719
|
+
payload: { checkpoints: [] }
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
break;
|
|
2723
|
+
}
|
|
2724
|
+
case "files.list": {
|
|
2725
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
2726
|
+
const payload = msg.payload ?? {};
|
|
2727
|
+
const limit = payload.limit ?? 50;
|
|
2728
|
+
const results = [];
|
|
2729
|
+
async function walk(dir, rel, depth) {
|
|
2730
|
+
if (depth > 8 || results.length >= 600) return;
|
|
2731
|
+
let entries = [];
|
|
2732
|
+
try {
|
|
2733
|
+
entries = await fsp4.readdir(dir, { withFileTypes: true });
|
|
2734
|
+
} catch {
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2737
|
+
for (const e of entries) {
|
|
2738
|
+
if (results.length >= 600) return;
|
|
2739
|
+
if (isHiddenEntry(e.name)) continue;
|
|
2740
|
+
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
2741
|
+
if (e.isDirectory()) {
|
|
2742
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
2743
|
+
await walk(path10.join(dir, e.name), childRel, depth + 1);
|
|
2744
|
+
} else if (e.isFile()) {
|
|
2745
|
+
results.push(childRel);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
await walk(projectRoot, "", 0);
|
|
2750
|
+
send(ws, {
|
|
2751
|
+
type: "files.list",
|
|
2752
|
+
payload: { files: rankFiles(results, payload.query ?? "", limit) }
|
|
2753
|
+
});
|
|
2754
|
+
break;
|
|
2755
|
+
}
|
|
2756
|
+
case "session.delete": {
|
|
2757
|
+
const { id } = msg.payload;
|
|
2758
|
+
if (id === opts.session.id) {
|
|
2759
|
+
sendResult(ws, false, "Cannot delete the active session");
|
|
2760
|
+
break;
|
|
2761
|
+
}
|
|
2762
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
|
|
2763
|
+
try {
|
|
2764
|
+
const store = new DefaultSessionStore({
|
|
2765
|
+
dir: path10.join(projectRoot, ".wrongstack", "sessions")
|
|
2766
|
+
});
|
|
2767
|
+
await store.delete(id);
|
|
2768
|
+
sendResult(ws, true, `Session ${id} deleted`);
|
|
2769
|
+
} catch (err) {
|
|
2770
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
2771
|
+
}
|
|
2772
|
+
break;
|
|
2773
|
+
}
|
|
2774
|
+
case "session.save":
|
|
2775
|
+
sendResult(ws, true, `Session ${opts.session.id} is auto-saved`);
|
|
2776
|
+
break;
|
|
2777
|
+
case "plan.get": {
|
|
2778
|
+
const planPath = opts.agent.ctx.meta["plan.path"];
|
|
2779
|
+
if (typeof planPath === "string" && planPath) {
|
|
2780
|
+
try {
|
|
2781
|
+
const { loadPlan: loadPlan2 } = await import('@wrongstack/core');
|
|
2782
|
+
const plan = await loadPlan2(planPath);
|
|
2783
|
+
send(ws, {
|
|
2784
|
+
type: "plan.updated",
|
|
2785
|
+
payload: {
|
|
2786
|
+
plan: plan ?? {
|
|
2787
|
+
version: 1,
|
|
2788
|
+
sessionId: opts.session.id,
|
|
2789
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2790
|
+
items: []
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
});
|
|
2794
|
+
} catch {
|
|
2795
|
+
send(ws, {
|
|
2796
|
+
type: "plan.updated",
|
|
2797
|
+
payload: {
|
|
2798
|
+
plan: {
|
|
2799
|
+
version: 1,
|
|
2800
|
+
sessionId: opts.session.id,
|
|
2801
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2802
|
+
items: []
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
} else {
|
|
2808
|
+
send(ws, {
|
|
2809
|
+
type: "plan.updated",
|
|
2810
|
+
payload: { plan: null, error: "Plan storage is not configured for this session." }
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
case "memory.list": {
|
|
2816
|
+
if (!opts.memoryStore) {
|
|
2817
|
+
send(ws, { type: "memory.list", payload: { text: "", error: "Memory store not available" } });
|
|
2818
|
+
break;
|
|
2819
|
+
}
|
|
2820
|
+
try {
|
|
2821
|
+
const text = await opts.memoryStore.readAll();
|
|
2822
|
+
send(ws, { type: "memory.list", payload: { text } });
|
|
2823
|
+
} catch (err) {
|
|
2824
|
+
send(ws, {
|
|
2825
|
+
type: "memory.list",
|
|
2826
|
+
payload: { text: "", error: err instanceof Error ? err.message : String(err) }
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
break;
|
|
2830
|
+
}
|
|
2831
|
+
case "memory.remember": {
|
|
2832
|
+
if (!opts.memoryStore) {
|
|
2833
|
+
sendResult(ws, false, "Memory store not available");
|
|
2834
|
+
break;
|
|
2835
|
+
}
|
|
2836
|
+
const { text, scope } = msg.payload;
|
|
2837
|
+
try {
|
|
2838
|
+
await opts.memoryStore.remember(text, scope ?? "project-memory");
|
|
2839
|
+
sendResult(ws, true, "Saved to memory");
|
|
2840
|
+
} catch (err) {
|
|
2841
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
2842
|
+
}
|
|
2843
|
+
break;
|
|
2844
|
+
}
|
|
2845
|
+
case "memory.forget": {
|
|
2846
|
+
if (!opts.memoryStore) {
|
|
2847
|
+
sendResult(ws, false, "Memory store not available");
|
|
2848
|
+
break;
|
|
2849
|
+
}
|
|
2850
|
+
const { text, scope } = msg.payload;
|
|
2851
|
+
try {
|
|
2852
|
+
const removed = await opts.memoryStore.forget(text, scope ?? "project-memory");
|
|
2853
|
+
sendResult(
|
|
2854
|
+
ws,
|
|
2855
|
+
removed > 0,
|
|
2856
|
+
removed > 0 ? `Removed ${removed} entr${removed === 1 ? "y" : "ies"}` : "No matching entries"
|
|
2857
|
+
);
|
|
2858
|
+
} catch (err) {
|
|
2859
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
2860
|
+
}
|
|
2861
|
+
break;
|
|
2862
|
+
}
|
|
2863
|
+
case "skills.list": {
|
|
2864
|
+
if (!opts.skillLoader) {
|
|
2865
|
+
send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
|
|
2866
|
+
break;
|
|
2867
|
+
}
|
|
2868
|
+
try {
|
|
2869
|
+
const manifests = await opts.skillLoader.list();
|
|
2870
|
+
const entries = await opts.skillLoader.listEntries();
|
|
2871
|
+
const byName = new Map(entries.map((e) => [e.name, e]));
|
|
2872
|
+
send(ws, {
|
|
2873
|
+
type: "skills.list",
|
|
2874
|
+
payload: {
|
|
2875
|
+
enabled: true,
|
|
2876
|
+
skills: manifests.map((m) => ({
|
|
2877
|
+
name: m.name,
|
|
2878
|
+
description: m.description,
|
|
2879
|
+
version: m.version ?? "",
|
|
2880
|
+
source: m.source,
|
|
2881
|
+
path: m.path,
|
|
2882
|
+
trigger: byName.get(m.name)?.trigger ?? "",
|
|
2883
|
+
scope: byName.get(m.name)?.scope ?? []
|
|
2884
|
+
}))
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
} catch (err) {
|
|
2888
|
+
send(ws, {
|
|
2889
|
+
type: "skills.list",
|
|
2890
|
+
payload: { skills: [], enabled: true, error: err instanceof Error ? err.message : String(err) }
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
break;
|
|
2894
|
+
}
|
|
2895
|
+
case "modes.list": {
|
|
2896
|
+
if (!opts.modeStore) {
|
|
2897
|
+
send(ws, {
|
|
2898
|
+
type: "modes.list",
|
|
2899
|
+
payload: { modes: [], activeId: "default", error: "Mode store not available" }
|
|
2900
|
+
});
|
|
2901
|
+
break;
|
|
2902
|
+
}
|
|
2903
|
+
try {
|
|
2904
|
+
const modes = await opts.modeStore.listModes();
|
|
2905
|
+
const active = await opts.modeStore.getActiveMode();
|
|
2906
|
+
send(ws, {
|
|
2907
|
+
type: "modes.list",
|
|
2908
|
+
payload: {
|
|
2909
|
+
modes: modes.map((m) => ({
|
|
2910
|
+
id: m.id,
|
|
2911
|
+
name: m.name,
|
|
2912
|
+
description: m.description,
|
|
2913
|
+
isActive: m.id === (active?.id ?? "default")
|
|
2914
|
+
})),
|
|
2915
|
+
activeId: active?.id ?? "default"
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
} catch (err) {
|
|
2919
|
+
send(ws, {
|
|
2920
|
+
type: "modes.list",
|
|
2921
|
+
payload: { modes: [], activeId: "default", error: err instanceof Error ? err.message : String(err) }
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
break;
|
|
2925
|
+
}
|
|
2926
|
+
case "mode.switch": {
|
|
2927
|
+
if (!opts.modeStore) {
|
|
2928
|
+
sendResult(ws, false, "Mode store not available");
|
|
2929
|
+
break;
|
|
2930
|
+
}
|
|
2931
|
+
const { id } = msg.payload;
|
|
2932
|
+
try {
|
|
2933
|
+
if (id === "default") {
|
|
2934
|
+
await opts.modeStore.setActiveMode(null);
|
|
2935
|
+
} else {
|
|
2936
|
+
const found = await opts.modeStore.getMode(id);
|
|
2937
|
+
if (!found) throw new Error(`Unknown mode "${id}"`);
|
|
2938
|
+
await opts.modeStore.setActiveMode(id);
|
|
2939
|
+
}
|
|
2940
|
+
opts.agent.ctx.meta["mode"] = id;
|
|
2941
|
+
sendResult(ws, true, `Switched to mode "${id}"`);
|
|
2942
|
+
broadcast({
|
|
2943
|
+
type: "session.start",
|
|
2944
|
+
payload: {
|
|
2945
|
+
sessionId: opts.session.id,
|
|
2946
|
+
model: opts.agent.ctx.model,
|
|
2947
|
+
provider: opts.agent.ctx.provider.id,
|
|
2948
|
+
mode: id,
|
|
2949
|
+
reset: true
|
|
2950
|
+
}
|
|
2951
|
+
});
|
|
2952
|
+
} catch (err) {
|
|
2953
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
2954
|
+
}
|
|
2955
|
+
break;
|
|
2956
|
+
}
|
|
2957
|
+
case "model.switch": {
|
|
2958
|
+
const { provider: newProvider, model: newModel } = msg.payload;
|
|
2959
|
+
try {
|
|
2960
|
+
const ctx = opts.agent.ctx;
|
|
2961
|
+
ctx.model = newModel;
|
|
2962
|
+
const { makeProviderFromConfig: makeProviderFromConfig5 } = await import('@wrongstack/providers');
|
|
2963
|
+
const { loadConfigProviders: loadConfigProviders2 } = await Promise.resolve().then(() => (init_provider_config_utils(), provider_config_utils_exports));
|
|
2964
|
+
const saved = opts.globalConfigPath ? await loadConfigProviders2(opts.globalConfigPath, getVault()) : {};
|
|
2965
|
+
const providerCfg = saved[newProvider] ?? { type: newProvider };
|
|
2966
|
+
const newProv = makeProviderFromConfig5(newProvider, providerCfg);
|
|
2967
|
+
ctx.provider = newProv;
|
|
2968
|
+
send(ws, {
|
|
2969
|
+
type: "key.operation_result",
|
|
2970
|
+
payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
|
|
2971
|
+
});
|
|
2972
|
+
broadcast({
|
|
2973
|
+
type: "session.start",
|
|
2974
|
+
payload: {
|
|
2975
|
+
sessionId: opts.session.id,
|
|
2976
|
+
model: newModel,
|
|
2977
|
+
provider: newProvider
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
} catch (err) {
|
|
2981
|
+
send(ws, {
|
|
2982
|
+
type: "key.operation_result",
|
|
2983
|
+
payload: {
|
|
2984
|
+
success: false,
|
|
2985
|
+
message: `Switch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2986
|
+
}
|
|
2987
|
+
});
|
|
2988
|
+
}
|
|
2989
|
+
break;
|
|
2990
|
+
}
|
|
2991
|
+
case "session.resume": {
|
|
2992
|
+
if (!opts.sessionStore) {
|
|
2993
|
+
sendResult(ws, false, "Session store not available");
|
|
2994
|
+
break;
|
|
2995
|
+
}
|
|
2996
|
+
const { id } = msg.payload;
|
|
2997
|
+
try {
|
|
2998
|
+
if (id === opts.session.id) {
|
|
2999
|
+
sendResult(ws, false, "Session is already active");
|
|
3000
|
+
break;
|
|
3001
|
+
}
|
|
3002
|
+
const resumed = await opts.sessionStore.resume(id);
|
|
3003
|
+
const ctx = opts.agent.ctx;
|
|
3004
|
+
ctx.state.replaceMessages(resumed.data.messages);
|
|
3005
|
+
ctx.state.replaceTodos([]);
|
|
3006
|
+
ctx.readFiles.clear();
|
|
3007
|
+
ctx.fileMtimes.clear();
|
|
3008
|
+
ctx.tokenCounter.reset();
|
|
3009
|
+
ctx.tokenCounter.account(resumed.data.usage, ctx.model);
|
|
3010
|
+
broadcast({
|
|
3011
|
+
type: "session.start",
|
|
3012
|
+
payload: {
|
|
3013
|
+
sessionId: opts.session.id,
|
|
3014
|
+
model: ctx.model,
|
|
3015
|
+
provider: ctx.provider.id,
|
|
3016
|
+
reset: true,
|
|
3017
|
+
replayMessages: resumed.data.messages,
|
|
3018
|
+
replayUsage: resumed.data.usage
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
sendResult(ws, true, `Resumed session ${id}`);
|
|
3022
|
+
} catch (err) {
|
|
3023
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
3024
|
+
}
|
|
3025
|
+
break;
|
|
3026
|
+
}
|
|
3027
|
+
case "context.debug": {
|
|
3028
|
+
const ctx = opts.agent.ctx;
|
|
3029
|
+
const breakdown = estimateContextBreakdown({
|
|
3030
|
+
systemPrompt: ctx.systemPrompt,
|
|
3031
|
+
tools: opts.agent.tools.list(),
|
|
3032
|
+
messages: ctx.messages
|
|
3033
|
+
});
|
|
3034
|
+
send(ws, {
|
|
3035
|
+
type: "context.debug",
|
|
3036
|
+
payload: {
|
|
3037
|
+
...breakdown,
|
|
3038
|
+
mode: ctx.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
3039
|
+
policy: ctx.meta["contextWindowPolicy"] ?? null
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
break;
|
|
3043
|
+
}
|
|
3044
|
+
case "context.compact": {
|
|
3045
|
+
const aggressive = !!msg.payload?.aggressive;
|
|
3046
|
+
try {
|
|
3047
|
+
const compactor = opts.agent.container.resolve(TOKENS.Compactor);
|
|
3048
|
+
if (!compactor) {
|
|
3049
|
+
sendResult(ws, false, "Compactor not available");
|
|
3050
|
+
break;
|
|
3051
|
+
}
|
|
3052
|
+
const before = opts.agent.ctx.tokenCounter.total();
|
|
3053
|
+
const report = await compactor.compact(opts.agent.ctx, { aggressive });
|
|
3054
|
+
const after = opts.agent.ctx.tokenCounter.total();
|
|
3055
|
+
send(ws, {
|
|
3056
|
+
type: "context.compacted",
|
|
3057
|
+
payload: {
|
|
3058
|
+
before: before.input + before.output,
|
|
3059
|
+
after: after.input + after.output,
|
|
3060
|
+
saved: Math.max(0, before.input + before.output - after.input - after.output),
|
|
3061
|
+
reductions: report.reductions ?? [],
|
|
3062
|
+
repaired: report.repaired ?? false
|
|
3063
|
+
}
|
|
3064
|
+
});
|
|
3065
|
+
sendResult(
|
|
3066
|
+
ws,
|
|
3067
|
+
true,
|
|
3068
|
+
`Compacted: ${before.input + before.output} \u2192 ${after.input + after.output} tokens`
|
|
3069
|
+
);
|
|
3070
|
+
} catch (err) {
|
|
3071
|
+
sendResult(ws, false, err instanceof Error ? err.message : String(err));
|
|
3072
|
+
}
|
|
3073
|
+
break;
|
|
3074
|
+
}
|
|
3075
|
+
case "context.repair": {
|
|
3076
|
+
const ctx = opts.agent.ctx;
|
|
3077
|
+
const beforeMessages = ctx.messages.length;
|
|
3078
|
+
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
3079
|
+
if (repaired.report.changed) {
|
|
3080
|
+
ctx.state.replaceMessages(repaired.messages);
|
|
3081
|
+
}
|
|
3082
|
+
const payload = {
|
|
3083
|
+
removedToolUses: repaired.report.removedToolUses,
|
|
3084
|
+
removedToolResults: repaired.report.removedToolResults,
|
|
3085
|
+
removedMessages: repaired.report.removedMessages,
|
|
3086
|
+
beforeMessages,
|
|
3087
|
+
afterMessages: ctx.messages.length
|
|
3088
|
+
};
|
|
3089
|
+
broadcast({ type: "context.repaired", payload });
|
|
3090
|
+
const removed = payload.removedToolUses.length + payload.removedToolResults.length + payload.removedMessages;
|
|
3091
|
+
sendResult(
|
|
3092
|
+
ws,
|
|
3093
|
+
true,
|
|
3094
|
+
removed > 0 ? `Context repaired: removed ${removed} orphan protocol item(s)` : "Context repair found no orphan protocol blocks"
|
|
3095
|
+
);
|
|
3096
|
+
break;
|
|
3097
|
+
}
|
|
3098
|
+
case "context.modes.list": {
|
|
3099
|
+
const active = String(
|
|
3100
|
+
opts.agent.ctx.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
3101
|
+
);
|
|
3102
|
+
send(ws, {
|
|
3103
|
+
type: "context.modes.list",
|
|
3104
|
+
payload: {
|
|
3105
|
+
activeId: active,
|
|
3106
|
+
modes: listContextWindowModes().map((m) => ({
|
|
3107
|
+
id: m.id,
|
|
3108
|
+
name: m.name,
|
|
3109
|
+
description: m.description,
|
|
3110
|
+
isActive: m.id === active,
|
|
3111
|
+
thresholds: m.thresholds,
|
|
3112
|
+
preserveK: m.preserveK,
|
|
3113
|
+
eliseThreshold: m.eliseThreshold
|
|
3114
|
+
}))
|
|
3115
|
+
}
|
|
3116
|
+
});
|
|
3117
|
+
break;
|
|
3118
|
+
}
|
|
3119
|
+
case "context.mode.switch": {
|
|
3120
|
+
const { id } = msg.payload;
|
|
3121
|
+
const policy = resolveContextWindowPolicy({}, id);
|
|
3122
|
+
if (policy.id !== id) {
|
|
3123
|
+
sendResult(ws, false, `Unknown context mode "${id}"`);
|
|
3124
|
+
break;
|
|
3125
|
+
}
|
|
3126
|
+
opts.agent.ctx.meta["contextWindowMode"] = policy.id;
|
|
3127
|
+
opts.agent.ctx.meta["contextWindowPolicy"] = policy;
|
|
3128
|
+
sendResult(ws, true, `Context mode switched to ${policy.id}`);
|
|
3129
|
+
broadcast({
|
|
3130
|
+
type: "context.mode.changed",
|
|
3131
|
+
payload: { id: policy.id, name: policy.name, policy }
|
|
3132
|
+
});
|
|
3133
|
+
break;
|
|
3134
|
+
}
|
|
3135
|
+
// Collaboration messages — the CLI webui-server doesn't run a
|
|
3136
|
+
// full collab hub; silently acknowledge and ignore.
|
|
3137
|
+
case "collab.join":
|
|
3138
|
+
case "collab.leave":
|
|
3139
|
+
case "collab.annotate":
|
|
3140
|
+
case "collab.resolve":
|
|
3141
|
+
break;
|
|
3142
|
+
default: {
|
|
3143
|
+
console.debug(
|
|
3144
|
+
`[WebUI] Unhandled message type: ${String(msg.type)}`
|
|
3145
|
+
);
|
|
3146
|
+
break;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
async function handleUserMessage(ws, _client, content) {
|
|
3151
|
+
if (abortController) {
|
|
3152
|
+
send(ws, {
|
|
3153
|
+
type: "error",
|
|
3154
|
+
payload: { phase: "agent.run", message: "A run is already in progress. Abort it first." }
|
|
3155
|
+
});
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
abortController = new AbortController();
|
|
3159
|
+
try {
|
|
3160
|
+
const result = await opts.agent.run(content, {
|
|
3161
|
+
signal: abortController.signal
|
|
3162
|
+
});
|
|
3163
|
+
send(ws, {
|
|
3164
|
+
type: "run.result",
|
|
3165
|
+
payload: {
|
|
3166
|
+
status: result.status,
|
|
3167
|
+
iterations: result.iterations,
|
|
3168
|
+
finalText: result.finalText,
|
|
3169
|
+
error: result.error ? {
|
|
3170
|
+
code: result.error.code,
|
|
3171
|
+
message: result.error.message,
|
|
3172
|
+
recoverable: result.error.recoverable
|
|
3173
|
+
} : void 0
|
|
3174
|
+
}
|
|
3175
|
+
});
|
|
3176
|
+
} catch (err) {
|
|
3177
|
+
send(ws, {
|
|
3178
|
+
type: "error",
|
|
3179
|
+
payload: {
|
|
3180
|
+
phase: "agent.run",
|
|
3181
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3182
|
+
}
|
|
3183
|
+
});
|
|
3184
|
+
} finally {
|
|
3185
|
+
abortController = null;
|
|
2206
3186
|
}
|
|
2207
3187
|
}
|
|
2208
3188
|
function send(ws, msg) {
|
|
@@ -2423,9 +3403,32 @@ async function runWebUI(opts) {
|
|
|
2423
3403
|
send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
2424
3404
|
}
|
|
2425
3405
|
}
|
|
3406
|
+
var SKIP_DIRS, KEEP_DOTFILES;
|
|
2426
3407
|
var init_webui_server = __esm({
|
|
2427
3408
|
"src/webui-server.ts"() {
|
|
2428
3409
|
init_provider_config_utils();
|
|
3410
|
+
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3411
|
+
".git",
|
|
3412
|
+
"node_modules",
|
|
3413
|
+
"dist",
|
|
3414
|
+
"build",
|
|
3415
|
+
".next",
|
|
3416
|
+
".turbo",
|
|
3417
|
+
".cache",
|
|
3418
|
+
"target",
|
|
3419
|
+
"coverage",
|
|
3420
|
+
".nyc_output",
|
|
3421
|
+
"out",
|
|
3422
|
+
".pnpm-store",
|
|
3423
|
+
".parcel-cache"
|
|
3424
|
+
]);
|
|
3425
|
+
KEEP_DOTFILES = /* @__PURE__ */ new Set([
|
|
3426
|
+
".wrongstack",
|
|
3427
|
+
".env.example",
|
|
3428
|
+
".gitignore",
|
|
3429
|
+
".eslintrc",
|
|
3430
|
+
".prettierrc"
|
|
3431
|
+
]);
|
|
2429
3432
|
}
|
|
2430
3433
|
});
|
|
2431
3434
|
var req = createRequire(import.meta.url);
|
|
@@ -2693,193 +3696,9 @@ function parseSpawnFlags(input) {
|
|
|
2693
3696
|
}
|
|
2694
3697
|
return { description: rest.trim(), opts };
|
|
2695
3698
|
}
|
|
2696
|
-
async function pathExists(file) {
|
|
2697
|
-
try {
|
|
2698
|
-
await fsp4.access(file);
|
|
2699
|
-
return true;
|
|
2700
|
-
} catch {
|
|
2701
|
-
return false;
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
async function detectPackageManager(root, declared) {
|
|
2705
|
-
if (declared) {
|
|
2706
|
-
const name = declared.split("@")[0];
|
|
2707
|
-
if (name) return name;
|
|
2708
|
-
}
|
|
2709
|
-
if (await pathExists(path10.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2710
|
-
if (await pathExists(path10.join(root, "bun.lockb"))) return "bun";
|
|
2711
|
-
if (await pathExists(path10.join(root, "bun.lock"))) return "bun";
|
|
2712
|
-
if (await pathExists(path10.join(root, "yarn.lock"))) return "yarn";
|
|
2713
|
-
return "npm";
|
|
2714
|
-
}
|
|
2715
|
-
function hasUsableScript(scripts, name) {
|
|
2716
|
-
const script = scripts[name];
|
|
2717
|
-
if (typeof script !== "string" || script.trim() === "") return false;
|
|
2718
|
-
if (name === "test" && /no test specified/i.test(script)) return false;
|
|
2719
|
-
return true;
|
|
2720
|
-
}
|
|
2721
|
-
function parseMakeTargets(makefile) {
|
|
2722
|
-
const targets = /* @__PURE__ */ new Set();
|
|
2723
|
-
for (const line of makefile.split(/\r?\n/)) {
|
|
2724
|
-
if (line.startsWith(" ") || line.trimStart().startsWith("#")) continue;
|
|
2725
|
-
const match = /^([A-Za-z0-9_.-]+)\s*:(?![=])/.exec(line);
|
|
2726
|
-
if (match?.[1]) targets.add(match[1]);
|
|
2727
|
-
}
|
|
2728
|
-
return targets;
|
|
2729
|
-
}
|
|
2730
|
-
async function detectProjectFacts(root) {
|
|
2731
|
-
const facts = { hints: [] };
|
|
2732
|
-
try {
|
|
2733
|
-
const pkg = JSON.parse(await fsp4.readFile(path10.join(root, "package.json"), "utf8"));
|
|
2734
|
-
const scripts = pkg.scripts ?? {};
|
|
2735
|
-
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2736
|
-
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
2737
|
-
if (hasUsableScript(scripts, "test")) facts.test = `${pm} test`;
|
|
2738
|
-
if (hasUsableScript(scripts, "lint")) facts.lint = `${pm} run lint`;
|
|
2739
|
-
const runScript = ["dev", "start", "serve", "preview"].find(
|
|
2740
|
-
(name) => hasUsableScript(scripts, name)
|
|
2741
|
-
);
|
|
2742
|
-
if (runScript) facts.run = `${pm} run ${runScript}`;
|
|
2743
|
-
facts.hints.push(Object.keys(scripts).length > 0 ? "package.json scripts" : "package.json");
|
|
2744
|
-
} catch {
|
|
2745
|
-
}
|
|
2746
|
-
try {
|
|
2747
|
-
if (!await pathExists(path10.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2748
|
-
facts.test ??= "pytest";
|
|
2749
|
-
facts.lint ??= "ruff check .";
|
|
2750
|
-
facts.hints.push("pyproject.toml");
|
|
2751
|
-
} catch {
|
|
2752
|
-
}
|
|
2753
|
-
try {
|
|
2754
|
-
if (!await pathExists(path10.join(root, "go.mod"))) throw new Error("not go");
|
|
2755
|
-
facts.build ??= "go build ./...";
|
|
2756
|
-
facts.test ??= "go test ./...";
|
|
2757
|
-
facts.run ??= "go run .";
|
|
2758
|
-
facts.hints.push("go.mod");
|
|
2759
|
-
} catch {
|
|
2760
|
-
}
|
|
2761
|
-
try {
|
|
2762
|
-
if (!await pathExists(path10.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2763
|
-
facts.build ??= "cargo build";
|
|
2764
|
-
facts.test ??= "cargo test";
|
|
2765
|
-
facts.lint ??= "cargo clippy";
|
|
2766
|
-
facts.run ??= "cargo run";
|
|
2767
|
-
facts.hints.push("Cargo.toml");
|
|
2768
|
-
} catch {
|
|
2769
|
-
}
|
|
2770
|
-
try {
|
|
2771
|
-
const makefile = await fsp4.readFile(path10.join(root, "Makefile"), "utf8");
|
|
2772
|
-
const targets = parseMakeTargets(makefile);
|
|
2773
|
-
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2774
|
-
if (targets.has("test")) facts.test ??= "make test";
|
|
2775
|
-
if (targets.has("lint")) facts.lint ??= "make lint";
|
|
2776
|
-
const runTarget = ["run", "dev", "start", "serve"].find((name) => targets.has(name));
|
|
2777
|
-
if (runTarget) facts.run ??= `make ${runTarget}`;
|
|
2778
|
-
facts.hints.push("Makefile");
|
|
2779
|
-
} catch {
|
|
2780
|
-
}
|
|
2781
|
-
return facts;
|
|
2782
|
-
}
|
|
2783
|
-
function renderAgentsTemplate(f) {
|
|
2784
|
-
const cmd = (s) => s ? `\`${s}\`` : "_TODO_";
|
|
2785
|
-
const hints = f.hints.length > 0 ? `
|
|
2786
|
-
|
|
2787
|
-
> Auto-detected: ${f.hints.join(", ")}` : "";
|
|
2788
|
-
return `# AGENTS.md
|
|
2789
|
-
|
|
2790
|
-
> **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
|
|
2791
|
-
> persistent project context. Previous content here may contain decisions,
|
|
2792
|
-
> architecture notes, domain knowledge, or verification history that should be
|
|
2793
|
-
> preserved. Merge additions rather than replacing.
|
|
2794
|
-
|
|
2795
|
-
## Project brief
|
|
2796
|
-
|
|
2797
|
-
- **Purpose:** _What does this project do and why does it exist?_
|
|
2798
|
-
- **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
|
|
2799
|
-
- **Runtime / deployment:** _CLI, server, browser, worker, library, package?_${hints}
|
|
2800
|
-
|
|
2801
|
-
## How to work safely
|
|
2802
|
-
|
|
2803
|
-
- _Project-specific rules the agent should always follow._
|
|
2804
|
-
- _Files, generated artifacts, migrations, or config the agent should not edit without asking._
|
|
2805
|
-
- _Preferred style or architecture choices not obvious from the code._
|
|
2806
|
-
- _Known fragile areas or historical bugs that deserve extra caution._
|
|
2807
|
-
|
|
2808
|
-
## Commands
|
|
2809
|
-
|
|
2810
|
-
| Command | Script |
|
|
2811
|
-
|---------|--------|
|
|
2812
|
-
| Build | ${cmd(f.build)} |
|
|
2813
|
-
| Test | ${cmd(f.test)} |
|
|
2814
|
-
| Lint | ${cmd(f.lint)} |
|
|
2815
|
-
| Run locally | ${cmd(f.run)} |
|
|
2816
|
-
|
|
2817
|
-
## Key files and entry points
|
|
2818
|
-
|
|
2819
|
-
| File / directory | Role |
|
|
2820
|
-
|---|---|
|
|
2821
|
-
| _src/_ | _Main source entry point(s)_ |
|
|
2822
|
-
| _tests/_ | _Test root or convention_ |
|
|
2823
|
-
| _docs/_ | _Architecture, runbooks, design notes_ |
|
|
2824
|
-
| _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
|
|
2825
|
-
|
|
2826
|
-
## Architecture notes
|
|
2827
|
-
|
|
2828
|
-
_Summarize the important modules, data flow, boundaries, and ownership rules.
|
|
2829
|
-
Mention anything a newcomer might misread or that looks unusual but is intentional._
|
|
2830
|
-
|
|
2831
|
-
### Dependency layers
|
|
2832
|
-
|
|
2833
|
-
_Describe the key dependency direction or layered structure, e.g.: "core has no
|
|
2834
|
-
runtime deps; cli assembles everything above it."_
|
|
2835
|
-
|
|
2836
|
-
### Extension points
|
|
2837
|
-
|
|
2838
|
-
_Plugin, MCP, extension hooks, custom tools \u2014 what's wired up and how._
|
|
2839
|
-
|
|
2840
|
-
## Domain knowledge
|
|
2841
|
-
|
|
2842
|
-
_Business rules, acronyms, invariants, external services, and notes where the
|
|
2843
|
-
code looks unusual but is intentional. E.g.: "IDs are ULIDs, not UUIDs", "the
|
|
2844
|
-
\`draft\` flag means uncommitted billing metadata", "MCP servers are restarted
|
|
2845
|
-
on disconnect with exponential backoff, up to 3 attempts"._
|
|
2846
|
-
|
|
2847
|
-
## Verification checklist
|
|
2848
3699
|
|
|
2849
|
-
-
|
|
2850
|
-
|
|
2851
|
-
- _What failure modes deserve extra attention?_
|
|
2852
|
-
- _Any known flaky tests or environment-dependent behavior?_
|
|
2853
|
-
|
|
2854
|
-
## Useful pointers
|
|
2855
|
-
|
|
2856
|
-
- _Docs, dashboards, runbooks, issue trackers, design notes, owner contacts._
|
|
2857
|
-
- _Related projects or repositories._`;
|
|
2858
|
-
}
|
|
2859
|
-
function countTurnPairs(messages) {
|
|
2860
|
-
let count = 0;
|
|
2861
|
-
for (const m of messages) {
|
|
2862
|
-
if (m.role === "user" || m.role === "assistant") count++;
|
|
2863
|
-
}
|
|
2864
|
-
return Math.floor(count / 2);
|
|
2865
|
-
}
|
|
2866
|
-
function countToolUses(messages) {
|
|
2867
|
-
let count = 0;
|
|
2868
|
-
for (const m of messages) {
|
|
2869
|
-
if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_use").length;
|
|
2870
|
-
}
|
|
2871
|
-
return count;
|
|
2872
|
-
}
|
|
2873
|
-
function countToolResults(messages) {
|
|
2874
|
-
let count = 0;
|
|
2875
|
-
for (const m of messages) {
|
|
2876
|
-
if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_result").length;
|
|
2877
|
-
}
|
|
2878
|
-
return count;
|
|
2879
|
-
}
|
|
2880
|
-
function estimateTokens(messages) {
|
|
2881
|
-
return estimateMessageTokens(messages);
|
|
2882
|
-
}
|
|
3700
|
+
// src/slash-commands/index.ts
|
|
3701
|
+
init_helpers();
|
|
2883
3702
|
|
|
2884
3703
|
// src/slash-commands/auth.ts
|
|
2885
3704
|
init_provider_config_utils();
|
|
@@ -3024,6 +3843,9 @@ function buildAuthCommand(opts) {
|
|
|
3024
3843
|
}
|
|
3025
3844
|
};
|
|
3026
3845
|
}
|
|
3846
|
+
|
|
3847
|
+
// src/slash-commands/autophase.ts
|
|
3848
|
+
init_helpers();
|
|
3027
3849
|
function getStore(opts) {
|
|
3028
3850
|
if (!opts.paths) throw new Error("PhaseStore not available \u2014 paths not configured.");
|
|
3029
3851
|
return new PhaseStore({ baseDir: opts.paths.projectAutophase });
|
|
@@ -3092,12 +3914,12 @@ function buildAutoPhaseCommand(opts) {
|
|
|
3092
3914
|
""
|
|
3093
3915
|
].join("\n"),
|
|
3094
3916
|
async run(args) {
|
|
3095
|
-
const
|
|
3096
|
-
const sub =
|
|
3917
|
+
const { cmd, rest } = parseSubcommand(args);
|
|
3918
|
+
const sub = cmd || "status";
|
|
3097
3919
|
const store = getStore(opts);
|
|
3098
3920
|
switch (sub) {
|
|
3099
3921
|
case "start": {
|
|
3100
|
-
const goal =
|
|
3922
|
+
const goal = rest.join(" ").trim();
|
|
3101
3923
|
if (!goal) {
|
|
3102
3924
|
return { message: "Usage: /autophase start <goal> \u2014 describe what to build." };
|
|
3103
3925
|
}
|
|
@@ -3142,7 +3964,7 @@ function buildAutoPhaseCommand(opts) {
|
|
|
3142
3964
|
return { message: `\u{1F4BE} AutoPhase saved: ${view.graph.title}` };
|
|
3143
3965
|
}
|
|
3144
3966
|
case "load": {
|
|
3145
|
-
const title =
|
|
3967
|
+
const title = rest.join(" ").trim();
|
|
3146
3968
|
const graphs = await store.list();
|
|
3147
3969
|
if (graphs.length === 0) return { message: "\u274C No saved projects." };
|
|
3148
3970
|
const entry = title ? graphs.find((g) => g.title.toLowerCase().includes(title.toLowerCase())) : graphs[0];
|
|
@@ -3179,10 +4001,24 @@ function buildAutoPhaseCommand(opts) {
|
|
|
3179
4001
|
};
|
|
3180
4002
|
}
|
|
3181
4003
|
}
|
|
3182
|
-
return { message:
|
|
4004
|
+
return { message: unknownSubcommand(sub, ["start", "pause", "resume", "stop", "save", "load", "list", "status"], "autophase") };
|
|
3183
4005
|
}
|
|
3184
4006
|
};
|
|
3185
4007
|
}
|
|
4008
|
+
var MODE_LABELS = {
|
|
4009
|
+
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
4010
|
+
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
4011
|
+
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
|
|
4012
|
+
eternal: `${color.red("ETERNAL")} ${color.dim("(goal-driven loop \u2014 YOLO, until /autonomy stop)")}`,
|
|
4013
|
+
"eternal-parallel": `${color.magenta("PARALLEL")} ${color.dim("(4-8 subagents per tick \u2014 fan-out, until /autonomy stop)")}`
|
|
4014
|
+
};
|
|
4015
|
+
var MODE_LABELS_SHORT = {
|
|
4016
|
+
off: `${color.green("OFF")} \u2014 agent stops after each turn`,
|
|
4017
|
+
suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
|
|
4018
|
+
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
|
|
4019
|
+
eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`,
|
|
4020
|
+
"eternal-parallel": `${color.magenta("PARALLEL")} \u2014 fan-out 4-8 subagents per tick`
|
|
4021
|
+
};
|
|
3186
4022
|
function buildAutonomyCommand(opts) {
|
|
3187
4023
|
return {
|
|
3188
4024
|
name: "autonomy",
|
|
@@ -3231,14 +4067,7 @@ function buildAutonomyCommand(opts) {
|
|
|
3231
4067
|
}
|
|
3232
4068
|
if (!arg || arg === "status") {
|
|
3233
4069
|
const current = opts.onAutonomy();
|
|
3234
|
-
const
|
|
3235
|
-
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
3236
|
-
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
3237
|
-
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
|
|
3238
|
-
eternal: `${color.red("ETERNAL")} ${color.dim("(goal-driven loop \u2014 YOLO, until /autonomy stop)")}`,
|
|
3239
|
-
"eternal-parallel": `${color.magenta("PARALLEL")} ${color.dim("(4-8 subagents per tick \u2014 fan-out, until /autonomy stop)")}`
|
|
3240
|
-
};
|
|
3241
|
-
const lines = [`Autonomy mode: ${labels2[current] ?? current}`];
|
|
4070
|
+
const lines = [`Autonomy mode: ${MODE_LABELS[current] ?? current}`];
|
|
3242
4071
|
try {
|
|
3243
4072
|
const goal = await loadGoal(goalFilePath(opts.projectRoot));
|
|
3244
4073
|
if (goal) {
|
|
@@ -3379,14 +4208,7 @@ ${color.dim("Regular YOLO enabled; destructive-gated calls still use the permiss
|
|
|
3379
4208
|
opts.onEternalStop();
|
|
3380
4209
|
}
|
|
3381
4210
|
opts.onAutonomy(newMode);
|
|
3382
|
-
const
|
|
3383
|
-
off: `${color.green("OFF")} \u2014 agent stops after each turn`,
|
|
3384
|
-
suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
|
|
3385
|
-
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
|
|
3386
|
-
eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`,
|
|
3387
|
-
"eternal-parallel": `${color.magenta("PARALLEL")} \u2014 fan-out 4-8 subagents per tick`
|
|
3388
|
-
};
|
|
3389
|
-
const msg = `Autonomy mode: ${labels[newMode]}`;
|
|
4211
|
+
const msg = `Autonomy mode: ${MODE_LABELS_SHORT[newMode]}`;
|
|
3390
4212
|
opts.renderer.write(msg);
|
|
3391
4213
|
return { message: msg };
|
|
3392
4214
|
}
|
|
@@ -3496,21 +4318,24 @@ ${color.yellow(` ${r.errors.length} file(s) had errors`)}` : "");
|
|
|
3496
4318
|
}
|
|
3497
4319
|
};
|
|
3498
4320
|
}
|
|
4321
|
+
|
|
4322
|
+
// src/slash-commands/collab.ts
|
|
4323
|
+
init_helpers();
|
|
3499
4324
|
function buildCollabCommand(opts) {
|
|
3500
4325
|
return {
|
|
3501
4326
|
name: "collab",
|
|
3502
4327
|
category: "Agent",
|
|
3503
4328
|
description: "Live collaboration helpers (status / invite / history).",
|
|
3504
4329
|
async run(args, ctx) {
|
|
3505
|
-
const
|
|
3506
|
-
const sub =
|
|
4330
|
+
const { cmd, rest } = parseSubcommand(args);
|
|
4331
|
+
const sub = cmd || "status";
|
|
3507
4332
|
switch (sub) {
|
|
3508
4333
|
case "status":
|
|
3509
4334
|
return statusCommand(ctx.session?.id);
|
|
3510
4335
|
case "invite":
|
|
3511
4336
|
return inviteCommand(ctx.session?.id);
|
|
3512
4337
|
case "history":
|
|
3513
|
-
return historyCommand(opts, ctx.session?.id,
|
|
4338
|
+
return historyCommand(opts, ctx.session?.id, rest);
|
|
3514
4339
|
case "annotations":
|
|
3515
4340
|
case "notes":
|
|
3516
4341
|
return annotationsCommand(opts, ctx.session?.id);
|
|
@@ -3521,7 +4346,7 @@ function buildCollabCommand(opts) {
|
|
|
3521
4346
|
default:
|
|
3522
4347
|
return {
|
|
3523
4348
|
message: color.yellow(
|
|
3524
|
-
|
|
4349
|
+
unknownSubcommand(sub, ["status", "invite", "history", "annotations"], "collab")
|
|
3525
4350
|
)
|
|
3526
4351
|
};
|
|
3527
4352
|
}
|
|
@@ -3716,6 +4541,9 @@ function buildCompactCommand(opts) {
|
|
|
3716
4541
|
}
|
|
3717
4542
|
};
|
|
3718
4543
|
}
|
|
4544
|
+
|
|
4545
|
+
// src/slash-commands/context.ts
|
|
4546
|
+
init_helpers();
|
|
3719
4547
|
function buildContextCommand(opts) {
|
|
3720
4548
|
return {
|
|
3721
4549
|
name: "context",
|
|
@@ -3958,8 +4786,173 @@ function parseThreshold(raw) {
|
|
|
3958
4786
|
function formatLimit(limit) {
|
|
3959
4787
|
return limit > 0 ? `${limit.toLocaleString()} tokens` : "unknown";
|
|
3960
4788
|
}
|
|
3961
|
-
function pct(n) {
|
|
3962
|
-
return `${Math.round(n * 100)}%`;
|
|
4789
|
+
function pct(n) {
|
|
4790
|
+
return `${Math.round(n * 100)}%`;
|
|
4791
|
+
}
|
|
4792
|
+
function buildDelegateCommand(opts) {
|
|
4793
|
+
return {
|
|
4794
|
+
name: "delegate",
|
|
4795
|
+
category: "Agent",
|
|
4796
|
+
description: "Hand a task to a specialist subagent. /delegate [--role=<role>] <task>. Auto-dispatches if no role given.",
|
|
4797
|
+
argsHint: "[--role=<role>] [--name=<label>] <task description>",
|
|
4798
|
+
help: [
|
|
4799
|
+
"User-facing counterpart to the AI's `delegate` tool.",
|
|
4800
|
+
"",
|
|
4801
|
+
"Usage:",
|
|
4802
|
+
" /delegate <task description> Auto-dispatch to best agent",
|
|
4803
|
+
" /delegate --role=<role> <task description> Spawn a specific role",
|
|
4804
|
+
" /delegate --role=<role> --name=<label> <task> Spawn with custom name",
|
|
4805
|
+
" /delegate list List available roles",
|
|
4806
|
+
"",
|
|
4807
|
+
"Examples:",
|
|
4808
|
+
' /delegate "audit packages/core for null-deref bugs"',
|
|
4809
|
+
' /delegate --role=bug-hunter "find the race condition in session.ts"',
|
|
4810
|
+
' /delegate --role=security-scanner --name=sec-audit "scan configs for secrets"',
|
|
4811
|
+
"",
|
|
4812
|
+
"Smart dispatch uses the same engine as /fleet dispatch: heuristic keyword",
|
|
4813
|
+
"matching with LLM fallback when ambiguous. The chosen agent is shown before",
|
|
4814
|
+
"spawning so you can confirm or cancel.",
|
|
4815
|
+
"",
|
|
4816
|
+
"Requires director mode. Run /director first, or start with wstack --director.",
|
|
4817
|
+
"",
|
|
4818
|
+
"Related: /spawn (fire-and-forget), /fleet dispatch (smart routing with fleet status)."
|
|
4819
|
+
].join("\n"),
|
|
4820
|
+
async run(args) {
|
|
4821
|
+
const trimmed = args.trim();
|
|
4822
|
+
if (trimmed === "list" || trimmed === "roles" || trimmed === "ls") {
|
|
4823
|
+
return listRoles();
|
|
4824
|
+
}
|
|
4825
|
+
if (!trimmed) {
|
|
4826
|
+
return {
|
|
4827
|
+
message: [
|
|
4828
|
+
`${color.bold("/delegate")} \u2014 Hand a task to a specialist subagent`,
|
|
4829
|
+
"",
|
|
4830
|
+
"Usage:",
|
|
4831
|
+
` ${color.cyan("/delegate <task>")} Auto-dispatch to best agent`,
|
|
4832
|
+
` ${color.cyan("/delegate --role=<role> <task>")} Spawn a specific role`,
|
|
4833
|
+
` ${color.cyan("/delegate list")} List available roles`,
|
|
4834
|
+
"",
|
|
4835
|
+
"Requires director mode. Run /director first."
|
|
4836
|
+
].join("\n")
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4839
|
+
let role;
|
|
4840
|
+
let name;
|
|
4841
|
+
const taskParts = [];
|
|
4842
|
+
const tokens = trimmed.split(/\s+/);
|
|
4843
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
4844
|
+
const t = tokens[i] ?? "";
|
|
4845
|
+
if (t.startsWith("--role=")) {
|
|
4846
|
+
role = t.slice("--role=".length);
|
|
4847
|
+
} else if (t === "--role" && tokens[i + 1]) {
|
|
4848
|
+
role = tokens[++i];
|
|
4849
|
+
} else if (t.startsWith("--name=")) {
|
|
4850
|
+
name = t.slice("--name=".length);
|
|
4851
|
+
} else if (t === "--name" && tokens[i + 1]) {
|
|
4852
|
+
name = tokens[++i];
|
|
4853
|
+
} else if (!t.startsWith("-")) {
|
|
4854
|
+
taskParts.push(t);
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
const task = taskParts.join(" ").trim();
|
|
4858
|
+
if (!task) {
|
|
4859
|
+
return {
|
|
4860
|
+
message: `${color.amber("Usage:")} /delegate [--role=<role>] [--name=<label>] <task description>`
|
|
4861
|
+
};
|
|
4862
|
+
}
|
|
4863
|
+
if (role) {
|
|
4864
|
+
const normalized = role.toLowerCase();
|
|
4865
|
+
const def = AGENT_CATALOG[normalized];
|
|
4866
|
+
if (!def) {
|
|
4867
|
+
const available = Object.keys(AGENT_CATALOG).sort().join(", ");
|
|
4868
|
+
return {
|
|
4869
|
+
message: `${color.red("Unknown role")} "${role}". Available roles:
|
|
4870
|
+
${color.dim(available)}
|
|
4871
|
+
|
|
4872
|
+
Use ${color.cyan("/delegate list")} to browse by phase.`
|
|
4873
|
+
};
|
|
4874
|
+
}
|
|
4875
|
+
return await spawnAgent(
|
|
4876
|
+
opts,
|
|
4877
|
+
normalized,
|
|
4878
|
+
task,
|
|
4879
|
+
name ?? normalized,
|
|
4880
|
+
`${color.green("\u2713")} Delegating to ${color.bold(normalized)}: ${color.dim(task)}`
|
|
4881
|
+
);
|
|
4882
|
+
}
|
|
4883
|
+
const decision = await dispatchAgent(task, {
|
|
4884
|
+
classifier: opts.onDispatchClassify
|
|
4885
|
+
});
|
|
4886
|
+
const pct2 = Math.round(decision.confidence * 100);
|
|
4887
|
+
const decisionMsg = [
|
|
4888
|
+
`${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`,
|
|
4889
|
+
` ${color.dim(decision.definition.capability.summary)}`,
|
|
4890
|
+
` ${color.dim("why:")} ${decision.reason}`,
|
|
4891
|
+
decision.alternatives.length > 0 ? ` ${color.dim("alternatives:")} ${decision.alternatives.slice(0, 3).map((a) => a.role).join(", ")}` : ""
|
|
4892
|
+
].filter(Boolean).join("\n");
|
|
4893
|
+
opts.renderer.write(decisionMsg);
|
|
4894
|
+
return await spawnAgent(
|
|
4895
|
+
opts,
|
|
4896
|
+
decision.role,
|
|
4897
|
+
task,
|
|
4898
|
+
name ?? decision.role,
|
|
4899
|
+
decisionMsg
|
|
4900
|
+
);
|
|
4901
|
+
}
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4904
|
+
async function spawnAgent(opts, role, _task, _name, header) {
|
|
4905
|
+
if (!opts.onFleetSpawn) {
|
|
4906
|
+
const msg = `${color.amber("\u26A0 No fleet active.")} Run ${color.bold("/director")} first, or start with ${color.bold("wstack --director")}.`;
|
|
4907
|
+
opts.renderer.writeWarning(msg);
|
|
4908
|
+
return { message: msg };
|
|
4909
|
+
}
|
|
4910
|
+
try {
|
|
4911
|
+
const id = await opts.onFleetSpawn(role);
|
|
4912
|
+
const msg = [
|
|
4913
|
+
header,
|
|
4914
|
+
` ${color.green("\u2713 spawned")} as ${color.dim(id)}`
|
|
4915
|
+
].join("\n");
|
|
4916
|
+
opts.renderer.write(msg);
|
|
4917
|
+
return { message: msg };
|
|
4918
|
+
} catch (err) {
|
|
4919
|
+
const msg = `${color.red("\u2717 Spawn failed")}: ${err instanceof Error ? err.message : String(err)}`;
|
|
4920
|
+
opts.renderer.writeWarning(msg);
|
|
4921
|
+
return { message: msg };
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
function listRoles() {
|
|
4925
|
+
const PHASE_ORDER2 = [
|
|
4926
|
+
{ phase: "discovery", label: "1 \xB7 Discovery" },
|
|
4927
|
+
{ phase: "planning", label: "2 \xB7 Planning" },
|
|
4928
|
+
{ phase: "build", label: "3 \xB7 Build" },
|
|
4929
|
+
{ phase: "verify", label: "4 \xB7 Verify" },
|
|
4930
|
+
{ phase: "review", label: "5 \xB7 Review" },
|
|
4931
|
+
{ phase: "domain", label: "6 \xB7 Domain" },
|
|
4932
|
+
{ phase: "knowledge", label: "7 \xB7 Knowledge" },
|
|
4933
|
+
{ phase: "delivery", label: "8 \xB7 Delivery & Ops" },
|
|
4934
|
+
{ phase: "meta", label: "9 \xB7 Meta" }
|
|
4935
|
+
];
|
|
4936
|
+
const totalRoles = Object.keys(AGENT_CATALOG).length;
|
|
4937
|
+
const lines = [
|
|
4938
|
+
`${color.bold("Available Agent Roles")} ${color.dim(`(${totalRoles} total)`)}`,
|
|
4939
|
+
"",
|
|
4940
|
+
`${color.dim("Use /delegate --role=<role> <task> to pick one,")}`,
|
|
4941
|
+
`${color.dim("or /delegate <task> for smart dispatch.")}`,
|
|
4942
|
+
""
|
|
4943
|
+
];
|
|
4944
|
+
for (const { phase, label } of PHASE_ORDER2) {
|
|
4945
|
+
const agents = AGENTS_BY_PHASE[phase];
|
|
4946
|
+
if (!agents || agents.length === 0) continue;
|
|
4947
|
+
lines.push(color.cyan(` Phase ${label}`));
|
|
4948
|
+
for (const def of agents.sort(
|
|
4949
|
+
(a, b) => (a.config.role ?? "").localeCompare(b.config.role ?? "")
|
|
4950
|
+
)) {
|
|
4951
|
+
const role = (def.config.role ?? "unknown").padEnd(20);
|
|
4952
|
+
lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
return { message: lines.join("\n") };
|
|
3963
4956
|
}
|
|
3964
4957
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
3965
4958
|
var MAX_OUTPUT_LINES = 500;
|
|
@@ -4222,11 +5215,6 @@ async function persistTelegramConfig(deps, mutator) {
|
|
|
4222
5215
|
}
|
|
4223
5216
|
|
|
4224
5217
|
// src/slash-commands/enhance.ts
|
|
4225
|
-
var noOpVault = {
|
|
4226
|
-
encrypt: (v) => v,
|
|
4227
|
-
decrypt: (v) => v,
|
|
4228
|
-
isEncrypted: () => false
|
|
4229
|
-
};
|
|
4230
5218
|
function buildEnhanceCommand(opts) {
|
|
4231
5219
|
const controller = opts.enhanceController;
|
|
4232
5220
|
return {
|
|
@@ -5055,7 +6043,7 @@ function skillLabel(skillHints) {
|
|
|
5055
6043
|
return skillHints.map((s) => `\`${s}\``).join(", ");
|
|
5056
6044
|
}
|
|
5057
6045
|
function categoryLabel(cli) {
|
|
5058
|
-
if (cli.language !== "unknown"
|
|
6046
|
+
if (cli.language !== "unknown") {
|
|
5059
6047
|
return `${cli.subcategory} (${cli.category}, ${cli.language})`;
|
|
5060
6048
|
}
|
|
5061
6049
|
return `${cli.subcategory} (${cli.category})`;
|
|
@@ -5257,273 +6245,323 @@ function buildFleetCommand(opts) {
|
|
|
5257
6245
|
const parts = args.trim().split(/\s+/);
|
|
5258
6246
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
5259
6247
|
const subargs = parts.slice(1);
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
);
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
const
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
const lines = [];
|
|
5316
|
-
lines.push(`${color.bold("Fleet Usage")}`);
|
|
5317
|
-
lines.push(
|
|
5318
|
-
` ${color.dim("Total:")} ${color.green(`${totalCost.toFixed(4)}`)} \xB7 ${color.cyan(totalIn.toLocaleString())} in \xB7 ${color.cyan(totalOut.toLocaleString())} out`
|
|
5319
|
-
);
|
|
5320
|
-
const subagents = Object.values(usage.perSubagent);
|
|
5321
|
-
if (subagents.length > 0) {
|
|
5322
|
-
lines.push("");
|
|
5323
|
-
for (const sa of subagents) {
|
|
5324
|
-
const name = (sa.subagentId ?? "?").padEnd(20);
|
|
5325
|
-
const cost = `${(sa.cost ?? 0).toFixed(4)}`.padStart(10);
|
|
5326
|
-
const tokens = `${sa.input ?? 0} in / ${sa.output ?? 0} out`.padEnd(30);
|
|
5327
|
-
lines.push(` ${color.dim(name)} ${color.cyan(cost)} ${color.dim(tokens)}`);
|
|
5328
|
-
}
|
|
5329
|
-
}
|
|
5330
|
-
const msg3 = lines.join("\n");
|
|
5331
|
-
opts.renderer.write(msg3);
|
|
5332
|
-
return { message: msg3 };
|
|
5333
|
-
}
|
|
5334
|
-
if (opts.onFleet) {
|
|
5335
|
-
const msg3 = await opts.onFleet("usage", void 0);
|
|
5336
|
-
return { message: msg3 };
|
|
5337
|
-
}
|
|
5338
|
-
const msg2 = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
|
|
5339
|
-
opts.renderer.write(msg2);
|
|
5340
|
-
return { message: msg2 };
|
|
5341
|
-
}
|
|
5342
|
-
if (cmd === "retry") {
|
|
5343
|
-
if (opts.onFleetRetry) {
|
|
5344
|
-
const targetId = subargs[0];
|
|
5345
|
-
const msg3 = await opts.onFleetRetry(targetId);
|
|
5346
|
-
return { message: msg3 };
|
|
5347
|
-
}
|
|
5348
|
-
if (opts.onFleet) {
|
|
5349
|
-
const msg3 = await opts.onFleet("retry", subargs[0]);
|
|
5350
|
-
return { message: msg3 };
|
|
5351
|
-
}
|
|
5352
|
-
const msg2 = `Retry is only available when director mode is active.`;
|
|
5353
|
-
opts.renderer.writeWarning(msg2);
|
|
5354
|
-
return { message: msg2 };
|
|
5355
|
-
}
|
|
5356
|
-
if (cmd === "journal" || cmd === "log") {
|
|
5357
|
-
if (opts.onFleetLog) {
|
|
5358
|
-
const subagentId = subargs[0];
|
|
5359
|
-
const mode = subargs[1] === "raw" ? "raw" : "summary";
|
|
5360
|
-
const msg3 = await opts.onFleetLog(subagentId, mode);
|
|
5361
|
-
return { message: msg3 };
|
|
5362
|
-
}
|
|
5363
|
-
if (opts.onFleet) {
|
|
5364
|
-
const msg3 = await opts.onFleet("log", subargs[0]);
|
|
5365
|
-
return { message: msg3 };
|
|
5366
|
-
}
|
|
5367
|
-
const msg2 = `${color.dim("No journal entries yet.")}`;
|
|
5368
|
-
opts.renderer.write(msg2);
|
|
5369
|
-
return { message: msg2 };
|
|
5370
|
-
}
|
|
5371
|
-
if (cmd === "kill" || cmd === "stop-all") {
|
|
5372
|
-
const targetId = subargs[0];
|
|
5373
|
-
if (targetId) {
|
|
5374
|
-
if (opts.onFleet) {
|
|
5375
|
-
const msg4 = await opts.onFleet("kill", targetId);
|
|
5376
|
-
return { message: msg4 };
|
|
5377
|
-
}
|
|
5378
|
-
const msg3 = `${color.amber("\u26A0 /fleet kill is not wired in this session.")}`;
|
|
5379
|
-
opts.renderer.writeWarning(msg3);
|
|
5380
|
-
return { message: msg3 };
|
|
5381
|
-
}
|
|
5382
|
-
if (opts.onFleetKill) {
|
|
5383
|
-
const killed = opts.onFleetKill();
|
|
5384
|
-
const msg3 = `${color.red("\u2717 Killed")} ${killed} subagent(s).`;
|
|
5385
|
-
opts.renderer.write(msg3);
|
|
5386
|
-
return { message: msg3 };
|
|
5387
|
-
}
|
|
5388
|
-
const msg2 = `${color.amber("\u26A0 /fleet kill is not wired in this session.")}`;
|
|
5389
|
-
opts.renderer.writeWarning(msg2);
|
|
5390
|
-
return { message: msg2 };
|
|
5391
|
-
}
|
|
5392
|
-
if (cmd === "terminate" || cmd === "stop") {
|
|
5393
|
-
const targetId = subargs[0];
|
|
5394
|
-
if (!targetId) {
|
|
5395
|
-
const msg3 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
|
|
5396
|
-
opts.renderer.writeWarning(msg3);
|
|
5397
|
-
return { message: msg3 };
|
|
5398
|
-
}
|
|
5399
|
-
if (!opts.onFleetTerminate) {
|
|
5400
|
-
const msg3 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
|
|
5401
|
-
opts.renderer.writeWarning(msg3);
|
|
5402
|
-
return { message: msg3 };
|
|
5403
|
-
}
|
|
5404
|
-
const ok = opts.onFleetTerminate(targetId);
|
|
5405
|
-
if (ok) {
|
|
5406
|
-
const msg3 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
|
|
5407
|
-
opts.renderer.write(msg3);
|
|
5408
|
-
return { message: msg3 };
|
|
5409
|
-
}
|
|
5410
|
-
const msg2 = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
|
|
5411
|
-
opts.renderer.writeWarning(msg2);
|
|
5412
|
-
return { message: msg2 };
|
|
5413
|
-
}
|
|
5414
|
-
if (cmd === "spawn" || cmd === "add") {
|
|
5415
|
-
const role = subargs[0] ?? "worker";
|
|
5416
|
-
const count = Math.min(16, Math.max(1, Number.parseInt(subargs[1] ?? "1", 10) || 1));
|
|
5417
|
-
if (!opts.onFleetSpawn) {
|
|
5418
|
-
const msg3 = `${color.amber("\u26A0 /fleet spawn is not wired in this session.")}`;
|
|
5419
|
-
opts.renderer.writeWarning(msg3);
|
|
5420
|
-
return { message: msg3 };
|
|
5421
|
-
}
|
|
5422
|
-
const spawned = [];
|
|
5423
|
-
let msg2;
|
|
5424
|
-
for (let i = 0; i < count; i++) {
|
|
5425
|
-
try {
|
|
5426
|
-
const id = await opts.onFleetSpawn(role);
|
|
5427
|
-
spawned.push(id);
|
|
5428
|
-
} catch (err) {
|
|
5429
|
-
const msg3 = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${err instanceof Error ? err.message : String(err)}`;
|
|
5430
|
-
opts.renderer.writeWarning(msg3);
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
if (spawned.length === count) {
|
|
5434
|
-
msg2 = `${color.green("\u2713 Spawned")} ${count} subagent(s) of role ${color.bold(role)}.`;
|
|
5435
|
-
opts.renderer.write(msg2);
|
|
5436
|
-
} else {
|
|
5437
|
-
msg2 = `${color.amber("\u26A0 Spawned")} ${spawned.length}/${count} subagent(s). Check /fleet for details.`;
|
|
5438
|
-
opts.renderer.writeWarning(msg2);
|
|
5439
|
-
}
|
|
5440
|
-
return { message: msg2 };
|
|
5441
|
-
}
|
|
5442
|
-
if (cmd === "list" || cmd === "roster" || cmd === "agents") {
|
|
5443
|
-
const lines = [`${color.bold("Agent Roster")} ${color.dim("(spawn with /fleet spawn <role>)")}`];
|
|
5444
|
-
for (const { phase, label } of PHASE_ORDER) {
|
|
5445
|
-
const defs = AGENTS_BY_PHASE[phase];
|
|
5446
|
-
if (!defs || defs.length === 0) continue;
|
|
5447
|
-
lines.push("");
|
|
5448
|
-
lines.push(color.cyan(` Phase ${label}`));
|
|
5449
|
-
for (const def of defs) {
|
|
5450
|
-
const role = (def.config.role ?? "").padEnd(18);
|
|
5451
|
-
lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
|
-
const msg2 = lines.join("\n");
|
|
5455
|
-
opts.renderer.write(msg2);
|
|
5456
|
-
return { message: msg2 };
|
|
5457
|
-
}
|
|
5458
|
-
if (cmd === "dispatch" || cmd === "route") {
|
|
5459
|
-
const task = subargs.join(" ").trim();
|
|
5460
|
-
if (!task) {
|
|
5461
|
-
const msg3 = `Usage: /fleet dispatch <task description> \u2014 routes the task to the best agent.`;
|
|
5462
|
-
opts.renderer.writeWarning(msg3);
|
|
5463
|
-
return { message: msg3 };
|
|
5464
|
-
}
|
|
5465
|
-
const decision = await dispatchAgent(task, { classifier: opts.onDispatchClassify });
|
|
5466
|
-
const pct2 = Math.round(decision.confidence * 100);
|
|
5467
|
-
const lines = [];
|
|
5468
|
-
lines.push(
|
|
5469
|
-
`${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`
|
|
5470
|
-
);
|
|
5471
|
-
lines.push(` ${color.dim(decision.definition.capability.summary)}`);
|
|
5472
|
-
lines.push(` ${color.dim("why:")} ${decision.reason}`);
|
|
5473
|
-
if (decision.alternatives.length > 0) {
|
|
5474
|
-
const alts = decision.alternatives.slice(0, 3).map((a) => a.role).join(", ");
|
|
5475
|
-
lines.push(` ${color.dim("alternatives:")} ${alts}`);
|
|
5476
|
-
}
|
|
5477
|
-
if (opts.onFleetSpawn) {
|
|
5478
|
-
try {
|
|
5479
|
-
const id = await opts.onFleetSpawn(decision.role);
|
|
5480
|
-
lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
|
|
5481
|
-
} catch (err) {
|
|
5482
|
-
lines.push(
|
|
5483
|
-
` ${color.amber("\u26A0 spawn failed:")} ${err instanceof Error ? err.message : String(err)}`
|
|
5484
|
-
);
|
|
5485
|
-
}
|
|
5486
|
-
} else {
|
|
5487
|
-
lines.push(` ${color.dim("(no fleet active \u2014 run /autonomy parallel or --director to spawn)")}`);
|
|
5488
|
-
}
|
|
5489
|
-
const msg2 = lines.join("\n");
|
|
5490
|
-
opts.renderer.write(msg2);
|
|
5491
|
-
return { message: msg2 };
|
|
5492
|
-
}
|
|
5493
|
-
if (cmd === "concurrency" || cmd === "slots" || cmd === "parallel") {
|
|
5494
|
-
if (opts.onFleet) {
|
|
5495
|
-
const n = subargs[0];
|
|
5496
|
-
const msg3 = await opts.onFleet("concurrency", n || void 0);
|
|
5497
|
-
return { message: msg3 };
|
|
6248
|
+
switch (cmd) {
|
|
6249
|
+
// ── /fleet (default / status / manifest) ────────────────────────────
|
|
6250
|
+
case "":
|
|
6251
|
+
case "status":
|
|
6252
|
+
case "info":
|
|
6253
|
+
case "manifest":
|
|
6254
|
+
return await handleStatus(opts, cmd);
|
|
6255
|
+
// ── /fleet usage ────────────────────────────────────────────────────
|
|
6256
|
+
case "usage":
|
|
6257
|
+
case "cost":
|
|
6258
|
+
case "tokens":
|
|
6259
|
+
return await handleUsage(opts);
|
|
6260
|
+
// ── /fleet retry ─────────────────────────────────────────────────────
|
|
6261
|
+
case "retry":
|
|
6262
|
+
return await handleRetry(opts, subargs);
|
|
6263
|
+
// ── /fleet journal / log ─────────────────────────────────────────────
|
|
6264
|
+
case "journal":
|
|
6265
|
+
case "log":
|
|
6266
|
+
return await handleJournal(opts, subargs);
|
|
6267
|
+
// ── /fleet kill ──────────────────────────────────────────────────────
|
|
6268
|
+
case "kill":
|
|
6269
|
+
case "stop-all":
|
|
6270
|
+
return await handleKill(opts, subargs);
|
|
6271
|
+
// ── /fleet terminate <subagentId> ────────────────────────────────────
|
|
6272
|
+
case "terminate":
|
|
6273
|
+
case "stop":
|
|
6274
|
+
return handleTerminate(opts, subargs);
|
|
6275
|
+
// ── /fleet spawn <role> [count] ──────────────────────────────────────
|
|
6276
|
+
case "spawn":
|
|
6277
|
+
case "add":
|
|
6278
|
+
return await handleSpawn(opts, subargs);
|
|
6279
|
+
// ── /fleet list ──────────────────────────────────────────────────────
|
|
6280
|
+
case "list":
|
|
6281
|
+
case "roster":
|
|
6282
|
+
case "agents":
|
|
6283
|
+
return handleList();
|
|
6284
|
+
// ── /fleet dispatch <task> ───────────────────────────────────────────
|
|
6285
|
+
case "dispatch":
|
|
6286
|
+
case "route":
|
|
6287
|
+
return await handleDispatch(opts, subargs);
|
|
6288
|
+
// ── /fleet concurrency [n] ───────────────────────────────────────────
|
|
6289
|
+
case "concurrency":
|
|
6290
|
+
case "slots":
|
|
6291
|
+
case "parallel":
|
|
6292
|
+
return await handleConcurrency(opts, subargs);
|
|
6293
|
+
// ── /fleet help ──────────────────────────────────────────────────────
|
|
6294
|
+
case "help":
|
|
6295
|
+
case "?":
|
|
6296
|
+
return handleFleetHelp(opts);
|
|
6297
|
+
// ── Unknown command ──────────────────────────────────────────────────
|
|
6298
|
+
default: {
|
|
6299
|
+
const valid = ["status", "list", "dispatch", "usage", "spawn", "terminate", "kill", "retry", "concurrency", "journal"];
|
|
6300
|
+
const msg = `Unknown subcommand "${cmd}". Valid subcommands: ${valid.join(", ")}. Run /fleet with no args to see status, or /fleet help for usage.`;
|
|
6301
|
+
opts.renderer.writeWarning(msg);
|
|
6302
|
+
return { message: msg };
|
|
5498
6303
|
}
|
|
5499
|
-
const msg2 = `${color.amber("\u26A0 /fleet concurrency is not wired in this session.")}`;
|
|
5500
|
-
opts.renderer.writeWarning(msg2);
|
|
5501
|
-
return { message: msg2 };
|
|
5502
6304
|
}
|
|
5503
|
-
if (cmd === "help" || cmd === "?") {
|
|
5504
|
-
const msg2 = [
|
|
5505
|
-
`${color.bold("Fleet Commands")}`,
|
|
5506
|
-
` ${color.dim("/fleet")} Show fleet status (default)`,
|
|
5507
|
-
` ${color.dim("/fleet status")} Same as /fleet (verbose status)`,
|
|
5508
|
-
` ${color.dim("/fleet list")} List the agent roster grouped by phase`,
|
|
5509
|
-
` ${color.dim("/fleet dispatch <task>")} Route a task to the best agent and spawn it`,
|
|
5510
|
-
` ${color.dim("/fleet spawn <role> [count]")} Spawn N subagents of a role (default 1)`,
|
|
5511
|
-
` ${color.dim("/fleet terminate <subagentId>")} Stop a specific subagent by id`,
|
|
5512
|
-
` ${color.dim("/fleet kill")} Stop all running subagents`,
|
|
5513
|
-
` ${color.dim("/fleet usage")} Token and cost breakdown across the fleet`,
|
|
5514
|
-
` ${color.dim("/fleet concurrency [n]")} Show or set the concurrent-subagent ceiling`,
|
|
5515
|
-
` ${color.dim("/fleet journal")} Show recent journal entries from /goal journal`
|
|
5516
|
-
].join("\n");
|
|
5517
|
-
opts.renderer.write(msg2);
|
|
5518
|
-
return { message: msg2 };
|
|
5519
|
-
}
|
|
5520
|
-
const valid = ["status", "list", "dispatch", "usage", "spawn", "terminate", "kill", "retry", "concurrency", "journal"];
|
|
5521
|
-
const msg = `Unknown subcommand "${cmd}". Valid subcommands: ${valid.join(", ")}. Run /fleet with no args to see status, or /fleet help for usage.`;
|
|
5522
|
-
opts.renderer.writeWarning(msg);
|
|
5523
|
-
return { message: msg };
|
|
5524
6305
|
}
|
|
5525
6306
|
};
|
|
5526
6307
|
}
|
|
6308
|
+
async function handleStatus(opts, cmd) {
|
|
6309
|
+
if (opts.onFleetStatus) {
|
|
6310
|
+
const status = opts.onFleetStatus();
|
|
6311
|
+
if (!status) {
|
|
6312
|
+
const msg3 = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
|
|
6313
|
+
opts.renderer.write(msg3);
|
|
6314
|
+
return { message: msg3 };
|
|
6315
|
+
}
|
|
6316
|
+
const lines = [];
|
|
6317
|
+
lines.push(`${color.bold("Fleet Status")}`);
|
|
6318
|
+
lines.push(
|
|
6319
|
+
color.dim(
|
|
6320
|
+
` coordinator: ${status.coordinatorId} \xB7 pending: ${status.pendingTasks} \xB7 done: ${status.completedTasks}`
|
|
6321
|
+
)
|
|
6322
|
+
);
|
|
6323
|
+
if (status.subagents.length === 0) {
|
|
6324
|
+
lines.push(color.dim(" No active subagents."));
|
|
6325
|
+
} else {
|
|
6326
|
+
lines.push("");
|
|
6327
|
+
lines.push(
|
|
6328
|
+
` ${color.bold("ID").padEnd(36)} ${color.bold("NAME").padEnd(16)} ${color.bold("STATUS").padEnd(10)} ${color.bold("TASK")}`
|
|
6329
|
+
);
|
|
6330
|
+
lines.push(color.dim(" " + "\u2500".repeat(80)));
|
|
6331
|
+
for (const sa of status.subagents) {
|
|
6332
|
+
const id = sa.id?.padEnd(36) ?? "".padEnd(36);
|
|
6333
|
+
const name = (sa.name ?? "worker").padEnd(16);
|
|
6334
|
+
const statusColor = sa.status === "running" ? color.green(sa.status.padEnd(10)) : sa.status === "idle" ? color.dim(sa.status.padEnd(10)) : color.dim(sa.status.padEnd(10));
|
|
6335
|
+
const ext = sa.extensions && sa.extensions > 0 ? `${color.yellow(`\u26A1\xD7${sa.extensions}`)} ` : "";
|
|
6336
|
+
const task = sa.currentTask ?? color.dim("\u2014");
|
|
6337
|
+
lines.push(` ${id} ${name} ${statusColor} ${ext}${task}`);
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
const msg2 = lines.join("\n");
|
|
6341
|
+
opts.renderer.write(msg2);
|
|
6342
|
+
return { message: msg2 };
|
|
6343
|
+
}
|
|
6344
|
+
if (opts.onFleet) {
|
|
6345
|
+
const msg2 = await opts.onFleet(cmd || "status", void 0);
|
|
6346
|
+
return { message: msg2 };
|
|
6347
|
+
}
|
|
6348
|
+
const msg = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
|
|
6349
|
+
opts.renderer.write(msg);
|
|
6350
|
+
return { message: msg };
|
|
6351
|
+
}
|
|
6352
|
+
async function handleUsage(opts) {
|
|
6353
|
+
if (opts.onFleetUsage) {
|
|
6354
|
+
const usage = opts.onFleetUsage();
|
|
6355
|
+
if (!usage) {
|
|
6356
|
+
const msg3 = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
|
|
6357
|
+
opts.renderer.write(msg3);
|
|
6358
|
+
return { message: msg3 };
|
|
6359
|
+
}
|
|
6360
|
+
const totalCost = usage.total?.cost ?? 0;
|
|
6361
|
+
const totalIn = usage.total?.input ?? 0;
|
|
6362
|
+
const totalOut = usage.total?.output ?? 0;
|
|
6363
|
+
const lines = [];
|
|
6364
|
+
lines.push(`${color.bold("Fleet Usage")}`);
|
|
6365
|
+
lines.push(
|
|
6366
|
+
` ${color.dim("Total:")} ${color.green(`${totalCost.toFixed(4)}`)} \xB7 ${color.cyan(totalIn.toLocaleString())} in \xB7 ${color.cyan(totalOut.toLocaleString())} out`
|
|
6367
|
+
);
|
|
6368
|
+
const subagents = Object.values(usage.perSubagent);
|
|
6369
|
+
if (subagents.length > 0) {
|
|
6370
|
+
lines.push("");
|
|
6371
|
+
for (const sa of subagents) {
|
|
6372
|
+
const name = (sa.subagentId ?? "?").padEnd(20);
|
|
6373
|
+
const cost = `${(sa.cost ?? 0).toFixed(4)}`.padStart(10);
|
|
6374
|
+
const tokens = `${sa.input ?? 0} in / ${sa.output ?? 0} out`.padEnd(30);
|
|
6375
|
+
lines.push(` ${color.dim(name)} ${color.cyan(cost)} ${color.dim(tokens)}`);
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
const msg2 = lines.join("\n");
|
|
6379
|
+
opts.renderer.write(msg2);
|
|
6380
|
+
return { message: msg2 };
|
|
6381
|
+
}
|
|
6382
|
+
if (opts.onFleet) {
|
|
6383
|
+
const msg2 = await opts.onFleet("usage", void 0);
|
|
6384
|
+
return { message: msg2 };
|
|
6385
|
+
}
|
|
6386
|
+
const msg = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
|
|
6387
|
+
opts.renderer.write(msg);
|
|
6388
|
+
return { message: msg };
|
|
6389
|
+
}
|
|
6390
|
+
async function handleRetry(opts, subargs) {
|
|
6391
|
+
if (opts.onFleetRetry) {
|
|
6392
|
+
const targetId = subargs[0];
|
|
6393
|
+
const msg2 = await opts.onFleetRetry(targetId);
|
|
6394
|
+
return { message: msg2 };
|
|
6395
|
+
}
|
|
6396
|
+
if (opts.onFleet) {
|
|
6397
|
+
const msg2 = await opts.onFleet("retry", subargs[0]);
|
|
6398
|
+
return { message: msg2 };
|
|
6399
|
+
}
|
|
6400
|
+
const msg = `Retry is only available when director mode is active.`;
|
|
6401
|
+
opts.renderer.writeWarning(msg);
|
|
6402
|
+
return { message: msg };
|
|
6403
|
+
}
|
|
6404
|
+
async function handleJournal(opts, subargs) {
|
|
6405
|
+
if (opts.onFleetLog) {
|
|
6406
|
+
const subagentId = subargs[0];
|
|
6407
|
+
const mode = subargs[1] === "raw" ? "raw" : "summary";
|
|
6408
|
+
const msg2 = await opts.onFleetLog(subagentId, mode);
|
|
6409
|
+
return { message: msg2 };
|
|
6410
|
+
}
|
|
6411
|
+
if (opts.onFleet) {
|
|
6412
|
+
const msg2 = await opts.onFleet("log", subargs[0]);
|
|
6413
|
+
return { message: msg2 };
|
|
6414
|
+
}
|
|
6415
|
+
const msg = `${color.dim("No journal entries yet.")}`;
|
|
6416
|
+
opts.renderer.write(msg);
|
|
6417
|
+
return { message: msg };
|
|
6418
|
+
}
|
|
6419
|
+
async function handleKill(opts, subargs) {
|
|
6420
|
+
const targetId = subargs[0];
|
|
6421
|
+
if (targetId) {
|
|
6422
|
+
if (opts.onFleet) {
|
|
6423
|
+
const msg3 = await opts.onFleet("kill", targetId);
|
|
6424
|
+
return { message: msg3 };
|
|
6425
|
+
}
|
|
6426
|
+
const msg2 = `${color.amber("\u26A0 /fleet kill <id> is not wired in this session.")}`;
|
|
6427
|
+
opts.renderer.writeWarning(msg2);
|
|
6428
|
+
return { message: msg2 };
|
|
6429
|
+
}
|
|
6430
|
+
if (opts.onFleetKill) {
|
|
6431
|
+
const killed = opts.onFleetKill();
|
|
6432
|
+
const msg2 = `${color.red("\u2717 Killed")} ${killed} subagent(s).`;
|
|
6433
|
+
opts.renderer.write(msg2);
|
|
6434
|
+
return { message: msg2 };
|
|
6435
|
+
}
|
|
6436
|
+
const msg = `${color.amber("\u26A0 /fleet kill is not wired in this session.")}`;
|
|
6437
|
+
opts.renderer.writeWarning(msg);
|
|
6438
|
+
return { message: msg };
|
|
6439
|
+
}
|
|
6440
|
+
function handleTerminate(opts, subargs) {
|
|
6441
|
+
const targetId = subargs[0];
|
|
6442
|
+
if (!targetId) {
|
|
6443
|
+
const msg2 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
|
|
6444
|
+
opts.renderer.writeWarning(msg2);
|
|
6445
|
+
return { message: msg2 };
|
|
6446
|
+
}
|
|
6447
|
+
if (!opts.onFleetTerminate) {
|
|
6448
|
+
const msg2 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
|
|
6449
|
+
opts.renderer.writeWarning(msg2);
|
|
6450
|
+
return { message: msg2 };
|
|
6451
|
+
}
|
|
6452
|
+
const ok = opts.onFleetTerminate(targetId);
|
|
6453
|
+
if (ok) {
|
|
6454
|
+
const msg2 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
|
|
6455
|
+
opts.renderer.write(msg2);
|
|
6456
|
+
return { message: msg2 };
|
|
6457
|
+
}
|
|
6458
|
+
const msg = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
|
|
6459
|
+
opts.renderer.writeWarning(msg);
|
|
6460
|
+
return { message: msg };
|
|
6461
|
+
}
|
|
6462
|
+
async function handleSpawn(opts, subargs) {
|
|
6463
|
+
const role = subargs[0] ?? "worker";
|
|
6464
|
+
const count = Math.min(16, Math.max(1, Number.parseInt(subargs[1] ?? "1", 10) || 1));
|
|
6465
|
+
if (!opts.onFleetSpawn) {
|
|
6466
|
+
const msg2 = `${color.amber("\u26A0 /fleet spawn is not wired in this session.")}`;
|
|
6467
|
+
opts.renderer.writeWarning(msg2);
|
|
6468
|
+
return { message: msg2 };
|
|
6469
|
+
}
|
|
6470
|
+
const spawned = [];
|
|
6471
|
+
for (let i = 0; i < count; i++) {
|
|
6472
|
+
try {
|
|
6473
|
+
const id = await opts.onFleetSpawn(role);
|
|
6474
|
+
spawned.push(id);
|
|
6475
|
+
} catch (err) {
|
|
6476
|
+
const warnMsg = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${err instanceof Error ? err.message : String(err)}`;
|
|
6477
|
+
opts.renderer.writeWarning(warnMsg);
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
if (spawned.length === count) {
|
|
6481
|
+
const msg2 = `${color.green("\u2713 Spawned")} ${count} subagent(s) of role ${color.bold(role)}.`;
|
|
6482
|
+
opts.renderer.write(msg2);
|
|
6483
|
+
return { message: msg2 };
|
|
6484
|
+
}
|
|
6485
|
+
const msg = `${color.amber("\u26A0 Spawned")} ${spawned.length}/${count} subagent(s). Check /fleet for details.`;
|
|
6486
|
+
opts.renderer.writeWarning(msg);
|
|
6487
|
+
return { message: msg };
|
|
6488
|
+
}
|
|
6489
|
+
function handleList() {
|
|
6490
|
+
const lines = [`${color.bold("Agent Roster")} ${color.dim("(spawn with /fleet spawn <role>)")}`];
|
|
6491
|
+
for (const { phase, label } of PHASE_ORDER) {
|
|
6492
|
+
const defs = AGENTS_BY_PHASE[phase];
|
|
6493
|
+
if (!defs || defs.length === 0) continue;
|
|
6494
|
+
lines.push("");
|
|
6495
|
+
lines.push(color.cyan(` Phase ${label}`));
|
|
6496
|
+
for (const def of defs) {
|
|
6497
|
+
const role = (def.config.role ?? "").padEnd(18);
|
|
6498
|
+
lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
return { message: lines.join("\n") };
|
|
6502
|
+
}
|
|
6503
|
+
async function handleDispatch(opts, subargs) {
|
|
6504
|
+
const task = subargs.join(" ").trim();
|
|
6505
|
+
if (!task) {
|
|
6506
|
+
const msg2 = `Usage: /fleet dispatch <task description> \u2014 routes the task to the best agent.`;
|
|
6507
|
+
opts.renderer.writeWarning(msg2);
|
|
6508
|
+
return { message: msg2 };
|
|
6509
|
+
}
|
|
6510
|
+
const decision = await dispatchAgent(task, { classifier: opts.onDispatchClassify });
|
|
6511
|
+
const pct2 = Math.round(decision.confidence * 100);
|
|
6512
|
+
const lines = [];
|
|
6513
|
+
lines.push(
|
|
6514
|
+
`${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`
|
|
6515
|
+
);
|
|
6516
|
+
lines.push(` ${color.dim(decision.definition.capability.summary)}`);
|
|
6517
|
+
lines.push(` ${color.dim("why:")} ${decision.reason}`);
|
|
6518
|
+
if (decision.alternatives.length > 0) {
|
|
6519
|
+
const alts = decision.alternatives.slice(0, 3).map((a) => a.role).join(", ");
|
|
6520
|
+
lines.push(` ${color.dim("alternatives:")} ${alts}`);
|
|
6521
|
+
}
|
|
6522
|
+
if (opts.onFleetSpawn) {
|
|
6523
|
+
try {
|
|
6524
|
+
const id = await opts.onFleetSpawn(decision.role);
|
|
6525
|
+
lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
|
|
6526
|
+
} catch (err) {
|
|
6527
|
+
lines.push(
|
|
6528
|
+
` ${color.amber("\u26A0 spawn failed:")} ${err instanceof Error ? err.message : String(err)}`
|
|
6529
|
+
);
|
|
6530
|
+
}
|
|
6531
|
+
} else {
|
|
6532
|
+
lines.push(` ${color.dim("(no fleet active \u2014 run /autonomy parallel or --director to spawn)")}`);
|
|
6533
|
+
}
|
|
6534
|
+
const msg = lines.join("\n");
|
|
6535
|
+
opts.renderer.write(msg);
|
|
6536
|
+
return { message: msg };
|
|
6537
|
+
}
|
|
6538
|
+
async function handleConcurrency(opts, subargs) {
|
|
6539
|
+
if (opts.onFleet) {
|
|
6540
|
+
const n = subargs[0];
|
|
6541
|
+
const msg2 = await opts.onFleet("concurrency", n || void 0);
|
|
6542
|
+
return { message: msg2 };
|
|
6543
|
+
}
|
|
6544
|
+
const msg = `${color.amber("\u26A0 /fleet concurrency is not wired in this session.")}`;
|
|
6545
|
+
opts.renderer.writeWarning(msg);
|
|
6546
|
+
return { message: msg };
|
|
6547
|
+
}
|
|
6548
|
+
function handleFleetHelp(opts) {
|
|
6549
|
+
const msg = [
|
|
6550
|
+
`${color.bold("Fleet Commands")}`,
|
|
6551
|
+
` ${color.dim("/fleet")} Show fleet status (default)`,
|
|
6552
|
+
` ${color.dim("/fleet status")} Same as /fleet (verbose status)`,
|
|
6553
|
+
` ${color.dim("/fleet list")} List the agent roster grouped by phase`,
|
|
6554
|
+
` ${color.dim("/fleet dispatch <task>")} Route a task to the best agent and spawn it`,
|
|
6555
|
+
` ${color.dim("/fleet spawn <role> [count]")} Spawn N subagents of a role (default 1)`,
|
|
6556
|
+
` ${color.dim("/fleet terminate <subagentId>")} Stop a specific subagent by id`,
|
|
6557
|
+
` ${color.dim("/fleet kill")} Stop all running subagents`,
|
|
6558
|
+
` ${color.dim("/fleet usage")} Token and cost breakdown across the fleet`,
|
|
6559
|
+
` ${color.dim("/fleet concurrency [n]")} Show or set the concurrent-subagent ceiling`,
|
|
6560
|
+
` ${color.dim("/fleet journal")} Show recent journal entries from /goal journal`
|
|
6561
|
+
].join("\n");
|
|
6562
|
+
opts.renderer.write(msg);
|
|
6563
|
+
return { message: msg };
|
|
6564
|
+
}
|
|
5527
6565
|
|
|
5528
6566
|
// src/slash-commands/goal-refiner.ts
|
|
5529
6567
|
async function refineGoal(rawGoal, provider, model) {
|
|
@@ -5934,6 +6972,9 @@ function buildHelpCommand(opts) {
|
|
|
5934
6972
|
}
|
|
5935
6973
|
};
|
|
5936
6974
|
}
|
|
6975
|
+
|
|
6976
|
+
// src/slash-commands/init.ts
|
|
6977
|
+
init_helpers();
|
|
5937
6978
|
function buildInitCommand(opts) {
|
|
5938
6979
|
return {
|
|
5939
6980
|
name: "init",
|
|
@@ -6233,6 +7274,7 @@ function buildMcpSlashCommand(opts) {
|
|
|
6233
7274
|
}
|
|
6234
7275
|
|
|
6235
7276
|
// src/slash-commands/memory.ts
|
|
7277
|
+
init_helpers();
|
|
6236
7278
|
function buildMemoryCommand(opts) {
|
|
6237
7279
|
return {
|
|
6238
7280
|
name: "memory",
|
|
@@ -6241,9 +7283,9 @@ function buildMemoryCommand(opts) {
|
|
|
6241
7283
|
async run(args) {
|
|
6242
7284
|
const store = opts.memoryStore;
|
|
6243
7285
|
if (!store) return { message: "No memory store configured." };
|
|
6244
|
-
const
|
|
7286
|
+
const { cmd, rest } = parseSubcommand(args);
|
|
6245
7287
|
const restJoined = rest.join(" ").trim();
|
|
6246
|
-
switch (
|
|
7288
|
+
switch (cmd) {
|
|
6247
7289
|
case "":
|
|
6248
7290
|
case "show":
|
|
6249
7291
|
case "list": {
|
|
@@ -6278,27 +7320,19 @@ function buildMemoryCommand(opts) {
|
|
|
6278
7320
|
}
|
|
6279
7321
|
default:
|
|
6280
7322
|
return {
|
|
6281
|
-
message:
|
|
7323
|
+
message: unknownSubcommand(cmd, ["show", "remember", "forget", "clear", "compact", "stats"], "memory")
|
|
6282
7324
|
};
|
|
6283
7325
|
}
|
|
6284
7326
|
}
|
|
6285
7327
|
};
|
|
6286
7328
|
}
|
|
6287
|
-
|
|
6288
|
-
const entriesBlock = entries.map(
|
|
6289
|
-
(e, i) => `${i + 1}. [${e.ts.slice(0, 10)}] ${e.id}
|
|
6290
|
-
${e.text}${e.tags ? `
|
|
6291
|
-
tags: ${e.tags.join(", ")}` : ""}${e.type ? `
|
|
6292
|
-
type: ${e.type}` : ""}${e.priority ? `
|
|
6293
|
-
priority: ${e.priority}` : ""}`
|
|
6294
|
-
).join("\n\n");
|
|
6295
|
-
return `You are a memory curator. Your task is to review, deduplicate, and improve a set of long-term memory entries.
|
|
7329
|
+
var COMPACT_SYSTEM_PROMPT = `You are a memory curator. Your task is to review, deduplicate, and improve a set of long-term memory entries.
|
|
6296
7330
|
|
|
6297
7331
|
These entries are injected into the context of an AI coding agent. Every token counts. The memory must be concise, accurate, and free of noise.
|
|
6298
7332
|
|
|
6299
7333
|
## Current Memory Entries
|
|
6300
7334
|
|
|
6301
|
-
|
|
7335
|
+
__ENTRIES__
|
|
6302
7336
|
|
|
6303
7337
|
## Your Task
|
|
6304
7338
|
|
|
@@ -6336,6 +7370,15 @@ Return ONLY valid JSON with this structure:
|
|
|
6336
7370
|
}
|
|
6337
7371
|
|
|
6338
7372
|
Use the EXACT entry IDs from the list above for "targets". No markdown, no explanation outside the JSON.`;
|
|
7373
|
+
function buildCompactPrompt(entries) {
|
|
7374
|
+
const entriesBlock = entries.map(
|
|
7375
|
+
(e, i) => `${i + 1}. [${e.ts.slice(0, 10)}] ${e.id}
|
|
7376
|
+
${e.text}${e.tags ? `
|
|
7377
|
+
tags: ${e.tags.join(", ")}` : ""}${e.type ? `
|
|
7378
|
+
type: ${e.type}` : ""}${e.priority ? `
|
|
7379
|
+
priority: ${e.priority}` : ""}`
|
|
7380
|
+
).join("\n\n");
|
|
7381
|
+
return COMPACT_SYSTEM_PROMPT.replace("__ENTRIES__", entriesBlock);
|
|
6339
7382
|
}
|
|
6340
7383
|
async function runCompact(opts) {
|
|
6341
7384
|
const store = opts.memoryStore;
|
|
@@ -6717,11 +7760,9 @@ ${targetMode.description}`
|
|
|
6717
7760
|
}
|
|
6718
7761
|
};
|
|
6719
7762
|
}
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
isEncrypted: () => false
|
|
6724
|
-
};
|
|
7763
|
+
|
|
7764
|
+
// src/slash-commands/models.ts
|
|
7765
|
+
init_helpers();
|
|
6725
7766
|
async function patchGlobalConfig(globalConfigPath, mutate) {
|
|
6726
7767
|
let raw = "{}";
|
|
6727
7768
|
let fileExists2 = true;
|
|
@@ -6740,9 +7781,9 @@ async function patchGlobalConfig(globalConfigPath, mutate) {
|
|
|
6740
7781
|
}
|
|
6741
7782
|
parsed = {};
|
|
6742
7783
|
}
|
|
6743
|
-
const decrypted = decryptConfigSecrets$1(parsed,
|
|
7784
|
+
const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
|
|
6744
7785
|
mutate(decrypted);
|
|
6745
|
-
const encrypted = encryptConfigSecrets$1(decrypted,
|
|
7786
|
+
const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
|
|
6746
7787
|
await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
6747
7788
|
return decrypted;
|
|
6748
7789
|
}
|
|
@@ -6851,8 +7892,8 @@ function buildModelsCommand(opts) {
|
|
|
6851
7892
|
description: "Manage custom model definitions.",
|
|
6852
7893
|
help,
|
|
6853
7894
|
async run(args) {
|
|
6854
|
-
const
|
|
6855
|
-
const sub =
|
|
7895
|
+
const { cmd, rest } = parseSubcommand(args);
|
|
7896
|
+
const sub = cmd;
|
|
6856
7897
|
if (sub === "help" || sub === "--help") return { message: help };
|
|
6857
7898
|
if (!opts.configStore || !opts.paths) {
|
|
6858
7899
|
return { message: `${color.red("Error")} config store not available.` };
|
|
@@ -6883,7 +7924,7 @@ function buildModelsCommand(opts) {
|
|
|
6883
7924
|
}
|
|
6884
7925
|
try {
|
|
6885
7926
|
if (sub === "add") {
|
|
6886
|
-
const { modelId, def, error } = parseFlags(
|
|
7927
|
+
const { modelId, def, error } = parseFlags(rest);
|
|
6887
7928
|
if (error) {
|
|
6888
7929
|
return { message: `${color.red("Error")}: ${error}. ${color.dim("/models help")}` };
|
|
6889
7930
|
}
|
|
@@ -6910,7 +7951,7 @@ function buildModelsCommand(opts) {
|
|
|
6910
7951
|
return { message: `${color.green("\u2713")} ${color.amber(modelId)} ${existed ? "updated" : "added"}.` };
|
|
6911
7952
|
}
|
|
6912
7953
|
if (sub === "remove" || sub === "rm") {
|
|
6913
|
-
const modelId =
|
|
7954
|
+
const modelId = rest[0];
|
|
6914
7955
|
if (!modelId) {
|
|
6915
7956
|
return { message: `${color.amber("Usage:")} /models remove <id>` };
|
|
6916
7957
|
}
|
|
@@ -6929,7 +7970,7 @@ function buildModelsCommand(opts) {
|
|
|
6929
7970
|
return { message: `${color.green("\u2713")} removed ${color.amber(modelId)}` };
|
|
6930
7971
|
}
|
|
6931
7972
|
return {
|
|
6932
|
-
message: `${color.red("Unknown subcommand")} "${sub}".
|
|
7973
|
+
message: `${color.red("Unknown subcommand")} "${sub}". ${unknownSubcommand(sub, ["add", "remove"], "models")}`
|
|
6933
7974
|
};
|
|
6934
7975
|
} catch (err) {
|
|
6935
7976
|
return {
|
|
@@ -6939,6 +7980,20 @@ function buildModelsCommand(opts) {
|
|
|
6939
7980
|
}
|
|
6940
7981
|
};
|
|
6941
7982
|
}
|
|
7983
|
+
|
|
7984
|
+
// src/slash-commands/suggestion-store.ts
|
|
7985
|
+
var sharedSuggestions = [];
|
|
7986
|
+
function setSuggestions(suggestions) {
|
|
7987
|
+
sharedSuggestions = suggestions;
|
|
7988
|
+
}
|
|
7989
|
+
function getSuggestions() {
|
|
7990
|
+
return sharedSuggestions;
|
|
7991
|
+
}
|
|
7992
|
+
function clearSuggestions() {
|
|
7993
|
+
sharedSuggestions = [];
|
|
7994
|
+
}
|
|
7995
|
+
|
|
7996
|
+
// src/slash-commands/next.ts
|
|
6942
7997
|
function buildNextCommand(opts) {
|
|
6943
7998
|
return {
|
|
6944
7999
|
name: "next",
|
|
@@ -6968,9 +8023,10 @@ function buildNextCommand(opts) {
|
|
|
6968
8023
|
}
|
|
6969
8024
|
const arg = trimmed.toLowerCase();
|
|
6970
8025
|
if (arg === "list" || arg === "ls" || arg === "show") {
|
|
6971
|
-
return
|
|
8026
|
+
return handleList2(opts);
|
|
6972
8027
|
}
|
|
6973
8028
|
if (arg === "clear" || arg === "reset") {
|
|
8029
|
+
clearSuggestions();
|
|
6974
8030
|
opts.onSuggestions?.([]);
|
|
6975
8031
|
return { message: color.dim("Suggestion list cleared.") };
|
|
6976
8032
|
}
|
|
@@ -6982,7 +8038,8 @@ function buildNextCommand(opts) {
|
|
|
6982
8038
|
const current = opts.onNextPredict();
|
|
6983
8039
|
const label = (on) => on ? `${color.cyan("ON")} ${color.dim("(predicted next steps shown after each turn)")}` : `${color.green("OFF")} ${color.dim("(no predictions)")}`;
|
|
6984
8040
|
if (!arg || arg === "status") {
|
|
6985
|
-
const
|
|
8041
|
+
const stored = getSuggestions();
|
|
8042
|
+
const suggestions = stored.length > 0 ? stored : opts.onSuggestions?.() ?? [];
|
|
6986
8043
|
const msg2 = [
|
|
6987
8044
|
`Next-task prediction: ${label(current)}`,
|
|
6988
8045
|
suggestions.length > 0 ? color.dim(` ${suggestions.length} suggestion(s) available \u2014 use /next list to view, /next 1 to execute`) : color.dim(" No suggestions stored \u2014 use /suggest to generate")
|
|
@@ -7015,7 +8072,7 @@ function handleSelection(input, opts) {
|
|
|
7015
8072
|
if (indices.length === 0) {
|
|
7016
8073
|
return { message: color.amber("No valid suggestion numbers found. Use /next 1, /next 1 2 3, etc.") };
|
|
7017
8074
|
}
|
|
7018
|
-
const suggestions = opts.onSuggestions?.() ?? [];
|
|
8075
|
+
const suggestions = getSuggestions().length > 0 ? getSuggestions() : opts.onSuggestions?.() ?? [];
|
|
7019
8076
|
if (suggestions.length === 0) {
|
|
7020
8077
|
return {
|
|
7021
8078
|
message: color.amber("No suggestions available. Run /suggest first, or enable prediction with /next on.")
|
|
@@ -7049,8 +8106,8 @@ function handleSelection(input, opts) {
|
|
|
7049
8106
|
runText
|
|
7050
8107
|
};
|
|
7051
8108
|
}
|
|
7052
|
-
function
|
|
7053
|
-
const suggestions = opts.onSuggestions?.() ?? [];
|
|
8109
|
+
function handleList2(opts) {
|
|
8110
|
+
const suggestions = getSuggestions().length > 0 ? getSuggestions() : opts.onSuggestions?.() ?? [];
|
|
7054
8111
|
if (suggestions.length === 0) {
|
|
7055
8112
|
return {
|
|
7056
8113
|
message: [
|
|
@@ -7196,9 +8253,9 @@ function buildSaveCommand(opts) {
|
|
|
7196
8253
|
}
|
|
7197
8254
|
function buildLoadCommand(opts) {
|
|
7198
8255
|
return {
|
|
7199
|
-
name: "
|
|
8256
|
+
name: "sessions",
|
|
7200
8257
|
category: "Session",
|
|
7201
|
-
aliases: ["
|
|
8258
|
+
aliases: ["resume", "load"],
|
|
7202
8259
|
description: "List recent sessions, show incomplete ones (--incomplete), or plan a recovery (--recover <id>).",
|
|
7203
8260
|
async run(args) {
|
|
7204
8261
|
const parts = args.split(/\s+/).filter(Boolean);
|
|
@@ -7370,11 +8427,6 @@ function summariseEvent(ev) {
|
|
|
7370
8427
|
return color.dim("\u2026");
|
|
7371
8428
|
}
|
|
7372
8429
|
}
|
|
7373
|
-
var noOpVault3 = {
|
|
7374
|
-
encrypt: (v) => v,
|
|
7375
|
-
decrypt: (v) => v,
|
|
7376
|
-
isEncrypted: () => false
|
|
7377
|
-
};
|
|
7378
8430
|
function providerHasKey(entry) {
|
|
7379
8431
|
if (!entry) return false;
|
|
7380
8432
|
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
|
|
@@ -7422,9 +8474,9 @@ async function patchGlobalConfig2(globalConfigPath, mutate) {
|
|
|
7422
8474
|
throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
|
|
7423
8475
|
parsed = {};
|
|
7424
8476
|
}
|
|
7425
|
-
const decrypted = decryptConfigSecrets$1(parsed,
|
|
8477
|
+
const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
|
|
7426
8478
|
mutate(decrypted);
|
|
7427
|
-
const encrypted = encryptConfigSecrets$1(decrypted,
|
|
8479
|
+
const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
|
|
7428
8480
|
await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
7429
8481
|
return decrypted;
|
|
7430
8482
|
}
|
|
@@ -7922,12 +8974,14 @@ function buildSuggestCommand(opts) {
|
|
|
7922
8974
|
const fast = /\b(--fast|-f)\b/.test(trimmed);
|
|
7923
8975
|
if (fast) {
|
|
7924
8976
|
const suggestions = generateHeuristicSuggestions(opts);
|
|
8977
|
+
setSuggestions(suggestions);
|
|
7925
8978
|
opts.onSuggestions?.(suggestions);
|
|
7926
8979
|
const display = formatSuggestions(suggestions);
|
|
7927
8980
|
return { message: display };
|
|
7928
8981
|
}
|
|
7929
8982
|
if (!opts.onSpawnAndWait) {
|
|
7930
8983
|
const suggestions = generateHeuristicSuggestions(opts);
|
|
8984
|
+
setSuggestions(suggestions);
|
|
7931
8985
|
opts.onSuggestions?.(suggestions);
|
|
7932
8986
|
const display = formatSuggestions(suggestions) + "\n" + color.dim("(Heuristic fallback \u2014 multi-agent not enabled)");
|
|
7933
8987
|
return { message: display };
|
|
@@ -7945,9 +8999,11 @@ function buildSuggestCommand(opts) {
|
|
|
7945
8999
|
const suggestions = parseSuggestions(raw);
|
|
7946
9000
|
if (suggestions.length === 0) {
|
|
7947
9001
|
const fallback = ["No pending actions \u2014 everything is up to date."];
|
|
9002
|
+
setSuggestions(fallback);
|
|
7948
9003
|
opts.onSuggestions?.(fallback);
|
|
7949
9004
|
return { message: formatSuggestions(fallback) };
|
|
7950
9005
|
}
|
|
9006
|
+
setSuggestions(suggestions);
|
|
7951
9007
|
opts.onSuggestions?.(suggestions);
|
|
7952
9008
|
return { message: formatSuggestions(suggestions) };
|
|
7953
9009
|
} catch (err) {
|
|
@@ -8013,11 +9069,7 @@ function formatSuggestions(suggestions) {
|
|
|
8013
9069
|
}
|
|
8014
9070
|
return lines.join("\n");
|
|
8015
9071
|
}
|
|
8016
|
-
|
|
8017
|
-
encrypt: (v) => v,
|
|
8018
|
-
decrypt: (v) => v,
|
|
8019
|
-
isEncrypted: () => false
|
|
8020
|
-
};
|
|
9072
|
+
init_helpers();
|
|
8021
9073
|
function formatDelay(ms) {
|
|
8022
9074
|
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
8023
9075
|
if (ms === 0) return "disabled";
|
|
@@ -8061,8 +9113,8 @@ function buildSettingsCommand(opts) {
|
|
|
8061
9113
|
description: "View or change settings (auto-proceed delay, default autonomy mode, launch hints).",
|
|
8062
9114
|
help,
|
|
8063
9115
|
async run(args) {
|
|
8064
|
-
const
|
|
8065
|
-
const sub =
|
|
9116
|
+
const { cmd, rest } = parseSubcommand(args);
|
|
9117
|
+
const sub = cmd;
|
|
8066
9118
|
if (sub === "help" || sub === "--help") {
|
|
8067
9119
|
return { message: this.help ?? "" };
|
|
8068
9120
|
}
|
|
@@ -8090,11 +9142,11 @@ function buildSettingsCommand(opts) {
|
|
|
8090
9142
|
configStore: opts.configStore,
|
|
8091
9143
|
globalConfigPath: opts.paths.globalConfig,
|
|
8092
9144
|
inProjectConfigPath: opts.paths.inProjectConfig,
|
|
8093
|
-
vault:
|
|
9145
|
+
vault: noOpVault
|
|
8094
9146
|
};
|
|
8095
9147
|
try {
|
|
8096
9148
|
if (sub === "delay") {
|
|
8097
|
-
const raw =
|
|
9149
|
+
const raw = rest[0];
|
|
8098
9150
|
if (raw === void 0) {
|
|
8099
9151
|
return {
|
|
8100
9152
|
message: `${color.amber("Usage:")} /settings delay <seconds> ${color.dim("(0 disables)")}`
|
|
@@ -8113,7 +9165,7 @@ function buildSettingsCommand(opts) {
|
|
|
8113
9165
|
return { message: `${color.green("\u2713")} auto-proceed delay \u2192 ${formatDelay(ms)}` };
|
|
8114
9166
|
}
|
|
8115
9167
|
if (sub === "mode") {
|
|
8116
|
-
const raw = (
|
|
9168
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
8117
9169
|
const modes = ["off", "suggest", "auto"];
|
|
8118
9170
|
if (!modes.includes(raw)) {
|
|
8119
9171
|
return { message: `${color.amber("Usage:")} /settings mode off|suggest|auto` };
|
|
@@ -8124,7 +9176,7 @@ function buildSettingsCommand(opts) {
|
|
|
8124
9176
|
return { message: `${color.green("\u2713")} default autonomy \u2192 ${color.bold(raw)}` };
|
|
8125
9177
|
}
|
|
8126
9178
|
if (sub === "hints") {
|
|
8127
|
-
const raw = (
|
|
9179
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
8128
9180
|
if (!["on", "off"].includes(raw)) {
|
|
8129
9181
|
return { message: `${color.amber("Usage:")} /settings hints on|off` };
|
|
8130
9182
|
}
|
|
@@ -8135,7 +9187,7 @@ function buildSettingsCommand(opts) {
|
|
|
8135
9187
|
return { message: `${color.green("\u2713")} launch hints \u2192 ${on ? color.cyan("on") : color.dim("off")}` };
|
|
8136
9188
|
}
|
|
8137
9189
|
if (sub === "debug-stream") {
|
|
8138
|
-
const raw = (
|
|
9190
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
8139
9191
|
if (!["on", "off"].includes(raw)) {
|
|
8140
9192
|
return { message: `${color.amber("Usage:")} /settings debug-stream on|off` };
|
|
8141
9193
|
}
|
|
@@ -8148,7 +9200,7 @@ function buildSettingsCommand(opts) {
|
|
|
8148
9200
|
return { message: `${color.green("\u2713")} debug stream \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim("raw SSE hex-dump to stderr")}` };
|
|
8149
9201
|
}
|
|
8150
9202
|
if (sub === "config-scope") {
|
|
8151
|
-
const raw = (
|
|
9203
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
8152
9204
|
if (!["global", "project"].includes(raw)) {
|
|
8153
9205
|
return { message: `${color.amber("Usage:")} /settings config-scope global|project` };
|
|
8154
9206
|
}
|
|
@@ -8159,7 +9211,7 @@ function buildSettingsCommand(opts) {
|
|
|
8159
9211
|
return { message: `${color.green("\u2713")} config scope \u2192 ${label}` };
|
|
8160
9212
|
}
|
|
8161
9213
|
return {
|
|
8162
|
-
message: `${color.red("Unknown setting")} "${sub}".
|
|
9214
|
+
message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "defaults"], "settings")}`
|
|
8163
9215
|
};
|
|
8164
9216
|
} catch (err) {
|
|
8165
9217
|
return {
|
|
@@ -8169,11 +9221,6 @@ function buildSettingsCommand(opts) {
|
|
|
8169
9221
|
}
|
|
8170
9222
|
};
|
|
8171
9223
|
}
|
|
8172
|
-
var noOpVault5 = {
|
|
8173
|
-
encrypt: (v) => v,
|
|
8174
|
-
decrypt: (v) => v,
|
|
8175
|
-
isEncrypted: () => false
|
|
8176
|
-
};
|
|
8177
9224
|
var HELP = [
|
|
8178
9225
|
"Usage:",
|
|
8179
9226
|
" /telegram-setup Show setup instructions",
|
|
@@ -8275,7 +9322,7 @@ function buildTelegramSetupCommand(opts) {
|
|
|
8275
9322
|
const persistDeps = {
|
|
8276
9323
|
configStore: opts.configStore,
|
|
8277
9324
|
globalConfigPath: opts.paths?.globalConfig ?? "",
|
|
8278
|
-
vault:
|
|
9325
|
+
vault: noOpVault
|
|
8279
9326
|
};
|
|
8280
9327
|
if (!persistDeps.globalConfigPath) {
|
|
8281
9328
|
return {
|
|
@@ -8328,11 +9375,31 @@ function buildSpawnCommand(opts) {
|
|
|
8328
9375
|
name: "spawn",
|
|
8329
9376
|
category: "Agent",
|
|
8330
9377
|
description: "Spawn an isolated subagent to handle a task.",
|
|
9378
|
+
argsHint: "[--name=<label>] [--model=<id>] <task description>",
|
|
9379
|
+
help: [
|
|
9380
|
+
"Fire-and-forget subagent spawn. The subagent runs independently; check",
|
|
9381
|
+
"its status with /agents or /fleet.",
|
|
9382
|
+
"",
|
|
9383
|
+
"Usage:",
|
|
9384
|
+
" /spawn <task description>",
|
|
9385
|
+
" /spawn --name=<label> --model=<id> <task description>",
|
|
9386
|
+
"",
|
|
9387
|
+
"Flags:",
|
|
9388
|
+
" --name=<label> Display name for the subagent",
|
|
9389
|
+
" --provider=<id> Override the provider (defaults to leader provider)",
|
|
9390
|
+
" --model=<id> Override the model (defaults to leader model)",
|
|
9391
|
+
" --tools=a,b,c Comma-separated list of tool names to grant",
|
|
9392
|
+
"",
|
|
9393
|
+
"For smart routing (auto-picking the right agent role), use /fleet dispatch.",
|
|
9394
|
+
"For explicit role assignment, use /fleet spawn <role>.",
|
|
9395
|
+
"",
|
|
9396
|
+
"Requires director mode. Run /director first."
|
|
9397
|
+
].join("\n"),
|
|
8331
9398
|
async run(args) {
|
|
8332
9399
|
const { description, opts: parsed } = parseSpawnFlags(args.trim());
|
|
8333
9400
|
if (!description)
|
|
8334
9401
|
return {
|
|
8335
|
-
message:
|
|
9402
|
+
message: 'Usage: /spawn [--name=<label>] [--model=<id>] <task description>\n\nExamples:\n /spawn "fix the auth bug in session.ts"\n /spawn --name=fixer "audit core for null-deref bugs"\n\nRequires director mode. Run /director first.'
|
|
8336
9403
|
};
|
|
8337
9404
|
if (!opts.onSpawn) return { message: "Multi-agent is not enabled in this session." };
|
|
8338
9405
|
try {
|
|
@@ -8387,6 +9454,19 @@ function buildDirectorCommand(opts) {
|
|
|
8387
9454
|
name: "director",
|
|
8388
9455
|
category: "Agent",
|
|
8389
9456
|
description: "Promote this session to director mode, enabling fleet orchestration tools. Only works before any subagents are spawned.",
|
|
9457
|
+
help: [
|
|
9458
|
+
"Promotes the current session to director mode, which unlocks:",
|
|
9459
|
+
"",
|
|
9460
|
+
" /fleet \u2014 fleet status, spawn, dispatch, kill, usage",
|
|
9461
|
+
" /spawn \u2014 fire-and-forget subagent spawns",
|
|
9462
|
+
" /agents \u2014 subagent status dashboard",
|
|
9463
|
+
"",
|
|
9464
|
+
"Director mode must be activated BEFORE any subagents are spawned.",
|
|
9465
|
+
"Alternatively, start a session with --director: wstack --director",
|
|
9466
|
+
"",
|
|
9467
|
+
"Director mode is a prerequisite for /fleet dispatch, /fleet spawn,",
|
|
9468
|
+
'and /spawn. Without it, those commands will report "not wired."'
|
|
9469
|
+
].join("\n"),
|
|
8390
9470
|
async run() {
|
|
8391
9471
|
if (!opts.onDirector) return { message: "Director promotion is not available in this session." };
|
|
8392
9472
|
const result = await opts.onDirector();
|
|
@@ -8508,6 +9588,9 @@ function buildStatuslineCommand(deps) {
|
|
|
8508
9588
|
}
|
|
8509
9589
|
};
|
|
8510
9590
|
}
|
|
9591
|
+
|
|
9592
|
+
// src/slash-commands/todos.ts
|
|
9593
|
+
init_helpers();
|
|
8511
9594
|
function findTodo(todos, query) {
|
|
8512
9595
|
const asIndex = Number.parseInt(query, 10);
|
|
8513
9596
|
if (!Number.isNaN(asIndex)) {
|
|
@@ -8536,9 +9619,9 @@ function buildTodosCommand(opts) {
|
|
|
8536
9619
|
async run(args) {
|
|
8537
9620
|
const ctx = opts.context;
|
|
8538
9621
|
if (!ctx) return { message: "No active context." };
|
|
8539
|
-
const
|
|
9622
|
+
const { cmd, rest } = parseSubcommand(args);
|
|
8540
9623
|
const restJoined = rest.join(" ").trim();
|
|
8541
|
-
switch (
|
|
9624
|
+
switch (cmd) {
|
|
8542
9625
|
case "":
|
|
8543
9626
|
case "show":
|
|
8544
9627
|
case "list": {
|
|
@@ -8589,12 +9672,15 @@ function buildTodosCommand(opts) {
|
|
|
8589
9672
|
}
|
|
8590
9673
|
default:
|
|
8591
9674
|
return {
|
|
8592
|
-
message:
|
|
9675
|
+
message: unknownSubcommand(cmd, ["show", "clear", "add", "done", "remove"], "todos")
|
|
8593
9676
|
};
|
|
8594
9677
|
}
|
|
8595
9678
|
}
|
|
8596
9679
|
};
|
|
8597
9680
|
}
|
|
9681
|
+
|
|
9682
|
+
// src/slash-commands/tasks.ts
|
|
9683
|
+
init_helpers();
|
|
8598
9684
|
function findTask(tasks, query) {
|
|
8599
9685
|
const asIndex = Number.parseInt(query, 10);
|
|
8600
9686
|
if (!Number.isNaN(asIndex)) {
|
|
@@ -8656,9 +9742,9 @@ function buildTasksCommand(_opts) {
|
|
|
8656
9742
|
}
|
|
8657
9743
|
const sessionId = ctx.session?.id ?? "unknown";
|
|
8658
9744
|
const file = await loadTasks(taskPath) ?? emptyTaskFile(sessionId);
|
|
8659
|
-
const
|
|
9745
|
+
const { cmd, rest } = parseSubcommand(args);
|
|
8660
9746
|
const restJoined = rest.join(" ").trim();
|
|
8661
|
-
switch (
|
|
9747
|
+
switch (cmd) {
|
|
8662
9748
|
case "":
|
|
8663
9749
|
case "show":
|
|
8664
9750
|
case "list":
|
|
@@ -8691,7 +9777,7 @@ ${formatTaskProgress(file.tasks)}` };
|
|
|
8691
9777
|
case "start":
|
|
8692
9778
|
case "done":
|
|
8693
9779
|
case "fail": {
|
|
8694
|
-
if (!restJoined) return { message: `Usage: /tasks ${
|
|
9780
|
+
if (!restJoined) return { message: `Usage: /tasks ${cmd} <id|index>` };
|
|
8695
9781
|
const found = findTask(file.tasks, restJoined);
|
|
8696
9782
|
if (!found) return { message: `No task matched "${restJoined}".` };
|
|
8697
9783
|
const statusMap = {
|
|
@@ -8699,7 +9785,13 @@ ${formatTaskProgress(file.tasks)}` };
|
|
|
8699
9785
|
done: "completed",
|
|
8700
9786
|
fail: "failed"
|
|
8701
9787
|
};
|
|
8702
|
-
|
|
9788
|
+
const verbMap = {
|
|
9789
|
+
start: "Started",
|
|
9790
|
+
done: "Completed",
|
|
9791
|
+
fail: "Failed"
|
|
9792
|
+
};
|
|
9793
|
+
const verb = verbMap[cmd] ?? cmd;
|
|
9794
|
+
found.item.status = statusMap[cmd] ?? "pending";
|
|
8703
9795
|
found.item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8704
9796
|
await saveTasks(taskPath, file);
|
|
8705
9797
|
return { message: `Marked ${verb}: ${found.item.title}
|
|
@@ -8781,7 +9873,7 @@ ${formatTaskProgress(file.tasks)}`
|
|
|
8781
9873
|
}
|
|
8782
9874
|
default:
|
|
8783
9875
|
return {
|
|
8784
|
-
message:
|
|
9876
|
+
message: unknownSubcommand(cmd, ["show", "add", "start", "done", "fail", "status", "depends", "assign", "promote", "clear"], "tasks")
|
|
8785
9877
|
};
|
|
8786
9878
|
}
|
|
8787
9879
|
}
|
|
@@ -9080,8 +10172,18 @@ function buildYoloCommand(opts) {
|
|
|
9080
10172
|
newState = false;
|
|
9081
10173
|
} else if (arg === "toggle") {
|
|
9082
10174
|
newState = !opts.onYolo();
|
|
10175
|
+
} else if (arg === "destructive") {
|
|
10176
|
+
const currentMode = opts.onYolo();
|
|
10177
|
+
if (!currentMode) {
|
|
10178
|
+
const msg3 = `${color.amber("YOLO is OFF.")} Enable YOLO first with /yolo on, then /yolo destructive will control the confirmation gate.`;
|
|
10179
|
+
opts.renderer.writeWarning(msg3);
|
|
10180
|
+
return { message: msg3 };
|
|
10181
|
+
}
|
|
10182
|
+
const msg2 = `${color.amber("Destructive gate:")} ${color.dim("YOLO is ON \u2014 destructive operations require confirmation. Use /yolo destructive to toggle this gate.")}`;
|
|
10183
|
+
opts.renderer.writeWarning(msg2);
|
|
10184
|
+
return { message: msg2 };
|
|
9083
10185
|
} else {
|
|
9084
|
-
const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, or /yolo
|
|
10186
|
+
const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, /yolo toggle, or /yolo destructive.`;
|
|
9085
10187
|
opts.renderer.writeWarning(msg2);
|
|
9086
10188
|
return { message: msg2 };
|
|
9087
10189
|
}
|
|
@@ -9102,6 +10204,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
9102
10204
|
buildClearCommand(opts),
|
|
9103
10205
|
buildCompactCommand(opts),
|
|
9104
10206
|
buildContextCommand(opts),
|
|
10207
|
+
buildDelegateCommand(opts),
|
|
9105
10208
|
buildDevCommand(opts),
|
|
9106
10209
|
buildCodebaseReindexCommand(opts),
|
|
9107
10210
|
buildTechStackCommand(opts),
|
|
@@ -9964,10 +11067,10 @@ var theme = { primary: color.amber };
|
|
|
9964
11067
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
|
|
9965
11068
|
try {
|
|
9966
11069
|
const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
|
|
9967
|
-
const
|
|
11070
|
+
const fs31 = await import('fs/promises');
|
|
9968
11071
|
let existing = {};
|
|
9969
11072
|
try {
|
|
9970
|
-
const raw = await
|
|
11073
|
+
const raw = await fs31.readFile(configPath2, "utf8");
|
|
9971
11074
|
existing = JSON.parse(raw);
|
|
9972
11075
|
} catch {
|
|
9973
11076
|
}
|
|
@@ -11982,6 +13085,9 @@ var exportCmd = async (args, deps) => {
|
|
|
11982
13085
|
}
|
|
11983
13086
|
return 0;
|
|
11984
13087
|
};
|
|
13088
|
+
|
|
13089
|
+
// src/subcommands/handlers/init.ts
|
|
13090
|
+
init_helpers();
|
|
11985
13091
|
var initCmd = async (_args, deps) => {
|
|
11986
13092
|
deps.renderer.write(color.bold("WrongStack init\n"));
|
|
11987
13093
|
deps.renderer.writeInfo("Loading provider catalog from models.dev (cached locally)\u2026");
|
|
@@ -13293,7 +14399,7 @@ var rewindCmd = async (args, deps) => {
|
|
|
13293
14399
|
if (result.revertedFiles.length === 0) {
|
|
13294
14400
|
deps.renderer.write("No files to revert.\n");
|
|
13295
14401
|
if (flags.resume) {
|
|
13296
|
-
const store = new DefaultSessionStore({ dir: sessionsDir });
|
|
14402
|
+
const store = new DefaultSessionStore$1({ dir: sessionsDir });
|
|
13297
14403
|
const resumed = await store.resume(targetSessionId);
|
|
13298
14404
|
const toIdx = result.toPromptIndex;
|
|
13299
14405
|
await resumed.writer.truncateToCheckpoint(toIdx);
|
|
@@ -13311,7 +14417,7 @@ Reverted ${result.revertedFiles.length} file(s):
|
|
|
13311
14417
|
`);
|
|
13312
14418
|
}
|
|
13313
14419
|
if (flags.resume) {
|
|
13314
|
-
const store = new DefaultSessionStore({ dir: sessionsDir });
|
|
14420
|
+
const store = new DefaultSessionStore$1({ dir: sessionsDir });
|
|
13315
14421
|
const resumed = await store.resume(targetSessionId);
|
|
13316
14422
|
const toIdx = result.toPromptIndex;
|
|
13317
14423
|
const removed = await resumed.writer.truncateToCheckpoint(toIdx);
|
|
@@ -13461,10 +14567,10 @@ var auditCmd = async (args, deps) => {
|
|
|
13461
14567
|
return verify.ok ? 0 : 1;
|
|
13462
14568
|
};
|
|
13463
14569
|
async function listAudits(log, dir, deps) {
|
|
13464
|
-
const
|
|
14570
|
+
const fs31 = await import('fs/promises');
|
|
13465
14571
|
let entries;
|
|
13466
14572
|
try {
|
|
13467
|
-
entries = await
|
|
14573
|
+
entries = await fs31.readdir(dir);
|
|
13468
14574
|
} catch {
|
|
13469
14575
|
deps.renderer.write(
|
|
13470
14576
|
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
@@ -14364,22 +15470,22 @@ function fmtDuration(ms) {
|
|
|
14364
15470
|
const remMin = m - h * 60;
|
|
14365
15471
|
return `${h}h${remMin}m`;
|
|
14366
15472
|
}
|
|
14367
|
-
function fmtTaskResultLine(r,
|
|
15473
|
+
function fmtTaskResultLine(r, color63) {
|
|
14368
15474
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
14369
15475
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
14370
15476
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
14371
15477
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
14372
|
-
const errKindChip = errKind ?
|
|
14373
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
15478
|
+
const errKindChip = errKind ? color63.dim(` [${errKind}]`) : "";
|
|
15479
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color63.dim(errTail)}` : "";
|
|
14374
15480
|
switch (r.status) {
|
|
14375
15481
|
case "success":
|
|
14376
|
-
return { mark:
|
|
15482
|
+
return { mark: color63.green("\u2713"), stats, tail: "" };
|
|
14377
15483
|
case "timeout":
|
|
14378
|
-
return { mark:
|
|
15484
|
+
return { mark: color63.yellow("\u23F1"), stats: `${color63.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
14379
15485
|
case "stopped":
|
|
14380
|
-
return { mark:
|
|
15486
|
+
return { mark: color63.dim("\u2298"), stats: `${color63.dim("stopped")} ${stats}`, tail: errSnip };
|
|
14381
15487
|
case "failed":
|
|
14382
|
-
return { mark:
|
|
15488
|
+
return { mark: color63.red("\u2717"), stats: `${color63.red("failed")} ${stats}`, tail: errSnip };
|
|
14383
15489
|
}
|
|
14384
15490
|
}
|
|
14385
15491
|
|
|
@@ -15919,7 +17025,7 @@ async function execute(deps) {
|
|
|
15919
17025
|
onAutonomy,
|
|
15920
17026
|
getNextPredict,
|
|
15921
17027
|
onSuggestionsParsed,
|
|
15922
|
-
getSuggestions,
|
|
17028
|
+
getSuggestions: getSuggestions2,
|
|
15923
17029
|
autoProceedDelayMs,
|
|
15924
17030
|
autoProceedMaxIterations,
|
|
15925
17031
|
onValidateAutoProceed,
|
|
@@ -15928,7 +17034,10 @@ async function execute(deps) {
|
|
|
15928
17034
|
subscribeEternalIteration,
|
|
15929
17035
|
subscribeEternalStage,
|
|
15930
17036
|
skillLoader,
|
|
15931
|
-
modeId
|
|
17037
|
+
modeId,
|
|
17038
|
+
sessionStore,
|
|
17039
|
+
memoryStore,
|
|
17040
|
+
modeStore
|
|
15932
17041
|
} = deps;
|
|
15933
17042
|
let code = 0;
|
|
15934
17043
|
let fleetStatusLine = null;
|
|
@@ -16164,7 +17273,7 @@ async function execute(deps) {
|
|
|
16164
17273
|
configStore,
|
|
16165
17274
|
globalConfigPath: wpaths.globalConfig,
|
|
16166
17275
|
inProjectConfigPath: wpaths.inProjectConfig,
|
|
16167
|
-
vault:
|
|
17276
|
+
vault: noOpVault
|
|
16168
17277
|
},
|
|
16169
17278
|
(autonomy) => {
|
|
16170
17279
|
autonomy.defaultMode = s.mode;
|
|
@@ -16182,8 +17291,7 @@ async function execute(deps) {
|
|
|
16182
17291
|
const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
|
|
16183
17292
|
const raw = await fsp4.readFile(targetPath, "utf8").catch(() => "{}");
|
|
16184
17293
|
const parsed = JSON.parse(raw);
|
|
16185
|
-
const
|
|
16186
|
-
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
17294
|
+
const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
|
|
16187
17295
|
if (s.nextPrediction !== void 0) {
|
|
16188
17296
|
decrypted.nextPrediction = s.nextPrediction;
|
|
16189
17297
|
}
|
|
@@ -16241,7 +17349,7 @@ async function execute(deps) {
|
|
|
16241
17349
|
decrypted.autonomy = autonomy;
|
|
16242
17350
|
}
|
|
16243
17351
|
const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
|
|
16244
|
-
const encrypted = encryptConfigSecrets$1(toWrite,
|
|
17352
|
+
const encrypted = encryptConfigSecrets$1(toWrite, noOpVault);
|
|
16245
17353
|
if (targetPath !== wpaths.globalConfig) {
|
|
16246
17354
|
await fsp4.mkdir(path10.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
|
|
16247
17355
|
}
|
|
@@ -16360,7 +17468,12 @@ async function execute(deps) {
|
|
|
16360
17468
|
open: !!flags.open,
|
|
16361
17469
|
modelsRegistry,
|
|
16362
17470
|
globalConfigPath: wpaths.globalConfig,
|
|
16363
|
-
subscribeEternalIteration
|
|
17471
|
+
subscribeEternalIteration,
|
|
17472
|
+
sessionStore,
|
|
17473
|
+
memoryStore,
|
|
17474
|
+
skillLoader,
|
|
17475
|
+
modeStore,
|
|
17476
|
+
modeId
|
|
16364
17477
|
});
|
|
16365
17478
|
try {
|
|
16366
17479
|
code = await runRepl({
|
|
@@ -16379,7 +17492,7 @@ async function execute(deps) {
|
|
|
16379
17492
|
onAutonomy,
|
|
16380
17493
|
getNextPredict,
|
|
16381
17494
|
onSuggestionsParsed,
|
|
16382
|
-
getSuggestions,
|
|
17495
|
+
getSuggestions: getSuggestions2,
|
|
16383
17496
|
autoProceedDelayMs,
|
|
16384
17497
|
autoProceedMaxIterations,
|
|
16385
17498
|
onValidateAutoProceed,
|
|
@@ -16411,7 +17524,7 @@ async function execute(deps) {
|
|
|
16411
17524
|
onAutonomy,
|
|
16412
17525
|
getNextPredict,
|
|
16413
17526
|
onSuggestionsParsed,
|
|
16414
|
-
getSuggestions,
|
|
17527
|
+
getSuggestions: getSuggestions2,
|
|
16415
17528
|
autoProceedDelayMs,
|
|
16416
17529
|
onValidateAutoProceed,
|
|
16417
17530
|
autoProceedMaxIterations,
|
|
@@ -18090,14 +19203,14 @@ function isIgnored(rel) {
|
|
|
18090
19203
|
return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
|
|
18091
19204
|
}
|
|
18092
19205
|
async function setupCodebaseIndexing(deps) {
|
|
18093
|
-
const { config, pipelines, projectRoot, logger } = deps;
|
|
19206
|
+
const { config, context, pipelines, projectRoot, logger } = deps;
|
|
18094
19207
|
const idx = config.indexing;
|
|
18095
19208
|
if (!idx) return () => {
|
|
18096
19209
|
};
|
|
18097
19210
|
const debounceMs = idx.debounceMs ?? 400;
|
|
18098
19211
|
const onError = (err) => logger.debug(`codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18099
19212
|
if (idx.onSessionStart) {
|
|
18100
|
-
void runStartupIndex({ projectRoot }).then((r) => {
|
|
19213
|
+
void runStartupIndex({ projectRoot, signal: context.signal }).then((r) => {
|
|
18101
19214
|
logger.info(
|
|
18102
19215
|
`codebase index ready: ${r.symbolsIndexed} symbols \xB7 ${r.filesIndexed} files \xB7 ${r.durationMs}ms`
|
|
18103
19216
|
);
|
|
@@ -19754,8 +20867,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
19754
20867
|
onSuggestions: (suggestions) => {
|
|
19755
20868
|
if (suggestions !== void 0) {
|
|
19756
20869
|
currentSuggestions = suggestions;
|
|
20870
|
+
setSuggestions(suggestions);
|
|
19757
20871
|
}
|
|
19758
|
-
|
|
20872
|
+
const shared = getSuggestions();
|
|
20873
|
+
return shared.length > 0 ? shared : currentSuggestions;
|
|
19759
20874
|
},
|
|
19760
20875
|
onAutonomy: (setTo) => {
|
|
19761
20876
|
if (setTo !== void 0) {
|
|
@@ -19951,6 +21066,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
19951
21066
|
}
|
|
19952
21067
|
const disposeIndexing = await setupCodebaseIndexing({
|
|
19953
21068
|
config,
|
|
21069
|
+
context,
|
|
19954
21070
|
pipelines,
|
|
19955
21071
|
projectRoot,
|
|
19956
21072
|
logger
|
|
@@ -20005,8 +21121,12 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
20005
21121
|
getNextPredict: () => nextPredictEnabled,
|
|
20006
21122
|
onSuggestionsParsed: (suggestions) => {
|
|
20007
21123
|
currentSuggestions = suggestions ?? [];
|
|
21124
|
+
setSuggestions(suggestions ?? []);
|
|
21125
|
+
},
|
|
21126
|
+
getSuggestions: () => {
|
|
21127
|
+
const shared = getSuggestions();
|
|
21128
|
+
return shared.length > 0 ? shared : currentSuggestions;
|
|
20008
21129
|
},
|
|
20009
|
-
getSuggestions: () => currentSuggestions,
|
|
20010
21130
|
autoProceedDelayMs: config.autonomy?.autoProceedDelayMs ?? 45e3,
|
|
20011
21131
|
autoProceedMaxIterations: config.autonomy?.autoProceedMaxIterations ?? 50,
|
|
20012
21132
|
onValidateAutoProceed: async (suggestion, lastOutput) => {
|
|
@@ -20062,7 +21182,10 @@ Reply YES to auto-proceed, NO to wait for human input.`
|
|
|
20062
21182
|
return () => stageListeners.delete(fn);
|
|
20063
21183
|
},
|
|
20064
21184
|
skillLoader: config.features.skills ? skillLoader : void 0,
|
|
20065
|
-
modeId
|
|
21185
|
+
modeId,
|
|
21186
|
+
sessionStore,
|
|
21187
|
+
memoryStore,
|
|
21188
|
+
modeStore
|
|
20066
21189
|
});
|
|
20067
21190
|
}
|
|
20068
21191
|
var isMain = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/")}` || process.argv[1]?.endsWith("/cli/dist/index.js") || process.argv[1]?.endsWith("\\cli\\dist\\index.js");
|