@wrongstack/cli 0.4.0 → 0.5.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 +1638 -125
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import * as path17 from 'path';
|
|
3
|
+
import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
|
|
3
4
|
import * as crypto from 'crypto';
|
|
4
5
|
import { randomUUID } from 'crypto';
|
|
5
6
|
import * as fs14 from 'fs/promises';
|
|
7
|
+
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
6
8
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
7
9
|
import { writeFileSync } from 'fs';
|
|
8
10
|
import { createRequire } from 'module';
|
|
9
|
-
import * as path14 from 'path';
|
|
10
11
|
import { MCPRegistry } from '@wrongstack/mcp';
|
|
11
12
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
12
13
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
13
14
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
14
|
-
import * as
|
|
15
|
+
import * as os4 from 'os';
|
|
15
16
|
import * as readline from 'readline';
|
|
17
|
+
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
16
18
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
17
19
|
|
|
18
20
|
var __defProp = Object.defineProperty;
|
|
21
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
19
22
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
23
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
20
24
|
var __esm = (fn, res) => function __init() {
|
|
21
25
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
22
26
|
};
|
|
@@ -24,6 +28,835 @@ var __export = (target, all) => {
|
|
|
24
28
|
for (var name in all)
|
|
25
29
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
30
|
};
|
|
31
|
+
var __copyProps = (to, from, except, desc) => {
|
|
32
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
33
|
+
for (let key of __getOwnPropNames(from))
|
|
34
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
35
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
36
|
+
}
|
|
37
|
+
return to;
|
|
38
|
+
};
|
|
39
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
40
|
+
|
|
41
|
+
// src/slash-commands/sdd.ts
|
|
42
|
+
var sdd_exports = {};
|
|
43
|
+
__export(sdd_exports, {
|
|
44
|
+
autoDetectTaskCompletion: () => autoDetectTaskCompletion,
|
|
45
|
+
buildSddCommand: () => buildSddCommand,
|
|
46
|
+
getActiveBuilder: () => getActiveBuilder,
|
|
47
|
+
getActiveSDDContext: () => getActiveSDDContext,
|
|
48
|
+
getActiveSDDPhase: () => getActiveSDDPhase,
|
|
49
|
+
getTaskListText: () => getTaskListText,
|
|
50
|
+
getTaskProgress: () => getTaskProgress,
|
|
51
|
+
markTaskCompleted: () => markTaskCompleted,
|
|
52
|
+
trySaveImplementationPlan: () => trySaveImplementationPlan,
|
|
53
|
+
trySaveSpecFromAIOutput: () => trySaveSpecFromAIOutput,
|
|
54
|
+
trySaveTasksFromAIOutput: () => trySaveTasksFromAIOutput
|
|
55
|
+
});
|
|
56
|
+
function getActiveSDDContext() {
|
|
57
|
+
if (!activeBuilder) return null;
|
|
58
|
+
const session = activeBuilder.getSession();
|
|
59
|
+
if (session.phase === "done") return null;
|
|
60
|
+
return activeBuilder.getAIPrompt();
|
|
61
|
+
}
|
|
62
|
+
function getActiveSDDPhase() {
|
|
63
|
+
if (!activeBuilder) return null;
|
|
64
|
+
return activeBuilder.getPhase();
|
|
65
|
+
}
|
|
66
|
+
async function trySaveSpecFromAIOutput(aiOutput) {
|
|
67
|
+
if (!activeBuilder) return false;
|
|
68
|
+
const spec = activeBuilder.tryParseSpecFromOutput(aiOutput);
|
|
69
|
+
if (!spec) return false;
|
|
70
|
+
activeBuilder.setSpec(spec);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
async function trySaveTasksFromAIOutput(aiOutput) {
|
|
74
|
+
if (!activeBuilder) return false;
|
|
75
|
+
const session = activeBuilder.getSession();
|
|
76
|
+
if (!session.spec) return false;
|
|
77
|
+
const json = activeBuilder.extractJSONArray(aiOutput);
|
|
78
|
+
if (!json) return false;
|
|
79
|
+
let tasks;
|
|
80
|
+
try {
|
|
81
|
+
tasks = JSON.parse(json);
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(tasks) || tasks.length === 0) return false;
|
|
86
|
+
const validTasks = tasks.filter((t) => t && typeof t === "object" && typeof t.title === "string" && t.title.length > 0);
|
|
87
|
+
if (validTasks.length === 0) return false;
|
|
88
|
+
const store = new DefaultTaskStore();
|
|
89
|
+
const tracker = new TaskTracker({ store });
|
|
90
|
+
const graph = await tracker.createGraph(session.spec.id, session.spec.title);
|
|
91
|
+
for (const task of validTasks) {
|
|
92
|
+
const title = String(task.title);
|
|
93
|
+
const description = String(task.description ?? "");
|
|
94
|
+
const type = ["feature", "bugfix", "refactor", "docs", "test", "chore"].includes(String(task.type)) ? String(task.type) : "feature";
|
|
95
|
+
const priority = ["critical", "high", "medium", "low"].includes(String(task.priority)) ? String(task.priority) : "medium";
|
|
96
|
+
const estimateHours = Number(task.estimateHours) || 2;
|
|
97
|
+
const tags = Array.isArray(task.tags) ? task.tags.map(String) : [];
|
|
98
|
+
tracker.addNode({
|
|
99
|
+
title,
|
|
100
|
+
description,
|
|
101
|
+
type,
|
|
102
|
+
priority,
|
|
103
|
+
status: "pending",
|
|
104
|
+
estimateHours,
|
|
105
|
+
tags
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
activeTaskStore = store;
|
|
109
|
+
activeTaskTracker = tracker;
|
|
110
|
+
activeTaskGraphId = graph.id;
|
|
111
|
+
activeBuilder.setTaskGraphId(graph.id);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
function getTaskProgress() {
|
|
115
|
+
if (!activeTaskTracker) return null;
|
|
116
|
+
const progress = activeTaskTracker.getProgress();
|
|
117
|
+
return {
|
|
118
|
+
total: progress.total,
|
|
119
|
+
completed: progress.completed,
|
|
120
|
+
pending: progress.pending,
|
|
121
|
+
percent: progress.percentComplete
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function getTaskListText() {
|
|
125
|
+
if (!activeTaskTracker) return null;
|
|
126
|
+
const nodes = activeTaskTracker.getAllNodes();
|
|
127
|
+
if (nodes.length === 0) return null;
|
|
128
|
+
const lines = nodes.map((n, i) => {
|
|
129
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : "\u23F3";
|
|
130
|
+
return `${i + 1}. ${status} [${n.priority}] ${n.title}`;
|
|
131
|
+
});
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
function markTaskCompleted(taskTitle) {
|
|
135
|
+
if (!activeTaskTracker) return false;
|
|
136
|
+
const nodes = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
137
|
+
const match = nodes.find(
|
|
138
|
+
(n) => n.title.toLowerCase().includes(taskTitle.toLowerCase()) || taskTitle.toLowerCase().includes(n.title.toLowerCase())
|
|
139
|
+
);
|
|
140
|
+
if (!match) return false;
|
|
141
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
function autoDetectTaskCompletion(aiOutput) {
|
|
145
|
+
if (!activeTaskTracker) return 0;
|
|
146
|
+
const pending = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
147
|
+
if (pending.length === 0) return 0;
|
|
148
|
+
let completed = 0;
|
|
149
|
+
const lines = aiOutput.split("\n");
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
const trimmed = line.trim();
|
|
152
|
+
const sddDoneMatch = trimmed.match(/\/sdd\s+done\s+(.+)/i);
|
|
153
|
+
if (sddDoneMatch?.[1]) {
|
|
154
|
+
const target = sddDoneMatch[1].trim();
|
|
155
|
+
const num = Number(target);
|
|
156
|
+
if (!Number.isNaN(num) && num >= 1 && num <= pending.length) {
|
|
157
|
+
const node = pending[num - 1];
|
|
158
|
+
if (node && node.status !== "completed") {
|
|
159
|
+
activeTaskTracker.updateNodeStatus(node.id, "completed");
|
|
160
|
+
completed++;
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
const match = pending.find(
|
|
164
|
+
(n) => n.title.toLowerCase().includes(target.toLowerCase()) || target.toLowerCase().includes(n.title.toLowerCase())
|
|
165
|
+
);
|
|
166
|
+
if (match && match.status !== "completed") {
|
|
167
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
168
|
+
completed++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const checkmarkMatch = trimmed.match(/^✅\s*(?:Task:\s*)?(.+)/i);
|
|
174
|
+
if (checkmarkMatch?.[1]) {
|
|
175
|
+
const title = checkmarkMatch[1].trim();
|
|
176
|
+
const match = pending.find(
|
|
177
|
+
(n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
|
|
178
|
+
);
|
|
179
|
+
if (match && match.status !== "completed") {
|
|
180
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
181
|
+
completed++;
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const taskNumMatch = trimmed.match(/Task\s+(\d+)\s*[:]\s*(?:complete|done|finished)/i);
|
|
186
|
+
if (taskNumMatch?.[1]) {
|
|
187
|
+
const num = Number(taskNumMatch[1]);
|
|
188
|
+
if (num >= 1 && num <= pending.length) {
|
|
189
|
+
const node = pending[num - 1];
|
|
190
|
+
if (node && node.status !== "completed") {
|
|
191
|
+
activeTaskTracker.updateNodeStatus(node.id, "completed");
|
|
192
|
+
completed++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const completedMatch = trimmed.match(/^(?:Completed|Done|Finished)\s*[:]\s*(.+)/i);
|
|
198
|
+
if (completedMatch?.[1]) {
|
|
199
|
+
const title = completedMatch[1].trim();
|
|
200
|
+
const match = pending.find(
|
|
201
|
+
(n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
|
|
202
|
+
);
|
|
203
|
+
if (match && match.status !== "completed") {
|
|
204
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
205
|
+
completed++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return completed;
|
|
210
|
+
}
|
|
211
|
+
function trySaveImplementationPlan(aiOutput) {
|
|
212
|
+
if (!activeBuilder) return false;
|
|
213
|
+
const session = activeBuilder.getSession();
|
|
214
|
+
if (session.phase !== "implementation") return false;
|
|
215
|
+
const jsonMatch = aiOutput.match(/```json\s*\[/);
|
|
216
|
+
if (jsonMatch?.index && jsonMatch.index > 0) {
|
|
217
|
+
const plan = aiOutput.substring(0, jsonMatch.index).trim();
|
|
218
|
+
if (plan.length > 50) {
|
|
219
|
+
activeBuilder.setImplementation(plan);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (aiOutput.length > 100 && !aiOutput.includes("```json")) {
|
|
224
|
+
activeBuilder.setImplementation(aiOutput.trim());
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function getActiveBuilder() {
|
|
230
|
+
return activeBuilder;
|
|
231
|
+
}
|
|
232
|
+
function buildSddCommand(opts) {
|
|
233
|
+
return {
|
|
234
|
+
name: "sdd",
|
|
235
|
+
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
236
|
+
async run(args) {
|
|
237
|
+
const ctx = opts.context;
|
|
238
|
+
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
239
|
+
const specsDir = path17.join(projectRoot, ".wrongstack", "specs");
|
|
240
|
+
const graphsDir = path17.join(projectRoot, ".wrongstack", "task-graphs");
|
|
241
|
+
const specStore = new SpecStore({ baseDir: specsDir });
|
|
242
|
+
new TaskGraphStore({ baseDir: graphsDir });
|
|
243
|
+
const versioning = new SpecVersioning();
|
|
244
|
+
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
245
|
+
const restJoined = rest.join(" ").trim();
|
|
246
|
+
switch (verb) {
|
|
247
|
+
case "":
|
|
248
|
+
case "help":
|
|
249
|
+
return { message: sddHelp() };
|
|
250
|
+
// ── AI-Driven Spec Session ─────────────────────────────────────────
|
|
251
|
+
case "new":
|
|
252
|
+
case "create": {
|
|
253
|
+
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
254
|
+
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
255
|
+
if (!activeBuilder && !forceFlag) {
|
|
256
|
+
const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
257
|
+
try {
|
|
258
|
+
const fsp = await import('fs/promises');
|
|
259
|
+
await fsp.access(sessionPath);
|
|
260
|
+
const projectContext2 = await gatherProjectContext(projectRoot);
|
|
261
|
+
const tempBuilder = new AISpecBuilder({
|
|
262
|
+
store: specStore,
|
|
263
|
+
projectContext: projectContext2,
|
|
264
|
+
sessionPath
|
|
265
|
+
});
|
|
266
|
+
const loaded = await tempBuilder.loadSession();
|
|
267
|
+
if (loaded) {
|
|
268
|
+
const existing = tempBuilder.getSession();
|
|
269
|
+
if (existing.phase !== "done") {
|
|
270
|
+
return {
|
|
271
|
+
message: [
|
|
272
|
+
`An existing SDD session was found:`,
|
|
273
|
+
` Feature: "${existing.title}"`,
|
|
274
|
+
` Phase: ${existing.phase}`,
|
|
275
|
+
` Questions: ${existing.questionCount}`,
|
|
276
|
+
"",
|
|
277
|
+
"Use /sdd resume to continue, or /sdd new --force to start fresh."
|
|
278
|
+
].join("\n")
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
activeTaskStore = null;
|
|
286
|
+
activeTaskTracker = null;
|
|
287
|
+
activeTaskGraphId = null;
|
|
288
|
+
const projectContext = await gatherProjectContext(projectRoot);
|
|
289
|
+
activeBuilder = new AISpecBuilder({
|
|
290
|
+
store: specStore,
|
|
291
|
+
projectContext,
|
|
292
|
+
minQuestions: 2,
|
|
293
|
+
maxQuestions: 10,
|
|
294
|
+
sessionPath: path17.join(projectRoot, ".wrongstack", "sdd-session.json")
|
|
295
|
+
});
|
|
296
|
+
activeBuilder.startSession(title);
|
|
297
|
+
const aiPrompt = activeBuilder.getAIPrompt();
|
|
298
|
+
return {
|
|
299
|
+
message: [
|
|
300
|
+
`\u2554\u2550\u2550\u2550 SDD: AI Spec Builder \u2550\u2550\u2550\u2557`,
|
|
301
|
+
"",
|
|
302
|
+
`Feature: "${title}"`,
|
|
303
|
+
"",
|
|
304
|
+
"The AI will now ask you contextual questions.",
|
|
305
|
+
"Answer naturally \u2014 it will generate the spec when ready.",
|
|
306
|
+
"",
|
|
307
|
+
"Commands: /sdd approve \xB7 /sdd status \xB7 /sdd cancel"
|
|
308
|
+
].join("\n"),
|
|
309
|
+
runText: `[SDD SESSION ACTIVE]
|
|
310
|
+
${aiPrompt}
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
User message:
|
|
314
|
+
Start the specification interview for "${title}". Ask your first contextual question.`
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
// ── Phase Transitions ──────────────────────────────────────────────
|
|
318
|
+
case "approve":
|
|
319
|
+
case "ok":
|
|
320
|
+
case "confirm": {
|
|
321
|
+
if (!activeBuilder) {
|
|
322
|
+
return {
|
|
323
|
+
message: "No active SDD session. Use /sdd new to start one."
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const phase = activeBuilder.getSession().phase;
|
|
327
|
+
if (phase === "questioning") {
|
|
328
|
+
const sddCtx = activeBuilder.getAIPrompt();
|
|
329
|
+
return {
|
|
330
|
+
message: "No spec generated yet. Generating now...",
|
|
331
|
+
runText: `[SDD SESSION ACTIVE]
|
|
332
|
+
${sddCtx}
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
User message:
|
|
336
|
+
Generate the complete specification now based on the conversation so far.`
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (phase === "spec_review") {
|
|
340
|
+
const spec = activeBuilder.getSession().spec;
|
|
341
|
+
if (!spec) {
|
|
342
|
+
return { message: "No spec to approve." };
|
|
343
|
+
}
|
|
344
|
+
await activeBuilder.saveSpec();
|
|
345
|
+
versioning.recordVersion(spec, "Initial spec approved");
|
|
346
|
+
activeBuilder.approve();
|
|
347
|
+
const implPrompt = activeBuilder.getAIPrompt();
|
|
348
|
+
return {
|
|
349
|
+
message: [
|
|
350
|
+
`\u2705 Spec "${spec.title}" approved and saved!`,
|
|
351
|
+
`ID: ${spec.id}`,
|
|
352
|
+
`Requirements: ${spec.requirements.length}`,
|
|
353
|
+
"",
|
|
354
|
+
"The AI will now generate an implementation plan and tasks."
|
|
355
|
+
].join("\n"),
|
|
356
|
+
runText: `[SDD SESSION ACTIVE]
|
|
357
|
+
${implPrompt}
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
User message:
|
|
361
|
+
Generate the implementation plan and tasks for the approved spec.`
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (phase === "task_review") {
|
|
365
|
+
activeBuilder.approve();
|
|
366
|
+
const execPrompt = activeBuilder.getAIPrompt();
|
|
367
|
+
return {
|
|
368
|
+
message: "\u2705 Tasks approved! The AI will now execute them one by one.",
|
|
369
|
+
runText: `[SDD SESSION ACTIVE]
|
|
370
|
+
${execPrompt}
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
User message:
|
|
374
|
+
Start executing the tasks one by one.`
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
message: `Current phase is "${phase}". Use /sdd status to see details.`
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// ── Task Execution ─────────────────────────────────────────────────
|
|
382
|
+
case "execute":
|
|
383
|
+
case "run": {
|
|
384
|
+
if (!activeBuilder) {
|
|
385
|
+
return {
|
|
386
|
+
message: "No active SDD session. Use /sdd new to start one."
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const session = activeBuilder.getSession();
|
|
390
|
+
if (session.phase !== "executing" && session.phase !== "task_review") {
|
|
391
|
+
return {
|
|
392
|
+
message: `Cannot execute in phase "${session.phase}". Use /sdd approve first.`
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const execPrompt = activeBuilder.getAIPrompt();
|
|
396
|
+
return {
|
|
397
|
+
message: "\u26A1 Starting task execution. The AI will execute tasks one by one.",
|
|
398
|
+
runText: `[SDD SESSION ACTIVE]
|
|
399
|
+
${execPrompt}
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
User message:
|
|
403
|
+
Start executing the tasks one by one.`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
case "plan":
|
|
407
|
+
case "impl": {
|
|
408
|
+
if (!activeBuilder) {
|
|
409
|
+
return { message: "No active SDD session. Use /sdd new to start one." };
|
|
410
|
+
}
|
|
411
|
+
const session = activeBuilder.getSession();
|
|
412
|
+
if (!session.implementation) {
|
|
413
|
+
return {
|
|
414
|
+
message: session.phase === "implementation" ? "No implementation plan yet. The AI will generate it after /sdd approve." : "No implementation plan in this session."
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
message: [
|
|
419
|
+
"\u2550\u2550\u2550 Implementation Plan \u2550\u2550\u2550",
|
|
420
|
+
"",
|
|
421
|
+
session.implementation
|
|
422
|
+
].join("\n")
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
case "spec": {
|
|
426
|
+
if (!activeBuilder) {
|
|
427
|
+
return { message: "No active SDD session. Use /sdd new to start one." };
|
|
428
|
+
}
|
|
429
|
+
const session = activeBuilder.getSession();
|
|
430
|
+
if (!session.spec) {
|
|
431
|
+
return {
|
|
432
|
+
message: session.phase === "questioning" ? "No spec generated yet. Keep answering the AI's questions." : "No spec in this session."
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
const spec = session.spec;
|
|
436
|
+
const lines = [
|
|
437
|
+
`\u2550\u2550\u2550 Current Spec \u2550\u2550\u2550`,
|
|
438
|
+
"",
|
|
439
|
+
`Title: ${spec.title}`,
|
|
440
|
+
`Version: ${spec.version}`,
|
|
441
|
+
`Status: ${spec.status}`,
|
|
442
|
+
"",
|
|
443
|
+
"## Overview",
|
|
444
|
+
spec.overview
|
|
445
|
+
];
|
|
446
|
+
if (spec.requirements.length > 0) {
|
|
447
|
+
lines.push("", `## Requirements (${spec.requirements.length})`);
|
|
448
|
+
for (const r of spec.requirements) {
|
|
449
|
+
const ac = r.acceptanceCriteria.length > 0 ? ` \u2192 ${r.acceptanceCriteria.join(", ")}` : "";
|
|
450
|
+
lines.push(` [${r.priority}] ${r.description}${ac}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return { message: lines.join("\n") };
|
|
454
|
+
}
|
|
455
|
+
case "tasks":
|
|
456
|
+
case "task": {
|
|
457
|
+
if (!activeTaskTracker) {
|
|
458
|
+
return { message: "No tasks generated yet. Use /sdd new to start." };
|
|
459
|
+
}
|
|
460
|
+
const nodes = activeTaskTracker.getAllNodes();
|
|
461
|
+
if (nodes.length === 0) {
|
|
462
|
+
return { message: "No tasks in the current graph." };
|
|
463
|
+
}
|
|
464
|
+
const progress = activeTaskTracker.getProgress();
|
|
465
|
+
const lines = [
|
|
466
|
+
`\u2550\u2550\u2550 Task List (${progress.completed}/${progress.total} done) \u2550\u2550\u2550`,
|
|
467
|
+
""
|
|
468
|
+
];
|
|
469
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
470
|
+
const n = nodes[i];
|
|
471
|
+
const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : "\u23F3";
|
|
472
|
+
lines.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
|
|
473
|
+
if (n.description) {
|
|
474
|
+
lines.push(` ${n.description.split("\n")[0]}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return { message: lines.join("\n") };
|
|
478
|
+
}
|
|
479
|
+
case "done":
|
|
480
|
+
case "complete": {
|
|
481
|
+
if (!activeTaskTracker) {
|
|
482
|
+
return { message: "No tasks to complete." };
|
|
483
|
+
}
|
|
484
|
+
if (!restJoined) {
|
|
485
|
+
return { message: "Usage: /sdd done <task title or number>" };
|
|
486
|
+
}
|
|
487
|
+
const nodes = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
|
|
488
|
+
const num = Number(restJoined);
|
|
489
|
+
let matched = false;
|
|
490
|
+
if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {
|
|
491
|
+
const node = nodes[num - 1];
|
|
492
|
+
if (node) {
|
|
493
|
+
activeTaskTracker.updateNodeStatus(node.id, "completed");
|
|
494
|
+
matched = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (!matched) {
|
|
498
|
+
const match = nodes.find(
|
|
499
|
+
(n) => n.title.toLowerCase().includes(restJoined.toLowerCase()) || restJoined.toLowerCase().includes(n.title.toLowerCase())
|
|
500
|
+
);
|
|
501
|
+
if (match) {
|
|
502
|
+
activeTaskTracker.updateNodeStatus(match.id, "completed");
|
|
503
|
+
matched = true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!matched) {
|
|
507
|
+
return { message: `No pending task matching "${restJoined}".` };
|
|
508
|
+
}
|
|
509
|
+
const remaining = activeTaskTracker.getProgress();
|
|
510
|
+
return {
|
|
511
|
+
message: `\u2705 Task completed! ${remaining.completed}/${remaining.total} done (${remaining.percentComplete}%)`
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// ── Session Management ─────────────────────────────────────────────
|
|
515
|
+
case "status": {
|
|
516
|
+
if (!activeBuilder) {
|
|
517
|
+
return { message: "No active SDD session." };
|
|
518
|
+
}
|
|
519
|
+
const session = activeBuilder.getSession();
|
|
520
|
+
const phaseEmoji = {
|
|
521
|
+
questioning: "\u2753",
|
|
522
|
+
spec_review: "\u{1F4CB}",
|
|
523
|
+
implementation: "\u{1F3D7}\uFE0F",
|
|
524
|
+
task_review: "\u{1F4DD}",
|
|
525
|
+
executing: "\u26A1",
|
|
526
|
+
done: "\u2705"
|
|
527
|
+
};
|
|
528
|
+
const progress = getTaskProgress();
|
|
529
|
+
const lines = [
|
|
530
|
+
"\u2550\u2550\u2550 SDD Session Status \u2550\u2550\u2550",
|
|
531
|
+
"",
|
|
532
|
+
`Feature: "${session.title}"`,
|
|
533
|
+
`Phase: ${phaseEmoji[session.phase]} ${session.phase}`,
|
|
534
|
+
`Questions asked: ${session.questionCount}`
|
|
535
|
+
];
|
|
536
|
+
if (session.spec) {
|
|
537
|
+
lines.push(`Spec: ${session.spec.title} (${session.spec.requirements.length} requirements)`);
|
|
538
|
+
lines.push(` Requirements: ${session.spec.requirements.map((r) => r.description).join(", ")}`);
|
|
539
|
+
}
|
|
540
|
+
if (session.implementation) {
|
|
541
|
+
const planPreview = session.implementation.split("\n").slice(0, 3).join(" ");
|
|
542
|
+
lines.push(`Implementation: ${planPreview}${session.implementation.length > 100 ? "..." : ""}`);
|
|
543
|
+
}
|
|
544
|
+
if (progress && progress.total > 0) {
|
|
545
|
+
lines.push(`Tasks: ${progress.completed}/${progress.total} (${progress.percent}%)`);
|
|
546
|
+
}
|
|
547
|
+
lines.push("", `Session ID: ${session.id}`);
|
|
548
|
+
lines.push("Commands: /sdd plan \xB7 /sdd tasks \xB7 /sdd approve \xB7 /sdd cancel");
|
|
549
|
+
return {
|
|
550
|
+
message: lines.join("\n")
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
case "cancel": {
|
|
554
|
+
const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
555
|
+
let deletedFromDisk = false;
|
|
556
|
+
try {
|
|
557
|
+
const fsp = await import('fs/promises');
|
|
558
|
+
await fsp.unlink(sessionPath);
|
|
559
|
+
deletedFromDisk = true;
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
if (activeBuilder) {
|
|
563
|
+
const title = activeBuilder.getSession().title;
|
|
564
|
+
await activeBuilder.deleteSession();
|
|
565
|
+
activeBuilder = null;
|
|
566
|
+
activeTaskStore = null;
|
|
567
|
+
activeTaskTracker = null;
|
|
568
|
+
activeTaskGraphId = null;
|
|
569
|
+
return { message: `SDD session for "${title}" cancelled.` };
|
|
570
|
+
}
|
|
571
|
+
if (deletedFromDisk) {
|
|
572
|
+
return { message: "Stale SDD session file deleted. You can now use /sdd new." };
|
|
573
|
+
}
|
|
574
|
+
return { message: "No active SDD session." };
|
|
575
|
+
}
|
|
576
|
+
case "resume": {
|
|
577
|
+
if (activeBuilder) {
|
|
578
|
+
return { message: "An SDD session is already active. Use /sdd cancel first." };
|
|
579
|
+
}
|
|
580
|
+
const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
581
|
+
const projectContext = await gatherProjectContext(projectRoot);
|
|
582
|
+
activeBuilder = new AISpecBuilder({
|
|
583
|
+
store: specStore,
|
|
584
|
+
projectContext,
|
|
585
|
+
minQuestions: 2,
|
|
586
|
+
maxQuestions: 10,
|
|
587
|
+
sessionPath
|
|
588
|
+
});
|
|
589
|
+
const loaded = await activeBuilder.loadSession();
|
|
590
|
+
if (!loaded) {
|
|
591
|
+
activeBuilder = null;
|
|
592
|
+
return { message: "No saved SDD session found. Use /sdd new to start one." };
|
|
593
|
+
}
|
|
594
|
+
const session = activeBuilder.getSession();
|
|
595
|
+
let taskCount = 0;
|
|
596
|
+
let completedCount = 0;
|
|
597
|
+
const taskGraphId = activeBuilder.getTaskGraphId();
|
|
598
|
+
if (taskGraphId) {
|
|
599
|
+
try {
|
|
600
|
+
const store = new DefaultTaskStore();
|
|
601
|
+
const tracker = new TaskTracker({ store });
|
|
602
|
+
const graph = await tracker.loadGraph(taskGraphId);
|
|
603
|
+
if (graph) {
|
|
604
|
+
activeTaskStore = store;
|
|
605
|
+
activeTaskTracker = tracker;
|
|
606
|
+
activeTaskGraphId = taskGraphId;
|
|
607
|
+
const progress = tracker.getProgress();
|
|
608
|
+
taskCount = progress.total;
|
|
609
|
+
completedCount = progress.completed;
|
|
610
|
+
}
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const resumePrompt = activeBuilder.getAIPrompt();
|
|
615
|
+
return {
|
|
616
|
+
message: [
|
|
617
|
+
`\u2554\u2550\u2550\u2550 SDD Session Resumed \u2550\u2550\u2550\u2557`,
|
|
618
|
+
"",
|
|
619
|
+
`Feature: "${session.title}"`,
|
|
620
|
+
`Phase: ${session.phase}`,
|
|
621
|
+
`Questions asked: ${session.questionCount}`,
|
|
622
|
+
session.spec ? `Spec: ${session.spec.title}` : "",
|
|
623
|
+
taskCount > 0 ? `Tasks: ${completedCount}/${taskCount} completed` : "",
|
|
624
|
+
"",
|
|
625
|
+
"The AI will continue from where you left off."
|
|
626
|
+
].filter(Boolean).join("\n"),
|
|
627
|
+
runText: `[SDD SESSION ACTIVE]
|
|
628
|
+
${resumePrompt}
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
User message:
|
|
632
|
+
Continue from where we left off. Check the session status and proceed.`
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
// ── Spec Browsing ──────────────────────────────────────────────────
|
|
636
|
+
case "list":
|
|
637
|
+
case "ls": {
|
|
638
|
+
const entries = await specStore.list();
|
|
639
|
+
if (entries.length === 0) {
|
|
640
|
+
return { message: "No specs saved. Use /sdd new to create one." };
|
|
641
|
+
}
|
|
642
|
+
const lines = entries.map((e, i) => {
|
|
643
|
+
const status = e.status === "draft" ? "\u{1F4DD}" : e.status === "approved" ? "\u2705" : "\u{1F4CB}";
|
|
644
|
+
return `${i + 1}. ${status} ${e.title} (${e.version}) \u2014 ${e.id.slice(0, 8)}...`;
|
|
645
|
+
});
|
|
646
|
+
return { message: `Saved Specs:
|
|
647
|
+
${lines.join("\n")}` };
|
|
648
|
+
}
|
|
649
|
+
case "show":
|
|
650
|
+
case "view": {
|
|
651
|
+
const spec = await findSpec(specStore, restJoined);
|
|
652
|
+
if (!spec) return { message: `Spec "${restJoined}" not found.` };
|
|
653
|
+
const parser = new SpecParser();
|
|
654
|
+
const analysis = parser.analyze(spec);
|
|
655
|
+
return {
|
|
656
|
+
message: [
|
|
657
|
+
`# ${spec.title}`,
|
|
658
|
+
`Version: ${spec.version} | Status: ${spec.status}`,
|
|
659
|
+
"",
|
|
660
|
+
"## Overview",
|
|
661
|
+
spec.overview,
|
|
662
|
+
"",
|
|
663
|
+
`## Requirements (${spec.requirements.length})`,
|
|
664
|
+
...spec.requirements.map((r) => {
|
|
665
|
+
const tags = `[${r.type}][${r.priority}]`;
|
|
666
|
+
const ac = r.acceptanceCriteria.length > 0 ? `
|
|
667
|
+
AC: ${r.acceptanceCriteria.join(", ")}` : "";
|
|
668
|
+
return `- ${tags} ${r.description}${ac}`;
|
|
669
|
+
}),
|
|
670
|
+
"",
|
|
671
|
+
renderSpecAnalysis(spec, {
|
|
672
|
+
completeness: analysis.completeness,
|
|
673
|
+
gaps: analysis.gaps,
|
|
674
|
+
risks: analysis.risks.map((r) => r.risk),
|
|
675
|
+
suggestions: analysis.suggestions
|
|
676
|
+
})
|
|
677
|
+
].join("\n")
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
case "templates": {
|
|
681
|
+
const templates = listTemplates();
|
|
682
|
+
const lines = templates.map(
|
|
683
|
+
(t) => ` ${t.id}: ${t.name} \u2014 ${t.description}`
|
|
684
|
+
);
|
|
685
|
+
return {
|
|
686
|
+
message: `Available Templates:
|
|
687
|
+
${lines.join("\n")}`
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
case "from": {
|
|
691
|
+
const templateId = restJoined || "feature";
|
|
692
|
+
const template = getTemplate(templateId);
|
|
693
|
+
if (!template) {
|
|
694
|
+
return {
|
|
695
|
+
message: `Template "${templateId}" not found.
|
|
696
|
+
Available: ${listTemplates().map((t) => t.id).join(", ")}`
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const skeleton = templateToMarkdown(template, "New Specification");
|
|
700
|
+
const spec = await specStore.createDraft("New Specification");
|
|
701
|
+
await specStore.update(spec.id, { sections: [] });
|
|
702
|
+
return {
|
|
703
|
+
message: [
|
|
704
|
+
`Created draft spec from template "${template.name}".`,
|
|
705
|
+
`ID: ${spec.id}`,
|
|
706
|
+
"",
|
|
707
|
+
"Edit the spec through the AI conversation or /sdd show to review.",
|
|
708
|
+
"",
|
|
709
|
+
skeleton
|
|
710
|
+
].join("\n")
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
case "version":
|
|
714
|
+
case "history": {
|
|
715
|
+
const spec = await findSpec(specStore, restJoined);
|
|
716
|
+
if (!spec)
|
|
717
|
+
return { message: `Spec "${restJoined}" not found.` };
|
|
718
|
+
const history = versioning.getHistory(spec.id);
|
|
719
|
+
if (history.length === 0) {
|
|
720
|
+
return {
|
|
721
|
+
message: `No version history for "${spec.title}".`
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
const lines = history.map(
|
|
725
|
+
(v, i) => `${i + 1}. v${v.version} \u2014 ${new Date(v.timestamp).toISOString()}${v.changeDescription ? ` (${v.changeDescription})` : ""}`
|
|
726
|
+
);
|
|
727
|
+
return {
|
|
728
|
+
message: `Version History for "${spec.title}":
|
|
729
|
+
${lines.join("\n")}`
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
default:
|
|
733
|
+
return {
|
|
734
|
+
message: `Unknown command "${verb}".
|
|
735
|
+
|
|
736
|
+
${sddHelp()}`
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function sddHelp() {
|
|
743
|
+
return [
|
|
744
|
+
"",
|
|
745
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
746
|
+
"\u2551 \u{1F680} SDD \u2014 AI-Driven Spec Builder \u2551",
|
|
747
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
748
|
+
"",
|
|
749
|
+
" \u250C\u2500 \u{1F195} Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
750
|
+
" \u2502 /sdd new [title] Start a new spec session \u2502",
|
|
751
|
+
" \u2502 /sdd new --force Start fresh (skip resume check) \u2502",
|
|
752
|
+
" \u2502 /sdd resume Resume a saved session \u2502",
|
|
753
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
754
|
+
"",
|
|
755
|
+
" \u250C\u2500 \u{1F504} Flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
756
|
+
" \u2502 /sdd approve Approve current phase \u2502",
|
|
757
|
+
" \u2502 /sdd spec Show current session's spec \u2502",
|
|
758
|
+
" \u2502 /sdd plan Show implementation plan \u2502",
|
|
759
|
+
" \u2502 /sdd execute Execute generated tasks \u2502",
|
|
760
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
761
|
+
"",
|
|
762
|
+
" \u250C\u2500 \u{1F4CB} Task Management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
763
|
+
" \u2502 /sdd tasks Show current task list \u2502",
|
|
764
|
+
" \u2502 /sdd done <N> Mark task complete (by # or name) \u2502",
|
|
765
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
766
|
+
"",
|
|
767
|
+
" \u250C\u2500 \u{1F4CA} Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
768
|
+
" \u2502 /sdd status Show session status \u2502",
|
|
769
|
+
" \u2502 /sdd cancel Cancel session \u2502",
|
|
770
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
771
|
+
"",
|
|
772
|
+
" \u250C\u2500 \u{1F4C1} Spec History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
773
|
+
" \u2502 /sdd list List saved specs \u2502",
|
|
774
|
+
" \u2502 /sdd show <id> Show spec details \u2502",
|
|
775
|
+
" \u2502 /sdd templates List available templates \u2502",
|
|
776
|
+
" \u2502 /sdd from <tmpl> Create from template \u2502",
|
|
777
|
+
" \u2502 /sdd version <id> Show version history \u2502",
|
|
778
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
779
|
+
"",
|
|
780
|
+
" \u250C\u2500 \u{1F4A1} Quick Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
781
|
+
" \u2502 \u2502",
|
|
782
|
+
" \u2502 1. /sdd new Auth System \u2502",
|
|
783
|
+
" \u2502 \u2192 AI starts asking questions \u2502",
|
|
784
|
+
" \u2502 \u2502",
|
|
785
|
+
" \u2502 2. Just type your answers naturally \u2502",
|
|
786
|
+
" \u2502 \u2192 AI continues the interview \u2502",
|
|
787
|
+
" \u2502 \u2502",
|
|
788
|
+
" \u2502 3. AI generates spec (auto-detected) \u2502",
|
|
789
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
790
|
+
" \u2502 \u2502",
|
|
791
|
+
" \u2502 3. AI generates implementation + tasks \u2502",
|
|
792
|
+
" \u2502 \u2192 /sdd approve \u2502",
|
|
793
|
+
" \u2502 \u2502",
|
|
794
|
+
" \u2502 4. AI executes tasks one by one \u2502",
|
|
795
|
+
" \u2502 \u2192 /sdd tasks (view progress) \u2502",
|
|
796
|
+
" \u2502 \u2192 /sdd done 1 (manual completion) \u2502",
|
|
797
|
+
" \u2502 \u2502",
|
|
798
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
799
|
+
""
|
|
800
|
+
].join("\n");
|
|
801
|
+
}
|
|
802
|
+
async function gatherProjectContext(projectRoot) {
|
|
803
|
+
const parts = [];
|
|
804
|
+
try {
|
|
805
|
+
const fsp = await import('fs/promises');
|
|
806
|
+
const pkgPath = path17.join(projectRoot, "package.json");
|
|
807
|
+
const pkgRaw = await fsp.readFile(pkgPath, "utf8");
|
|
808
|
+
const pkg = JSON.parse(pkgRaw);
|
|
809
|
+
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
810
|
+
parts.push(`Description: ${String(pkg.description ?? "none")}`);
|
|
811
|
+
if (pkg.dependencies) {
|
|
812
|
+
const deps = Object.keys(pkg.dependencies);
|
|
813
|
+
parts.push(`Dependencies: ${deps.slice(0, 20).join(", ")}${deps.length > 20 ? "..." : ""}`);
|
|
814
|
+
}
|
|
815
|
+
if (pkg.devDependencies) {
|
|
816
|
+
const devDeps = Object.keys(pkg.devDependencies);
|
|
817
|
+
parts.push(`Dev Dependencies: ${devDeps.slice(0, 15).join(", ")}${devDeps.length > 15 ? "..." : ""}`);
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
820
|
+
}
|
|
821
|
+
try {
|
|
822
|
+
const fsp = await import('fs/promises');
|
|
823
|
+
const tsconfigPath = path17.join(projectRoot, "tsconfig.json");
|
|
824
|
+
await fsp.access(tsconfigPath);
|
|
825
|
+
parts.push("Language: TypeScript");
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
const fsp = await import('fs/promises');
|
|
830
|
+
const srcDir = path17.join(projectRoot, "src");
|
|
831
|
+
const entries = await fsp.readdir(srcDir, { withFileTypes: true });
|
|
832
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
833
|
+
if (dirs.length > 0) {
|
|
834
|
+
parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
835
|
+
}
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
838
|
+
return parts.join("\n");
|
|
839
|
+
}
|
|
840
|
+
async function findSpec(store, idOrTitle) {
|
|
841
|
+
if (!idOrTitle) return null;
|
|
842
|
+
const byId = await store.load(idOrTitle);
|
|
843
|
+
if (byId) return byId;
|
|
844
|
+
const all = await store.list();
|
|
845
|
+
const match = all.find(
|
|
846
|
+
(e) => e.id.startsWith(idOrTitle) || e.title.toLowerCase().includes(idOrTitle.toLowerCase())
|
|
847
|
+
);
|
|
848
|
+
if (match) return store.load(match.id);
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
var activeBuilder, activeTaskStore, activeTaskTracker, activeTaskGraphId;
|
|
852
|
+
var init_sdd = __esm({
|
|
853
|
+
"src/slash-commands/sdd.ts"() {
|
|
854
|
+
activeBuilder = null;
|
|
855
|
+
activeTaskStore = null;
|
|
856
|
+
activeTaskTracker = null;
|
|
857
|
+
activeTaskGraphId = null;
|
|
858
|
+
}
|
|
859
|
+
});
|
|
27
860
|
function normalizeKeys(cfg) {
|
|
28
861
|
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
29
862
|
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
@@ -76,9 +909,8 @@ async function runWebUI(opts) {
|
|
|
76
909
|
const clients = /* @__PURE__ */ new Map();
|
|
77
910
|
let abortController = null;
|
|
78
911
|
const authToken = crypto.randomBytes(16).toString("hex");
|
|
79
|
-
const wss = new WebSocketServer({ port, host: "127.0.0.1" });
|
|
912
|
+
const wss = new WebSocketServer({ port, host: "127.0.0.1", maxPayload: 1 * 1024 * 1024 });
|
|
80
913
|
console.log(`[WebUI] WebSocket server starting on ws://localhost:${port}`);
|
|
81
|
-
console.log(`[WebUI] Auth token: ${authToken}`);
|
|
82
914
|
const eventUnsubscribers = [];
|
|
83
915
|
function setupEvents() {
|
|
84
916
|
for (const unsub of eventUnsubscribers) unsub();
|
|
@@ -198,6 +1030,8 @@ async function runWebUI(opts) {
|
|
|
198
1030
|
}
|
|
199
1031
|
} else {
|
|
200
1032
|
if (!tokenOk) {
|
|
1033
|
+
ws.close(4003, "Forbidden: auth token required for non-browser clients");
|
|
1034
|
+
return;
|
|
201
1035
|
}
|
|
202
1036
|
}
|
|
203
1037
|
} catch {
|
|
@@ -557,7 +1391,10 @@ async function runWebUI(opts) {
|
|
|
557
1391
|
} catch {
|
|
558
1392
|
return {};
|
|
559
1393
|
}
|
|
560
|
-
|
|
1394
|
+
if (!parsed.providers) return {};
|
|
1395
|
+
const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
|
|
1396
|
+
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1397
|
+
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
561
1398
|
}
|
|
562
1399
|
async function saveProviders(providers) {
|
|
563
1400
|
if (!opts.globalConfigPath) return;
|
|
@@ -574,7 +1411,10 @@ async function runWebUI(opts) {
|
|
|
574
1411
|
parsed = {};
|
|
575
1412
|
}
|
|
576
1413
|
parsed.providers = providers;
|
|
577
|
-
|
|
1414
|
+
const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
|
|
1415
|
+
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1416
|
+
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
1417
|
+
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
578
1418
|
}
|
|
579
1419
|
function sendResult(ws, success, message) {
|
|
580
1420
|
send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
@@ -723,10 +1563,10 @@ function parseSpawnFlags(input) {
|
|
|
723
1563
|
return { description: rest.trim(), opts };
|
|
724
1564
|
}
|
|
725
1565
|
async function bootConfig(flags) {
|
|
726
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
1566
|
+
const cwd = typeof flags["cwd"] === "string" ? path17.resolve(flags["cwd"]) : process.cwd();
|
|
727
1567
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
728
1568
|
const projectRoot = pathResolver.projectRoot;
|
|
729
|
-
const userHome =
|
|
1569
|
+
const userHome = os4.homedir();
|
|
730
1570
|
const wpaths = resolveWstackPaths({ projectRoot, userHome });
|
|
731
1571
|
await ensureProjectMeta(wpaths, projectRoot);
|
|
732
1572
|
const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
|
|
@@ -790,7 +1630,7 @@ var ReadlineInputReader = class {
|
|
|
790
1630
|
history = [];
|
|
791
1631
|
pending = false;
|
|
792
1632
|
constructor(opts = {}) {
|
|
793
|
-
this.historyFile = opts.historyFile ??
|
|
1633
|
+
this.historyFile = opts.historyFile ?? path17.join(os4.homedir(), ".wrongstack", "history");
|
|
794
1634
|
}
|
|
795
1635
|
async loadHistory() {
|
|
796
1636
|
try {
|
|
@@ -802,7 +1642,7 @@ var ReadlineInputReader = class {
|
|
|
802
1642
|
}
|
|
803
1643
|
async saveHistory() {
|
|
804
1644
|
try {
|
|
805
|
-
await fs14.mkdir(
|
|
1645
|
+
await fs14.mkdir(path17.dirname(this.historyFile), { recursive: true });
|
|
806
1646
|
await fs14.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
807
1647
|
} catch {
|
|
808
1648
|
}
|
|
@@ -1284,10 +2124,10 @@ async function detectPackageManager(root, declared) {
|
|
|
1284
2124
|
const name = declared.split("@")[0];
|
|
1285
2125
|
if (name) return name;
|
|
1286
2126
|
}
|
|
1287
|
-
if (await pathExists(
|
|
1288
|
-
if (await pathExists(
|
|
1289
|
-
if (await pathExists(
|
|
1290
|
-
if (await pathExists(
|
|
2127
|
+
if (await pathExists(path17.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2128
|
+
if (await pathExists(path17.join(root, "bun.lockb"))) return "bun";
|
|
2129
|
+
if (await pathExists(path17.join(root, "bun.lock"))) return "bun";
|
|
2130
|
+
if (await pathExists(path17.join(root, "yarn.lock"))) return "yarn";
|
|
1291
2131
|
return "npm";
|
|
1292
2132
|
}
|
|
1293
2133
|
function hasUsableScript(scripts, name) {
|
|
@@ -1308,7 +2148,7 @@ function parseMakeTargets(makefile) {
|
|
|
1308
2148
|
async function detectProjectFacts(root) {
|
|
1309
2149
|
const facts = { hints: [] };
|
|
1310
2150
|
try {
|
|
1311
|
-
const pkg = JSON.parse(await fs14.readFile(
|
|
2151
|
+
const pkg = JSON.parse(await fs14.readFile(path17.join(root, "package.json"), "utf8"));
|
|
1312
2152
|
const scripts = pkg.scripts ?? {};
|
|
1313
2153
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
1314
2154
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -1322,14 +2162,14 @@ async function detectProjectFacts(root) {
|
|
|
1322
2162
|
} catch {
|
|
1323
2163
|
}
|
|
1324
2164
|
try {
|
|
1325
|
-
if (!await pathExists(
|
|
2165
|
+
if (!await pathExists(path17.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
1326
2166
|
facts.test ??= "pytest";
|
|
1327
2167
|
facts.lint ??= "ruff check .";
|
|
1328
2168
|
facts.hints.push("pyproject.toml");
|
|
1329
2169
|
} catch {
|
|
1330
2170
|
}
|
|
1331
2171
|
try {
|
|
1332
|
-
if (!await pathExists(
|
|
2172
|
+
if (!await pathExists(path17.join(root, "go.mod"))) throw new Error("not go");
|
|
1333
2173
|
facts.build ??= "go build ./...";
|
|
1334
2174
|
facts.test ??= "go test ./...";
|
|
1335
2175
|
facts.run ??= "go run .";
|
|
@@ -1337,7 +2177,7 @@ async function detectProjectFacts(root) {
|
|
|
1337
2177
|
} catch {
|
|
1338
2178
|
}
|
|
1339
2179
|
try {
|
|
1340
|
-
if (!await pathExists(
|
|
2180
|
+
if (!await pathExists(path17.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
1341
2181
|
facts.build ??= "cargo build";
|
|
1342
2182
|
facts.test ??= "cargo test";
|
|
1343
2183
|
facts.lint ??= "cargo clippy";
|
|
@@ -1346,7 +2186,7 @@ async function detectProjectFacts(root) {
|
|
|
1346
2186
|
} catch {
|
|
1347
2187
|
}
|
|
1348
2188
|
try {
|
|
1349
|
-
const makefile = await fs14.readFile(
|
|
2189
|
+
const makefile = await fs14.readFile(path17.join(root, "Makefile"), "utf8");
|
|
1350
2190
|
const targets = parseMakeTargets(makefile);
|
|
1351
2191
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
1352
2192
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -1809,8 +2649,8 @@ function buildInitCommand(opts) {
|
|
|
1809
2649
|
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
1810
2650
|
async run(args, ctx) {
|
|
1811
2651
|
const force = args.trim() === "--force";
|
|
1812
|
-
const dir =
|
|
1813
|
-
const file =
|
|
2652
|
+
const dir = path17.join(ctx.projectRoot, ".wrongstack");
|
|
2653
|
+
const file = path17.join(dir, "AGENTS.md");
|
|
1814
2654
|
try {
|
|
1815
2655
|
await fs14.access(file);
|
|
1816
2656
|
if (!force) {
|
|
@@ -2060,7 +2900,7 @@ function buildExitCommand(opts) {
|
|
|
2060
2900
|
function buildSkillCommand(opts) {
|
|
2061
2901
|
return {
|
|
2062
2902
|
name: "skill",
|
|
2063
|
-
description: "Show skill details or list available skills.",
|
|
2903
|
+
description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
|
|
2064
2904
|
async run(args) {
|
|
2065
2905
|
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
2066
2906
|
if (!args.trim()) {
|
|
@@ -2198,6 +3038,392 @@ ${lines.join("\n")}
|
|
|
2198
3038
|
}
|
|
2199
3039
|
};
|
|
2200
3040
|
}
|
|
3041
|
+
function buildYoloCommand(opts) {
|
|
3042
|
+
return {
|
|
3043
|
+
name: "yolo",
|
|
3044
|
+
description: "Toggle or query YOLO (auto-approve) mode.",
|
|
3045
|
+
help: [
|
|
3046
|
+
"Usage:",
|
|
3047
|
+
" /yolo Show current YOLO status",
|
|
3048
|
+
" /yolo on Enable YOLO mode (auto-approve every tool call)",
|
|
3049
|
+
" /yolo off Disable YOLO mode (restore permission prompts)",
|
|
3050
|
+
" /yolo toggle Toggle YOLO mode",
|
|
3051
|
+
"",
|
|
3052
|
+
"YOLO mode skips all permission prompts and auto-approves every tool call.",
|
|
3053
|
+
"Use with caution \u2014 the agent can execute any tool without asking."
|
|
3054
|
+
].join("\n"),
|
|
3055
|
+
async run(args) {
|
|
3056
|
+
const arg = args.trim().toLowerCase();
|
|
3057
|
+
if (!opts.onYolo) {
|
|
3058
|
+
const msg2 = "YOLO toggle is not available in this session.";
|
|
3059
|
+
opts.renderer.writeWarning(msg2);
|
|
3060
|
+
return { message: msg2 };
|
|
3061
|
+
}
|
|
3062
|
+
if (!arg) {
|
|
3063
|
+
const current = opts.onYolo();
|
|
3064
|
+
const status = current ? `${color.yellow("ON")} ${color.dim("(auto-approving all tool calls)")}` : `${color.green("OFF")} ${color.dim("(permission prompts active)")}`;
|
|
3065
|
+
const msg2 = `YOLO mode: ${status}`;
|
|
3066
|
+
opts.renderer.write(msg2);
|
|
3067
|
+
return { message: msg2 };
|
|
3068
|
+
}
|
|
3069
|
+
let newState;
|
|
3070
|
+
if (arg === "on" || arg === "enable" || arg === "true" || arg === "1") {
|
|
3071
|
+
newState = true;
|
|
3072
|
+
} else if (arg === "off" || arg === "disable" || arg === "false" || arg === "0") {
|
|
3073
|
+
newState = false;
|
|
3074
|
+
} else if (arg === "toggle") {
|
|
3075
|
+
newState = !opts.onYolo();
|
|
3076
|
+
} else {
|
|
3077
|
+
const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, or /yolo toggle.`;
|
|
3078
|
+
opts.renderer.writeWarning(msg2);
|
|
3079
|
+
return { message: msg2 };
|
|
3080
|
+
}
|
|
3081
|
+
opts.onYolo(newState);
|
|
3082
|
+
const label = newState ? `${color.yellow("ENABLED")} \u2014 all tool calls will be auto-approved` : `${color.green("DISABLED")} \u2014 permission prompts are active`;
|
|
3083
|
+
const msg = `YOLO mode: ${label}`;
|
|
3084
|
+
opts.renderer.write(msg);
|
|
3085
|
+
return { message: msg };
|
|
3086
|
+
}
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
function buildAutonomyCommand(opts) {
|
|
3090
|
+
return {
|
|
3091
|
+
name: "autonomy",
|
|
3092
|
+
description: "Toggle or query autonomy mode (self-driving agent).",
|
|
3093
|
+
help: [
|
|
3094
|
+
"Usage:",
|
|
3095
|
+
" /autonomy Show current autonomy status",
|
|
3096
|
+
" /autonomy off Disabled \u2014 agent stops after each turn (default)",
|
|
3097
|
+
" /autonomy suggest Show next-step suggestions after each turn",
|
|
3098
|
+
" /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
|
|
3099
|
+
" /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 off",
|
|
3100
|
+
"",
|
|
3101
|
+
"Modes:",
|
|
3102
|
+
" off \u2014 Normal interactive mode. Agent stops and waits.",
|
|
3103
|
+
" suggest \u2014 After each turn, agent suggests next steps. You pick.",
|
|
3104
|
+
" auto \u2014 After each turn, agent picks the best next step and continues.",
|
|
3105
|
+
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
3106
|
+
"",
|
|
3107
|
+
"In auto mode the agent works autonomously. Press Esc to redirect,",
|
|
3108
|
+
"Ctrl+C to stop. The agent suggests context-aware next steps based on",
|
|
3109
|
+
"the conversation history."
|
|
3110
|
+
].join("\n"),
|
|
3111
|
+
async run(args) {
|
|
3112
|
+
const arg = args.trim().toLowerCase();
|
|
3113
|
+
if (!opts.onAutonomy) {
|
|
3114
|
+
const msg2 = "Autonomy mode is not available in this session.";
|
|
3115
|
+
opts.renderer.writeWarning(msg2);
|
|
3116
|
+
return { message: msg2 };
|
|
3117
|
+
}
|
|
3118
|
+
if (!arg) {
|
|
3119
|
+
const current = opts.onAutonomy();
|
|
3120
|
+
const labels2 = {
|
|
3121
|
+
off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
|
|
3122
|
+
suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
|
|
3123
|
+
auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`
|
|
3124
|
+
};
|
|
3125
|
+
const msg2 = `Autonomy mode: ${labels2[current]}`;
|
|
3126
|
+
opts.renderer.write(msg2);
|
|
3127
|
+
return { message: msg2 };
|
|
3128
|
+
}
|
|
3129
|
+
let newMode;
|
|
3130
|
+
if (arg === "on" || arg === "enable" || arg === "true" || arg === "auto") {
|
|
3131
|
+
newMode = "auto";
|
|
3132
|
+
} else if (arg === "off" || arg === "disable" || arg === "false") {
|
|
3133
|
+
newMode = "off";
|
|
3134
|
+
} else if (arg === "suggest" || arg === "suggestions") {
|
|
3135
|
+
newMode = "suggest";
|
|
3136
|
+
} else if (arg === "toggle" || arg === "cycle") {
|
|
3137
|
+
const current = opts.onAutonomy() ?? "off";
|
|
3138
|
+
const cycle = ["off", "suggest", "auto"];
|
|
3139
|
+
newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
|
|
3140
|
+
} else {
|
|
3141
|
+
const msg2 = `Unknown argument: ${arg}. Use /autonomy on, /autonomy off, /autonomy suggest, or /autonomy toggle.`;
|
|
3142
|
+
opts.renderer.writeWarning(msg2);
|
|
3143
|
+
return { message: msg2 };
|
|
3144
|
+
}
|
|
3145
|
+
opts.onAutonomy(newMode);
|
|
3146
|
+
const labels = {
|
|
3147
|
+
off: `${color.green("OFF")} \u2014 agent stops after each turn`,
|
|
3148
|
+
suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
|
|
3149
|
+
auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`
|
|
3150
|
+
};
|
|
3151
|
+
const msg = `Autonomy mode: ${labels[newMode]}`;
|
|
3152
|
+
opts.renderer.write(msg);
|
|
3153
|
+
return { message: msg };
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// src/slash-commands/mode.ts
|
|
3159
|
+
function buildModeCommand(opts) {
|
|
3160
|
+
return {
|
|
3161
|
+
name: "mode",
|
|
3162
|
+
description: "Switch or view the current mode",
|
|
3163
|
+
help: [
|
|
3164
|
+
"Usage:",
|
|
3165
|
+
" /mode Show current mode and available modes",
|
|
3166
|
+
" /mode <id> Switch to a different mode",
|
|
3167
|
+
"",
|
|
3168
|
+
"Available modes:",
|
|
3169
|
+
" default General-purpose coding assistant",
|
|
3170
|
+
" brief Fast, no-nonsense \u2014 get to the point",
|
|
3171
|
+
" teach Mentor mode \u2014 explains why, not just what",
|
|
3172
|
+
" code-reviewer, code-auditor, architect, debugger, tester, devops, refactorer",
|
|
3173
|
+
"",
|
|
3174
|
+
"Example:",
|
|
3175
|
+
" /mode brief Switch to brief mode",
|
|
3176
|
+
" /mode teach Switch to teach mode"
|
|
3177
|
+
].join("\n"),
|
|
3178
|
+
async run(args) {
|
|
3179
|
+
const modeStore = opts.modeStore;
|
|
3180
|
+
if (!modeStore) {
|
|
3181
|
+
return { message: "Mode store not available in this context." };
|
|
3182
|
+
}
|
|
3183
|
+
const modes = await modeStore.listModes();
|
|
3184
|
+
const active = await modeStore.getActiveMode();
|
|
3185
|
+
if (!args.trim()) {
|
|
3186
|
+
const lines = [`Current mode: ${active?.name ?? "none"}`, "", "Available modes:"];
|
|
3187
|
+
for (const m of modes) {
|
|
3188
|
+
const mark = m.id === active?.id ? " [active]" : "";
|
|
3189
|
+
lines.push(` ${m.id} \u2014 ${m.description}${mark}`);
|
|
3190
|
+
}
|
|
3191
|
+
return { message: lines.join("\n") };
|
|
3192
|
+
}
|
|
3193
|
+
const target = args.trim().toLowerCase();
|
|
3194
|
+
const targetMode = modes.find((m) => m.id === target);
|
|
3195
|
+
if (!targetMode) {
|
|
3196
|
+
const available = modes.map((m) => m.id).join(", ");
|
|
3197
|
+
return { message: `Unknown mode "${target}". Available: ${available}` };
|
|
3198
|
+
}
|
|
3199
|
+
await modeStore.setActiveMode(targetMode.id);
|
|
3200
|
+
return {
|
|
3201
|
+
message: `Switched to "${targetMode.name}" mode.
|
|
3202
|
+
${targetMode.description}`
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
// src/slash-commands/index.ts
|
|
3209
|
+
init_sdd();
|
|
3210
|
+
|
|
3211
|
+
// src/slash-commands/skill-generator.ts
|
|
3212
|
+
function buildSkillGeneratorCommand(opts) {
|
|
3213
|
+
return {
|
|
3214
|
+
name: "skill-gen",
|
|
3215
|
+
description: "Create a new AI skill interactively. The AI will guide you.",
|
|
3216
|
+
help: [
|
|
3217
|
+
"\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557",
|
|
3218
|
+
"",
|
|
3219
|
+
"Create new AI skills with AI guidance.",
|
|
3220
|
+
"",
|
|
3221
|
+
"Usage:",
|
|
3222
|
+
" /skill-gen Start skill creation",
|
|
3223
|
+
" /skill-gen list List existing skills",
|
|
3224
|
+
" /skill-gen edit <name> View an existing skill",
|
|
3225
|
+
"",
|
|
3226
|
+
"The AI will ask you questions and create the skill file.",
|
|
3227
|
+
"Skills are saved to .wrongstack/skills/<name>/SKILL.md"
|
|
3228
|
+
].join("\n"),
|
|
3229
|
+
async run(args) {
|
|
3230
|
+
const trimmed = args.trim();
|
|
3231
|
+
if (trimmed === "list" || trimmed === "ls") {
|
|
3232
|
+
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
3233
|
+
const entries = await opts.skillLoader.listEntries();
|
|
3234
|
+
if (entries.length === 0) return { message: "No skills found." };
|
|
3235
|
+
const lines = entries.map((e) => {
|
|
3236
|
+
const src = e.source === "project" ? "\u{1F4C1}" : e.source === "user" ? "\u{1F464}" : "\u{1F4E6}";
|
|
3237
|
+
return ` ${src} ${e.name}
|
|
3238
|
+
${e.trigger}`;
|
|
3239
|
+
});
|
|
3240
|
+
return { message: `Available Skills:
|
|
3241
|
+
${lines.join("\n\n")}
|
|
3242
|
+
` };
|
|
3243
|
+
}
|
|
3244
|
+
if (trimmed.startsWith("edit ")) {
|
|
3245
|
+
const skillName = trimmed.slice(5).trim();
|
|
3246
|
+
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
3247
|
+
const skill = await opts.skillLoader.find(skillName);
|
|
3248
|
+
if (!skill) return { message: `Skill "${skillName}" not found.` };
|
|
3249
|
+
const body = await opts.skillLoader.readBody(skillName);
|
|
3250
|
+
return {
|
|
3251
|
+
message: [
|
|
3252
|
+
`Skill: ${skillName}`,
|
|
3253
|
+
`Path: ${skill.path}`,
|
|
3254
|
+
"",
|
|
3255
|
+
body
|
|
3256
|
+
].join("\n")
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
return {
|
|
3260
|
+
message: "\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557\n\nThe AI will guide you through creating a new skill.\nAnswer its questions naturally.",
|
|
3261
|
+
runText: "I want to create a new AI skill. Read the skill-creator skill and guide me through the process. Ask me questions one at a time \u2014 name, description, what to cover \u2014 then create the SKILL.md file."
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
};
|
|
3265
|
+
}
|
|
3266
|
+
function makeInstaller(opts, projectRoot, global) {
|
|
3267
|
+
const globalRoot = path17.join(os4.homedir(), ".wrongstack");
|
|
3268
|
+
return new SkillInstaller({
|
|
3269
|
+
manifestPath: path17.join(globalRoot, "installed-skills.json"),
|
|
3270
|
+
projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
|
|
3271
|
+
globalSkillsDir: path17.join(globalRoot, "skills"),
|
|
3272
|
+
projectHash: projectHash(projectRoot),
|
|
3273
|
+
skillLoader: opts.skillLoader
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
function buildSkillInstallCommand(opts) {
|
|
3277
|
+
return {
|
|
3278
|
+
name: "skill-install",
|
|
3279
|
+
description: "Install skills from a GitHub repository.",
|
|
3280
|
+
argsHint: "<user/repo[@ref]> [--global]",
|
|
3281
|
+
help: [
|
|
3282
|
+
"\u2554\u2550\u2550\u2550 Skill Install \u2550\u2550\u2550\u2557",
|
|
3283
|
+
"",
|
|
3284
|
+
"Install skills from a GitHub repository.",
|
|
3285
|
+
"",
|
|
3286
|
+
"Usage:",
|
|
3287
|
+
" /skill-install <user/repo> Install from default branch (main)",
|
|
3288
|
+
" /skill-install <user/repo@ref> Install specific tag/branch/commit",
|
|
3289
|
+
" /skill-install <user/repo> --global Install to user-global skills",
|
|
3290
|
+
"",
|
|
3291
|
+
"Supports both single-skill repos (SKILL.md at root)",
|
|
3292
|
+
"and multi-skill repos (skills/ subdirectory).",
|
|
3293
|
+
"",
|
|
3294
|
+
"Examples:",
|
|
3295
|
+
" /skill-install wrongstack/awesome-skills",
|
|
3296
|
+
" /skill-install wrongstack/skills@v1.0",
|
|
3297
|
+
" /skill-install user/my-skills --global"
|
|
3298
|
+
].join("\n"),
|
|
3299
|
+
async run(args, ctx) {
|
|
3300
|
+
const parts = args.trim().split(/\s+/);
|
|
3301
|
+
const ref = parts.find((p) => !p.startsWith("--"));
|
|
3302
|
+
const isGlobal = parts.includes("--global");
|
|
3303
|
+
if (!ref) {
|
|
3304
|
+
return { message: "Usage: /skill-install <user/repo[@ref]> [--global]" };
|
|
3305
|
+
}
|
|
3306
|
+
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
3307
|
+
try {
|
|
3308
|
+
const results = await installer.install(ref, { global: isGlobal });
|
|
3309
|
+
if (results.length === 0) {
|
|
3310
|
+
return { message: "No skills found in the repository." };
|
|
3311
|
+
}
|
|
3312
|
+
const scope = isGlobal ? "user-global" : "project";
|
|
3313
|
+
const lines = [`Installed ${results.length} skill(s) [${scope}]:`];
|
|
3314
|
+
for (const r of results) {
|
|
3315
|
+
lines.push(` \u2713 ${r.name} (${r.source}@${r.ref})`);
|
|
3316
|
+
lines.push(` \u2192 ${r.path}`);
|
|
3317
|
+
}
|
|
3318
|
+
return { message: lines.join("\n") };
|
|
3319
|
+
} catch (err) {
|
|
3320
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3321
|
+
opts.renderer.writeError(`Install failed: ${msg}`);
|
|
3322
|
+
return { message: `\u2717 Install failed: ${msg}` };
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
function buildSkillUpdateCommand(opts) {
|
|
3328
|
+
return {
|
|
3329
|
+
name: "skill-update",
|
|
3330
|
+
description: "Update installed skills from their GitHub source.",
|
|
3331
|
+
argsHint: "[name|ref] [--global]",
|
|
3332
|
+
help: [
|
|
3333
|
+
"\u2554\u2550\u2550\u2550 Skill Update \u2550\u2550\u2550\u2557",
|
|
3334
|
+
"",
|
|
3335
|
+
"Update installed skills from their GitHub source.",
|
|
3336
|
+
"",
|
|
3337
|
+
"Usage:",
|
|
3338
|
+
" /skill-update Update all installed skills",
|
|
3339
|
+
" /skill-update <name> Update a specific skill",
|
|
3340
|
+
" /skill-update <user/repo@ref> Update to a different ref",
|
|
3341
|
+
" /skill-update <name> --global Update a global skill"
|
|
3342
|
+
].join("\n"),
|
|
3343
|
+
async run(args, ctx) {
|
|
3344
|
+
const parts = args.trim().split(/\s+/);
|
|
3345
|
+
const nameOrRef = parts.find((p) => !p.startsWith("--"));
|
|
3346
|
+
const isGlobal = parts.includes("--global");
|
|
3347
|
+
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
3348
|
+
try {
|
|
3349
|
+
const result = await installer.update(nameOrRef, { global: isGlobal });
|
|
3350
|
+
const lines = [];
|
|
3351
|
+
if (result.updated.length > 0) {
|
|
3352
|
+
lines.push(`Updated ${result.updated.length} skill(s):`);
|
|
3353
|
+
for (const u of result.updated) {
|
|
3354
|
+
if (u.oldRef !== u.newRef) {
|
|
3355
|
+
lines.push(` \u2713 ${u.name} (${u.oldRef} \u2192 ${u.newRef})`);
|
|
3356
|
+
} else {
|
|
3357
|
+
lines.push(` \u2713 ${u.name} (refreshed)`);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
if (result.unchanged.length > 0) {
|
|
3362
|
+
lines.push(`Up to date: ${result.unchanged.join(", ")}`);
|
|
3363
|
+
}
|
|
3364
|
+
if (result.errors.length > 0) {
|
|
3365
|
+
for (const e of result.errors) {
|
|
3366
|
+
lines.push(` \u2717 ${e.name}: ${e.error}`);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
if (lines.length === 0) {
|
|
3370
|
+
return { message: "No installed skills to update." };
|
|
3371
|
+
}
|
|
3372
|
+
return { message: lines.join("\n") };
|
|
3373
|
+
} catch (err) {
|
|
3374
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3375
|
+
return { message: `\u2717 Update failed: ${msg}` };
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
function buildSkillUninstallCommand(opts) {
|
|
3381
|
+
return {
|
|
3382
|
+
name: "skill-uninstall",
|
|
3383
|
+
description: "Remove an installed skill.",
|
|
3384
|
+
argsHint: "<name> [--global]",
|
|
3385
|
+
help: [
|
|
3386
|
+
"\u2554\u2550\u2550\u2550 Skill Uninstall \u2550\u2550\u2550\u2557",
|
|
3387
|
+
"",
|
|
3388
|
+
"Remove an installed skill and its files.",
|
|
3389
|
+
"",
|
|
3390
|
+
"Usage:",
|
|
3391
|
+
" /skill-uninstall <name> Remove from project skills",
|
|
3392
|
+
" /skill-uninstall <name> --global Remove from user-global skills"
|
|
3393
|
+
].join("\n"),
|
|
3394
|
+
async run(args, ctx) {
|
|
3395
|
+
const parts = args.trim().split(/\s+/);
|
|
3396
|
+
const name = parts.find((p) => !p.startsWith("--"));
|
|
3397
|
+
const isGlobal = parts.includes("--global");
|
|
3398
|
+
if (!name) {
|
|
3399
|
+
const installer2 = makeInstaller(opts, ctx.projectRoot);
|
|
3400
|
+
const installed = await installer2.listInstalled();
|
|
3401
|
+
if (installed.length === 0) {
|
|
3402
|
+
return { message: "No installed skills found." };
|
|
3403
|
+
}
|
|
3404
|
+
const scope = isGlobal ? "user" : "project";
|
|
3405
|
+
const filtered = installed.filter((s) => s.scope === scope);
|
|
3406
|
+
if (filtered.length === 0) {
|
|
3407
|
+
return { message: `No installed skills found (${scope} scope).` };
|
|
3408
|
+
}
|
|
3409
|
+
const lines = [`Installed skills (${scope}):`];
|
|
3410
|
+
for (const s of filtered) {
|
|
3411
|
+
lines.push(` ${s.name} ${s.source}@${s.ref} (${s.installedAt.slice(0, 10)})`);
|
|
3412
|
+
}
|
|
3413
|
+
lines.push("", "Use /skill-uninstall <name> to remove.");
|
|
3414
|
+
return { message: lines.join("\n") };
|
|
3415
|
+
}
|
|
3416
|
+
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
3417
|
+
try {
|
|
3418
|
+
await installer.uninstall(name, { global: isGlobal });
|
|
3419
|
+
return { message: `\u2713 Skill "${name}" uninstalled.` };
|
|
3420
|
+
} catch (err) {
|
|
3421
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3422
|
+
return { message: `\u2717 Uninstall failed: ${msg}` };
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
2201
3427
|
|
|
2202
3428
|
// src/slash-commands/index.ts
|
|
2203
3429
|
function buildBuiltinSlashCommands(opts) {
|
|
@@ -2209,6 +3435,10 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
2209
3435
|
buildContextCommand(opts),
|
|
2210
3436
|
buildToolsCommand(opts),
|
|
2211
3437
|
buildSkillCommand(opts),
|
|
3438
|
+
buildSkillGeneratorCommand(opts),
|
|
3439
|
+
buildSkillInstallCommand(opts),
|
|
3440
|
+
buildSkillUpdateCommand(opts),
|
|
3441
|
+
buildSkillUninstallCommand(opts),
|
|
2212
3442
|
buildPluginCommand(opts),
|
|
2213
3443
|
buildDiagCommand(opts),
|
|
2214
3444
|
buildStatsCommand(opts),
|
|
@@ -2221,8 +3451,12 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
2221
3451
|
buildMemoryCommand(opts),
|
|
2222
3452
|
buildTodosCommand(opts),
|
|
2223
3453
|
buildPlanCommand(opts),
|
|
3454
|
+
buildSddCommand(opts),
|
|
2224
3455
|
buildSaveCommand(opts),
|
|
2225
3456
|
buildLoadCommand(opts),
|
|
3457
|
+
buildYoloCommand(opts),
|
|
3458
|
+
buildAutonomyCommand(opts),
|
|
3459
|
+
buildModeCommand(opts),
|
|
2226
3460
|
buildExitCommand(opts)
|
|
2227
3461
|
];
|
|
2228
3462
|
}
|
|
@@ -2242,13 +3476,13 @@ var MANIFESTS = [
|
|
|
2242
3476
|
];
|
|
2243
3477
|
async function detectProjectKind(projectRoot) {
|
|
2244
3478
|
try {
|
|
2245
|
-
await fs14.access(
|
|
3479
|
+
await fs14.access(path17.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
2246
3480
|
return "initialized";
|
|
2247
3481
|
} catch {
|
|
2248
3482
|
}
|
|
2249
3483
|
for (const m of MANIFESTS) {
|
|
2250
3484
|
try {
|
|
2251
|
-
await fs14.access(
|
|
3485
|
+
await fs14.access(path17.join(projectRoot, m));
|
|
2252
3486
|
return "project";
|
|
2253
3487
|
} catch {
|
|
2254
3488
|
}
|
|
@@ -2256,8 +3490,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
2256
3490
|
return "empty";
|
|
2257
3491
|
}
|
|
2258
3492
|
async function scaffoldAgentsMd(projectRoot) {
|
|
2259
|
-
const dir =
|
|
2260
|
-
const file =
|
|
3493
|
+
const dir = path17.join(projectRoot, ".wrongstack");
|
|
3494
|
+
const file = path17.join(dir, "AGENTS.md");
|
|
2261
3495
|
const facts = await detectProjectFacts(projectRoot);
|
|
2262
3496
|
const body = renderAgentsTemplate(facts);
|
|
2263
3497
|
await fs14.mkdir(dir, { recursive: true });
|
|
@@ -2270,7 +3504,7 @@ async function runProjectCheck(opts) {
|
|
|
2270
3504
|
if (kind === "initialized") {
|
|
2271
3505
|
renderer.write(
|
|
2272
3506
|
`
|
|
2273
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
3507
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path17.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
2274
3508
|
`
|
|
2275
3509
|
);
|
|
2276
3510
|
return true;
|
|
@@ -2326,9 +3560,9 @@ async function runLaunchPrompts(opts) {
|
|
|
2326
3560
|
yolo = yoloPinned;
|
|
2327
3561
|
} else {
|
|
2328
3562
|
const answer = (await reader.readLine(
|
|
2329
|
-
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[
|
|
3563
|
+
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[Y/n]")} `
|
|
2330
3564
|
)).trim().toLowerCase();
|
|
2331
|
-
yolo = answer
|
|
3565
|
+
yolo = answer !== "n" && answer !== "no";
|
|
2332
3566
|
}
|
|
2333
3567
|
renderer.write(
|
|
2334
3568
|
`
|
|
@@ -2566,14 +3800,14 @@ function summarize(value, name) {
|
|
|
2566
3800
|
if (typeof v === "object" && v !== null) {
|
|
2567
3801
|
const o = v;
|
|
2568
3802
|
if (name === "edit") {
|
|
2569
|
-
const
|
|
3803
|
+
const path18 = typeof o["path"] === "string" ? o["path"] : "";
|
|
2570
3804
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
2571
|
-
return `${
|
|
3805
|
+
return `${path18} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
2572
3806
|
}
|
|
2573
3807
|
if (name === "write") {
|
|
2574
|
-
const
|
|
3808
|
+
const path18 = typeof o["path"] === "string" ? o["path"] : "";
|
|
2575
3809
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
2576
|
-
return bytes !== void 0 ? `${
|
|
3810
|
+
return bytes !== void 0 ? `${path18} ${bytes}B` : path18;
|
|
2577
3811
|
}
|
|
2578
3812
|
if (typeof o["count"] === "number") {
|
|
2579
3813
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -3199,7 +4433,7 @@ async function mutateProviders(deps, mutator) {
|
|
|
3199
4433
|
const providers = decrypted.providers ?? {};
|
|
3200
4434
|
mutator(providers);
|
|
3201
4435
|
decrypted.providers = providers;
|
|
3202
|
-
const encrypted = encryptConfigSecrets(decrypted, deps.vault);
|
|
4436
|
+
const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
|
|
3203
4437
|
await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3204
4438
|
}
|
|
3205
4439
|
|
|
@@ -3257,7 +4491,7 @@ var diagCmd = async (_args, deps) => {
|
|
|
3257
4491
|
` modelsCache: ${deps.paths.modelsCache}`,
|
|
3258
4492
|
` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
|
|
3259
4493
|
` node: ${process.version}`,
|
|
3260
|
-
` os: ${
|
|
4494
|
+
` os: ${os4.platform()} ${os4.release()}`,
|
|
3261
4495
|
` provider: ${cfg.provider ?? "<unset>"}`,
|
|
3262
4496
|
` model: ${cfg.model ?? "<unset>"}`,
|
|
3263
4497
|
` tools: ${deps.toolRegistry?.list().length ?? 0}`,
|
|
@@ -3336,7 +4570,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
3336
4570
|
}
|
|
3337
4571
|
try {
|
|
3338
4572
|
await fs14.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
3339
|
-
const probe =
|
|
4573
|
+
const probe = path17.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
3340
4574
|
await fs14.writeFile(probe, "");
|
|
3341
4575
|
await fs14.unlink(probe);
|
|
3342
4576
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -3439,8 +4673,8 @@ var exportCmd = async (args, deps) => {
|
|
|
3439
4673
|
return 1;
|
|
3440
4674
|
}
|
|
3441
4675
|
if (output) {
|
|
3442
|
-
await fs14.mkdir(
|
|
3443
|
-
await fs14.writeFile(
|
|
4676
|
+
await fs14.mkdir(path17.dirname(path17.resolve(deps.cwd, output)), { recursive: true });
|
|
4677
|
+
await fs14.writeFile(path17.resolve(deps.cwd, output), rendered, "utf8");
|
|
3444
4678
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
3445
4679
|
`);
|
|
3446
4680
|
} else {
|
|
@@ -3500,9 +4734,12 @@ var initCmd = async (_args, deps) => {
|
|
|
3500
4734
|
await fs14.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
3501
4735
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
3502
4736
|
if (apiKey) config.apiKey = apiKey;
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
const
|
|
4737
|
+
const keyFile = path17.join(path17.dirname(deps.paths.globalConfig), ".key");
|
|
4738
|
+
const vault = new DefaultSecretVault$1({ keyFile });
|
|
4739
|
+
const encrypted = encryptConfigSecrets(config, vault);
|
|
4740
|
+
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
4741
|
+
await fs14.mkdir(path17.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
4742
|
+
const agentsFile = path17.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
3506
4743
|
try {
|
|
3507
4744
|
await fs14.access(agentsFile);
|
|
3508
4745
|
} catch {
|
|
@@ -3812,7 +5049,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
3812
5049
|
return 0;
|
|
3813
5050
|
};
|
|
3814
5051
|
var projectsCmd = async (_args, deps) => {
|
|
3815
|
-
const projectsRoot =
|
|
5052
|
+
const projectsRoot = path17.join(deps.paths.globalRoot, "projects");
|
|
3816
5053
|
try {
|
|
3817
5054
|
const entries = await fs14.readdir(projectsRoot);
|
|
3818
5055
|
if (entries.length === 0) {
|
|
@@ -3822,7 +5059,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
3822
5059
|
for (const hash of entries) {
|
|
3823
5060
|
try {
|
|
3824
5061
|
const meta = JSON.parse(
|
|
3825
|
-
await fs14.readFile(
|
|
5062
|
+
await fs14.readFile(path17.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
3826
5063
|
);
|
|
3827
5064
|
deps.renderer.write(
|
|
3828
5065
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -4021,7 +5258,7 @@ var skillsCmd = async (_args, deps) => {
|
|
|
4021
5258
|
};
|
|
4022
5259
|
var versionCmd = async (_args, deps) => {
|
|
4023
5260
|
deps.renderer.write(
|
|
4024
|
-
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${
|
|
5261
|
+
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os4.platform()})
|
|
4025
5262
|
`
|
|
4026
5263
|
);
|
|
4027
5264
|
return 0;
|
|
@@ -4097,31 +5334,29 @@ function fmtDuration(ms) {
|
|
|
4097
5334
|
const remMin = m - h * 60;
|
|
4098
5335
|
return `${h}h${remMin}m`;
|
|
4099
5336
|
}
|
|
4100
|
-
function fmtTaskResultLine(r,
|
|
5337
|
+
function fmtTaskResultLine(r, color30) {
|
|
4101
5338
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
4102
5339
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
4103
5340
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
4104
5341
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
4105
|
-
const errKindChip = errKind ?
|
|
4106
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
5342
|
+
const errKindChip = errKind ? color30.dim(` [${errKind}]`) : "";
|
|
5343
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color30.dim(errTail)}` : "";
|
|
4107
5344
|
switch (r.status) {
|
|
4108
5345
|
case "success":
|
|
4109
|
-
return { mark:
|
|
5346
|
+
return { mark: color30.green("\u2713"), stats, tail: "" };
|
|
4110
5347
|
case "timeout":
|
|
4111
|
-
return { mark:
|
|
5348
|
+
return { mark: color30.yellow("\u23F1"), stats: `${color30.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
4112
5349
|
case "stopped":
|
|
4113
|
-
return { mark:
|
|
5350
|
+
return { mark: color30.dim("\u2298"), stats: `${color30.dim("stopped")} ${stats}`, tail: errSnip };
|
|
4114
5351
|
case "failed":
|
|
4115
|
-
return { mark:
|
|
5352
|
+
return { mark: color30.red("\u2717"), stats: `${color30.red("failed")} ${stats}`, tail: errSnip };
|
|
4116
5353
|
}
|
|
4117
5354
|
}
|
|
4118
|
-
|
|
4119
|
-
// src/boot.ts
|
|
4120
5355
|
function resolveBundledSkillsDir() {
|
|
4121
5356
|
try {
|
|
4122
5357
|
const req2 = createRequire(import.meta.url);
|
|
4123
5358
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
4124
|
-
return
|
|
5359
|
+
return path17.join(path17.dirname(corePkg), "skills");
|
|
4125
5360
|
} catch {
|
|
4126
5361
|
return void 0;
|
|
4127
5362
|
}
|
|
@@ -4152,11 +5387,15 @@ async function boot(argv) {
|
|
|
4152
5387
|
});
|
|
4153
5388
|
const first = positional[0];
|
|
4154
5389
|
if (first && subcommands[first]) {
|
|
4155
|
-
const
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
5390
|
+
const container = createDefaultContainer({
|
|
5391
|
+
config,
|
|
5392
|
+
wpaths,
|
|
5393
|
+
logger,
|
|
5394
|
+
modelsRegistry,
|
|
5395
|
+
bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir() : void 0
|
|
4159
5396
|
});
|
|
5397
|
+
const sessionStore = container.resolve(TOKENS.SessionStore);
|
|
5398
|
+
const skillLoader = container.resolve(TOKENS.SkillLoader);
|
|
4160
5399
|
const toolRegistryForSubcmd = new ToolRegistry();
|
|
4161
5400
|
toolRegistryForSubcmd.registerAllOrThrow(
|
|
4162
5401
|
[...builtinToolsPack.tools ?? []],
|
|
@@ -4257,6 +5496,9 @@ async function boot(argv) {
|
|
|
4257
5496
|
logger
|
|
4258
5497
|
};
|
|
4259
5498
|
}
|
|
5499
|
+
|
|
5500
|
+
// src/repl.ts
|
|
5501
|
+
init_sdd();
|
|
4260
5502
|
async function runRepl(opts) {
|
|
4261
5503
|
if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
|
|
4262
5504
|
let activeCtrl;
|
|
@@ -4300,6 +5542,58 @@ async function runRepl(opts) {
|
|
|
4300
5542
|
if (res?.message) opts.renderer.write(`${res.message}
|
|
4301
5543
|
`);
|
|
4302
5544
|
if (res?.exit) break;
|
|
5545
|
+
if (res?.runText) {
|
|
5546
|
+
const runBlocks = [{ type: "text", text: res.runText }];
|
|
5547
|
+
const runCtrl2 = new AbortController();
|
|
5548
|
+
activeCtrl = runCtrl2;
|
|
5549
|
+
try {
|
|
5550
|
+
const runResult = await opts.agent.run(runBlocks, { signal: runCtrl2.signal });
|
|
5551
|
+
if (runResult.status === "done" && runResult.finalText) {
|
|
5552
|
+
const specSaved = await trySaveSpecFromAIOutput(runResult.finalText);
|
|
5553
|
+
if (specSaved) {
|
|
5554
|
+
opts.renderer.write(
|
|
5555
|
+
`
|
|
5556
|
+
${color.cyan(" \u2713 Spec detected and saved! Use /sdd approve to continue.")}
|
|
5557
|
+
`
|
|
5558
|
+
);
|
|
5559
|
+
}
|
|
5560
|
+
const planSaved = trySaveImplementationPlan(runResult.finalText);
|
|
5561
|
+
if (planSaved) {
|
|
5562
|
+
opts.renderer.write(
|
|
5563
|
+
`
|
|
5564
|
+
${color.cyan(" \u2713 Implementation plan saved!")}
|
|
5565
|
+
`
|
|
5566
|
+
);
|
|
5567
|
+
}
|
|
5568
|
+
const tasksSaved = await trySaveTasksFromAIOutput(runResult.finalText);
|
|
5569
|
+
if (tasksSaved) {
|
|
5570
|
+
const progress = getTaskProgress();
|
|
5571
|
+
const count = progress?.total ?? 0;
|
|
5572
|
+
opts.renderer.write(
|
|
5573
|
+
`
|
|
5574
|
+
${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`)}
|
|
5575
|
+
`
|
|
5576
|
+
);
|
|
5577
|
+
}
|
|
5578
|
+
const sddPhase2 = getActiveSDDPhase();
|
|
5579
|
+
if (sddPhase2 === "executing") {
|
|
5580
|
+
const autoCompleted = autoDetectTaskCompletion(runResult.finalText);
|
|
5581
|
+
if (autoCompleted > 0) {
|
|
5582
|
+
const progress = getTaskProgress();
|
|
5583
|
+
if (progress) {
|
|
5584
|
+
opts.renderer.write(
|
|
5585
|
+
`
|
|
5586
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`)}
|
|
5587
|
+
`
|
|
5588
|
+
);
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
} catch (runErr) {
|
|
5594
|
+
opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
4303
5597
|
} catch (err) {
|
|
4304
5598
|
opts.renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
4305
5599
|
}
|
|
@@ -4312,20 +5606,47 @@ async function runRepl(opts) {
|
|
|
4312
5606
|
`));
|
|
4313
5607
|
}
|
|
4314
5608
|
const blocks = await builder.submit();
|
|
5609
|
+
const sddContext = getActiveSDDContext();
|
|
5610
|
+
const taskList = getTaskListText();
|
|
5611
|
+
const taskProgress = getTaskProgress();
|
|
5612
|
+
const sddPhase = getActiveSDDPhase();
|
|
5613
|
+
let sddPrefix = "";
|
|
5614
|
+
if (sddContext) {
|
|
5615
|
+
sddPrefix = `[SDD SESSION ACTIVE]
|
|
5616
|
+
${sddContext}`;
|
|
5617
|
+
if (taskList) {
|
|
5618
|
+
sddPrefix += `
|
|
5619
|
+
|
|
5620
|
+
**Current Task List:**
|
|
5621
|
+
${taskList}`;
|
|
5622
|
+
}
|
|
5623
|
+
if (taskProgress && taskProgress.total > 0) {
|
|
5624
|
+
sddPrefix += `
|
|
5625
|
+
**Progress:** ${taskProgress.completed}/${taskProgress.total} (${taskProgress.percent}%)`;
|
|
5626
|
+
}
|
|
5627
|
+
if (sddPhase === "executing" && taskProgress && taskProgress.percent === 100) {
|
|
5628
|
+
sddPrefix += "\n\n**All tasks completed! Provide a summary of everything implemented.**";
|
|
5629
|
+
}
|
|
5630
|
+
sddPrefix += "\n\n---\nUser message:\n";
|
|
5631
|
+
}
|
|
5632
|
+
const effectiveBlocks = sddPrefix ? [
|
|
5633
|
+
{ type: "text", text: sddPrefix },
|
|
5634
|
+
...blocks
|
|
5635
|
+
] : blocks;
|
|
4315
5636
|
const runCtrl = new AbortController();
|
|
4316
5637
|
activeCtrl = runCtrl;
|
|
4317
5638
|
try {
|
|
4318
5639
|
const startedAt = Date.now();
|
|
4319
5640
|
const before = opts.tokenCounter?.total();
|
|
4320
5641
|
const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
|
|
4321
|
-
const routed =
|
|
5642
|
+
const routed = effectiveBlocks.some((block) => block.type === "image") ? await routeImagesForModel(effectiveBlocks, {
|
|
4322
5643
|
supportsVision: opts.supportsVision ? await opts.supportsVision() : opts.agent.ctx.provider.capabilities.vision,
|
|
4323
5644
|
adapters: opts.visionAdapters ?? [],
|
|
4324
5645
|
ctx: opts.agent.ctx,
|
|
4325
5646
|
signal: runCtrl.signal,
|
|
4326
5647
|
providerId: opts.agent.ctx.provider.id,
|
|
4327
5648
|
model: opts.agent.ctx.model
|
|
4328
|
-
}) : { blocks, route: "none", convertedImages: 0 };
|
|
5649
|
+
}) : { blocks: effectiveBlocks, route: "none", convertedImages: 0 };
|
|
4329
5650
|
if (routed.route === "adapter") {
|
|
4330
5651
|
opts.renderer.write(
|
|
4331
5652
|
color.dim(
|
|
@@ -4348,6 +5669,55 @@ async function runRepl(opts) {
|
|
|
4348
5669
|
} else if (result.status === "max_iterations") {
|
|
4349
5670
|
opts.renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
|
|
4350
5671
|
}
|
|
5672
|
+
if (result.status === "done" && result.finalText && sddContext) {
|
|
5673
|
+
const specSaved = await trySaveSpecFromAIOutput(result.finalText);
|
|
5674
|
+
if (specSaved) {
|
|
5675
|
+
opts.renderer.write(
|
|
5676
|
+
`
|
|
5677
|
+
${color.cyan(" \u2713 Spec detected and saved! Use /sdd approve to continue.")}
|
|
5678
|
+
`
|
|
5679
|
+
);
|
|
5680
|
+
}
|
|
5681
|
+
const planSaved = trySaveImplementationPlan(result.finalText);
|
|
5682
|
+
if (planSaved) {
|
|
5683
|
+
opts.renderer.write(
|
|
5684
|
+
`
|
|
5685
|
+
${color.cyan(" \u2713 Implementation plan saved!")}
|
|
5686
|
+
`
|
|
5687
|
+
);
|
|
5688
|
+
}
|
|
5689
|
+
const tasksSaved = await trySaveTasksFromAIOutput(result.finalText);
|
|
5690
|
+
if (tasksSaved) {
|
|
5691
|
+
const progress = getTaskProgress();
|
|
5692
|
+
const count = progress?.total ?? 0;
|
|
5693
|
+
opts.renderer.write(
|
|
5694
|
+
`
|
|
5695
|
+
${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`)}
|
|
5696
|
+
`
|
|
5697
|
+
);
|
|
5698
|
+
}
|
|
5699
|
+
const phase = getActiveSDDPhase();
|
|
5700
|
+
if (phase === "executing") {
|
|
5701
|
+
const autoCompleted = autoDetectTaskCompletion(result.finalText);
|
|
5702
|
+
if (autoCompleted > 0) {
|
|
5703
|
+
const progress = getTaskProgress();
|
|
5704
|
+
if (progress) {
|
|
5705
|
+
opts.renderer.write(
|
|
5706
|
+
`
|
|
5707
|
+
${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`)}
|
|
5708
|
+
`
|
|
5709
|
+
);
|
|
5710
|
+
if (progress.percent === 100) {
|
|
5711
|
+
opts.renderer.write(
|
|
5712
|
+
`
|
|
5713
|
+
${color.green(" \u{1F389} All tasks completed! Use /sdd cancel to end the session.")}
|
|
5714
|
+
`
|
|
5715
|
+
);
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
4351
5721
|
if (opts.tokenCounter && before) {
|
|
4352
5722
|
const after = opts.tokenCounter.total();
|
|
4353
5723
|
const costAfter = opts.tokenCounter.estimateCost().total;
|
|
@@ -4360,6 +5730,49 @@ ${color.dim(
|
|
|
4360
5730
|
`
|
|
4361
5731
|
);
|
|
4362
5732
|
}
|
|
5733
|
+
if (result.status === "done" && opts.getAutonomy) {
|
|
5734
|
+
const autonomy = opts.getAutonomy();
|
|
5735
|
+
if (autonomy === "auto") {
|
|
5736
|
+
const nextPrompt = 'Based on what you just did, what is the single most important next step? Just do it \u2014 execute the next logical step without asking for confirmation. If there is nothing meaningful left to do, say "DONE" and nothing else.';
|
|
5737
|
+
opts.renderer.write(color.dim("\n \u21B3 [autonomy] continuing\u2026\n"));
|
|
5738
|
+
const nextBlocks = [{ type: "text", text: nextPrompt }];
|
|
5739
|
+
const nextCtrl = new AbortController();
|
|
5740
|
+
activeCtrl = nextCtrl;
|
|
5741
|
+
try {
|
|
5742
|
+
const nextResult = await opts.agent.run(nextBlocks, { signal: nextCtrl.signal });
|
|
5743
|
+
if (nextResult.status === "done" && nextResult.finalText?.trim() === "DONE") {
|
|
5744
|
+
opts.renderer.write(color.dim("\n \u21B3 [autonomy] agent reports task complete.\n"));
|
|
5745
|
+
}
|
|
5746
|
+
if (opts.getAutonomy() === "auto" && nextResult.status === "done") {
|
|
5747
|
+
}
|
|
5748
|
+
} catch (err) {
|
|
5749
|
+
opts.renderer.writeError(
|
|
5750
|
+
`[autonomy] ${err instanceof Error ? err.message : String(err)}`
|
|
5751
|
+
);
|
|
5752
|
+
} finally {
|
|
5753
|
+
activeCtrl = void 0;
|
|
5754
|
+
}
|
|
5755
|
+
} else if (autonomy === "suggest") {
|
|
5756
|
+
const suggestPrompt = 'Based on what you just did, suggest 3 concrete next steps. Format: numbered list, one line each, no explanation. If there is nothing meaningful left, say "No further steps needed."';
|
|
5757
|
+
const suggestBlocks = [{ type: "text", text: suggestPrompt }];
|
|
5758
|
+
const suggestCtrl = new AbortController();
|
|
5759
|
+
activeCtrl = suggestCtrl;
|
|
5760
|
+
try {
|
|
5761
|
+
const suggestResult = await opts.agent.run(suggestBlocks, { signal: suggestCtrl.signal });
|
|
5762
|
+
if (suggestResult.status === "done" && suggestResult.finalText) {
|
|
5763
|
+
opts.renderer.write(
|
|
5764
|
+
`
|
|
5765
|
+
${color.cyan(" Suggested next steps:")}
|
|
5766
|
+
${suggestResult.finalText}
|
|
5767
|
+
`
|
|
5768
|
+
);
|
|
5769
|
+
}
|
|
5770
|
+
} catch {
|
|
5771
|
+
} finally {
|
|
5772
|
+
activeCtrl = void 0;
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
4363
5776
|
} catch (err) {
|
|
4364
5777
|
opts.renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
4365
5778
|
} finally {
|
|
@@ -4472,7 +5885,10 @@ async function execute(deps) {
|
|
|
4472
5885
|
switchProviderAndModel,
|
|
4473
5886
|
director,
|
|
4474
5887
|
fleetRoster,
|
|
4475
|
-
fleetStreamController
|
|
5888
|
+
fleetStreamController,
|
|
5889
|
+
getYolo,
|
|
5890
|
+
getAutonomy,
|
|
5891
|
+
skillLoader
|
|
4476
5892
|
} = deps;
|
|
4477
5893
|
let code = 0;
|
|
4478
5894
|
try {
|
|
@@ -4576,6 +5992,7 @@ async function execute(deps) {
|
|
|
4576
5992
|
banner: !flags["no-banner"],
|
|
4577
5993
|
queueStore,
|
|
4578
5994
|
yolo: !!config.yolo,
|
|
5995
|
+
getYolo,
|
|
4579
5996
|
appVersion: CLI_VERSION,
|
|
4580
5997
|
provider: config.provider,
|
|
4581
5998
|
family: banneredFamily,
|
|
@@ -4602,7 +6019,36 @@ async function execute(deps) {
|
|
|
4602
6019
|
},
|
|
4603
6020
|
fleetStreamController,
|
|
4604
6021
|
initialGoal: goalFlag,
|
|
4605
|
-
initialAsk: askFlag
|
|
6022
|
+
initialAsk: askFlag,
|
|
6023
|
+
getSDDContext: () => {
|
|
6024
|
+
const { getActiveSDDContext: getActiveSDDContext2 } = (init_sdd(), __toCommonJS(sdd_exports));
|
|
6025
|
+
return getActiveSDDContext2();
|
|
6026
|
+
},
|
|
6027
|
+
onSDDOutput: async (output) => {
|
|
6028
|
+
const { trySaveSpecFromAIOutput: trySaveSpecFromAIOutput2, trySaveImplementationPlan: trySaveImplementationPlan2, trySaveTasksFromAIOutput: trySaveTasksFromAIOutput2, autoDetectTaskCompletion: autoDetectTaskCompletion2, getTaskProgress: getTaskProgress2, getActiveSDDPhase: getActiveSDDPhase2 } = (init_sdd(), __toCommonJS(sdd_exports));
|
|
6029
|
+
const messages = [];
|
|
6030
|
+
const specSaved = await trySaveSpecFromAIOutput2(output);
|
|
6031
|
+
if (specSaved) messages.push("\u2713 Spec detected and saved! Use /sdd approve to continue.");
|
|
6032
|
+
const planSaved = trySaveImplementationPlan2(output);
|
|
6033
|
+
if (planSaved) messages.push("\u2713 Implementation plan saved!");
|
|
6034
|
+
const tasksSaved = await trySaveTasksFromAIOutput2(output);
|
|
6035
|
+
if (tasksSaved) {
|
|
6036
|
+
const progress = getTaskProgress2();
|
|
6037
|
+
const count = progress?.total ?? 0;
|
|
6038
|
+
messages.push(`\u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`);
|
|
6039
|
+
}
|
|
6040
|
+
const sddPhase = getActiveSDDPhase2();
|
|
6041
|
+
if (sddPhase === "executing") {
|
|
6042
|
+
const autoCompleted = autoDetectTaskCompletion2(output);
|
|
6043
|
+
if (autoCompleted > 0) {
|
|
6044
|
+
const progress = getTaskProgress2();
|
|
6045
|
+
if (progress) {
|
|
6046
|
+
messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`);
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
return messages;
|
|
6051
|
+
}
|
|
4606
6052
|
});
|
|
4607
6053
|
} finally {
|
|
4608
6054
|
renderer.setSilent(false);
|
|
@@ -4628,7 +6074,9 @@ async function execute(deps) {
|
|
|
4628
6074
|
supportsVision,
|
|
4629
6075
|
attachments,
|
|
4630
6076
|
effectiveMaxContext,
|
|
4631
|
-
projectName:
|
|
6077
|
+
projectName: path17.basename(projectRoot) || void 0,
|
|
6078
|
+
getAutonomy,
|
|
6079
|
+
skillLoader
|
|
4632
6080
|
});
|
|
4633
6081
|
} finally {
|
|
4634
6082
|
await webuiPromise.catch(() => void 0);
|
|
@@ -4644,7 +6092,9 @@ async function execute(deps) {
|
|
|
4644
6092
|
supportsVision,
|
|
4645
6093
|
attachments,
|
|
4646
6094
|
effectiveMaxContext,
|
|
4647
|
-
projectName:
|
|
6095
|
+
projectName: path17.basename(projectRoot) || void 0,
|
|
6096
|
+
getAutonomy,
|
|
6097
|
+
skillLoader
|
|
4648
6098
|
});
|
|
4649
6099
|
}
|
|
4650
6100
|
} finally {
|
|
@@ -4744,6 +6194,15 @@ var MultiAgentHost = class {
|
|
|
4744
6194
|
this.pending.delete(task.id);
|
|
4745
6195
|
this.emitLifecycleCompleted(task.id, result);
|
|
4746
6196
|
});
|
|
6197
|
+
this.director.fleet.filter("budget.threshold_reached", (e) => {
|
|
6198
|
+
const payload = e.payload;
|
|
6199
|
+
this.deps.events.emit("subagent.budget_warning", {
|
|
6200
|
+
subagentId: e.subagentId,
|
|
6201
|
+
kind: payload.kind,
|
|
6202
|
+
used: payload.used,
|
|
6203
|
+
limit: payload.limit
|
|
6204
|
+
});
|
|
6205
|
+
});
|
|
4747
6206
|
this.coordinator = this.director.coordinator;
|
|
4748
6207
|
} else {
|
|
4749
6208
|
this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
|
|
@@ -4905,7 +6364,7 @@ var MultiAgentHost = class {
|
|
|
4905
6364
|
model: opts?.model,
|
|
4906
6365
|
tools: opts?.tools
|
|
4907
6366
|
};
|
|
4908
|
-
const transcriptPath = this.sessionFactory ?
|
|
6367
|
+
const transcriptPath = this.sessionFactory ? path17.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
4909
6368
|
if (this.director) {
|
|
4910
6369
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
4911
6370
|
const taskId2 = randomUUID();
|
|
@@ -4974,8 +6433,16 @@ var MultiAgentHost = class {
|
|
|
4974
6433
|
description: v.description,
|
|
4975
6434
|
subagentId: v.subagentId
|
|
4976
6435
|
}));
|
|
4977
|
-
const
|
|
4978
|
-
|
|
6436
|
+
const live = [];
|
|
6437
|
+
if (this.coordinator) {
|
|
6438
|
+
const s = this.coordinator.getStatus();
|
|
6439
|
+
for (const a of s.subagents) {
|
|
6440
|
+
live.push({ subagentId: a.id, status: a.status, task: a.currentTask });
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
|
|
6444
|
+
const summary = !this.coordinator ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${this.results.length} completed.` : `${pending.length} pending, ${this.results.length} completed.`;
|
|
6445
|
+
return { pending, completed: this.results, live, summary };
|
|
4979
6446
|
}
|
|
4980
6447
|
/**
|
|
4981
6448
|
* Roll up per-subagent runtime cost from completed TaskResults. We don't
|
|
@@ -5057,16 +6524,16 @@ var MultiAgentHost = class {
|
|
|
5057
6524
|
}
|
|
5058
6525
|
this.opts.directorMode = true;
|
|
5059
6526
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
5060
|
-
this.opts.manifestPath =
|
|
6527
|
+
this.opts.manifestPath = path17.join(this.opts.fleetRoot, "fleet.json");
|
|
5061
6528
|
}
|
|
5062
6529
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
5063
|
-
this.opts.sharedScratchpadPath =
|
|
6530
|
+
this.opts.sharedScratchpadPath = path17.join(this.opts.fleetRoot, "shared");
|
|
5064
6531
|
}
|
|
5065
6532
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
5066
|
-
this.opts.sessionsRoot =
|
|
6533
|
+
this.opts.sessionsRoot = path17.join(this.opts.fleetRoot, "subagents");
|
|
5067
6534
|
}
|
|
5068
6535
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
5069
|
-
this.opts.stateCheckpointPath =
|
|
6536
|
+
this.opts.stateCheckpointPath = path17.join(this.opts.fleetRoot, "director-state.json");
|
|
5070
6537
|
}
|
|
5071
6538
|
await this.ensureDirector();
|
|
5072
6539
|
return this.director ?? null;
|
|
@@ -5187,11 +6654,11 @@ var SessionStats = class {
|
|
|
5187
6654
|
if (e.name === "bash") this.bashCommands++;
|
|
5188
6655
|
else if (e.name === "fetch") this.fetches++;
|
|
5189
6656
|
if (!e.ok) return;
|
|
5190
|
-
const
|
|
5191
|
-
if (e.name === "read" &&
|
|
5192
|
-
else if (e.name === "edit" &&
|
|
5193
|
-
else if (e.name === "write" &&
|
|
5194
|
-
this.writtenPaths.add(
|
|
6657
|
+
const path18 = typeof input?.path === "string" ? input.path : void 0;
|
|
6658
|
+
if (e.name === "read" && path18) this.readPaths.add(path18);
|
|
6659
|
+
else if (e.name === "edit" && path18) this.editedPaths.add(path18);
|
|
6660
|
+
else if (e.name === "write" && path18) {
|
|
6661
|
+
this.writtenPaths.add(path18);
|
|
5195
6662
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
5196
6663
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
5197
6664
|
}
|
|
@@ -5397,27 +6864,14 @@ async function setupCompaction(params) {
|
|
|
5397
6864
|
const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
|
|
5398
6865
|
const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
|
|
5399
6866
|
const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
|
|
6867
|
+
let autoCompactor;
|
|
5400
6868
|
if (config.context.autoCompact !== false) {
|
|
5401
|
-
|
|
6869
|
+
autoCompactor = new AutoCompactionMiddleware(
|
|
5402
6870
|
compactor,
|
|
5403
6871
|
effectiveMaxContext,
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
if (typeof m.content === "string") {
|
|
5408
|
-
total += Math.ceil(m.content.length / 4);
|
|
5409
|
-
} else if (Array.isArray(m.content)) {
|
|
5410
|
-
for (const b of m.content) {
|
|
5411
|
-
if (b.type === "text") {
|
|
5412
|
-
total += Math.ceil(b.text.length / 4);
|
|
5413
|
-
} else if (b.type === "tool_use" || b.type === "tool_result") {
|
|
5414
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
5415
|
-
}
|
|
5416
|
-
}
|
|
5417
|
-
}
|
|
5418
|
-
}
|
|
5419
|
-
return total;
|
|
5420
|
-
},
|
|
6872
|
+
// Use the full API request estimator: messages + system prompt + tool definitions.
|
|
6873
|
+
// This matches what the provider actually counts as input tokens.
|
|
6874
|
+
(ctx) => estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total,
|
|
5421
6875
|
{
|
|
5422
6876
|
warn: config.context.warnThreshold,
|
|
5423
6877
|
soft: config.context.softThreshold,
|
|
@@ -5427,7 +6881,7 @@ async function setupCompaction(params) {
|
|
|
5427
6881
|
);
|
|
5428
6882
|
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
5429
6883
|
}
|
|
5430
|
-
return effectiveMaxContext;
|
|
6884
|
+
return { effectiveMaxContext, autoCompactor };
|
|
5431
6885
|
}
|
|
5432
6886
|
function createAgent(params) {
|
|
5433
6887
|
return new Agent({
|
|
@@ -5535,12 +6989,12 @@ async function setupSession(params) {
|
|
|
5535
6989
|
}
|
|
5536
6990
|
const sessionRef = { current: session };
|
|
5537
6991
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
5538
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
5539
|
-
const queueStore = new QueueStore({ dir:
|
|
6992
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path17.join(wpaths.projectSessions, session.id, "attachments") });
|
|
6993
|
+
const queueStore = new QueueStore({ dir: path17.join(wpaths.projectSessions, session.id) });
|
|
5540
6994
|
const ctxSignal = new AbortController().signal;
|
|
5541
6995
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
5542
6996
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
5543
|
-
const todosCheckpointPath =
|
|
6997
|
+
const todosCheckpointPath = path17.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
5544
6998
|
if (resumeId) {
|
|
5545
6999
|
try {
|
|
5546
7000
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -5552,12 +7006,12 @@ async function setupSession(params) {
|
|
|
5552
7006
|
}
|
|
5553
7007
|
}
|
|
5554
7008
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
5555
|
-
const planPath =
|
|
7009
|
+
const planPath = path17.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
5556
7010
|
context.state.setMeta("plan.path", planPath);
|
|
5557
7011
|
if (resumeId) {
|
|
5558
7012
|
try {
|
|
5559
|
-
const fleetRoot =
|
|
5560
|
-
const dirState = await loadDirectorState(
|
|
7013
|
+
const fleetRoot = path17.join(wpaths.projectSessions, session.id);
|
|
7014
|
+
const dirState = await loadDirectorState(path17.join(fleetRoot, "director-state.json"));
|
|
5561
7015
|
if (dirState) {
|
|
5562
7016
|
const tCounts = {};
|
|
5563
7017
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -5584,7 +7038,7 @@ function resolveBundledSkillsDir2() {
|
|
|
5584
7038
|
try {
|
|
5585
7039
|
const req2 = createRequire(import.meta.url);
|
|
5586
7040
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
5587
|
-
return
|
|
7041
|
+
return path17.join(path17.dirname(corePkg), "skills");
|
|
5588
7042
|
} catch {
|
|
5589
7043
|
return void 0;
|
|
5590
7044
|
}
|
|
@@ -5671,7 +7125,7 @@ async function main(argv) {
|
|
|
5671
7125
|
modeId,
|
|
5672
7126
|
modePrompt,
|
|
5673
7127
|
modelCapabilities,
|
|
5674
|
-
planPath: () => sessionRef.current ?
|
|
7128
|
+
planPath: () => sessionRef.current ? path17.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
5675
7129
|
})
|
|
5676
7130
|
);
|
|
5677
7131
|
const toolRegistry = new ToolRegistry();
|
|
@@ -5716,7 +7170,7 @@ async function main(argv) {
|
|
|
5716
7170
|
const dumpMetrics = () => {
|
|
5717
7171
|
if (!metricsSink) return;
|
|
5718
7172
|
try {
|
|
5719
|
-
const out =
|
|
7173
|
+
const out = path17.join(wpaths.projectSessions, "metrics.json");
|
|
5720
7174
|
const snap = metricsSink.snapshot();
|
|
5721
7175
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
5722
7176
|
} catch {
|
|
@@ -5844,7 +7298,13 @@ async function main(argv) {
|
|
|
5844
7298
|
});
|
|
5845
7299
|
const pipelines = setupPipelines({ events, logger });
|
|
5846
7300
|
const compactor = container.resolve(TOKENS.Compactor);
|
|
5847
|
-
const effectiveMaxContext = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
|
|
7301
|
+
const { effectiveMaxContext, autoCompactor } = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
|
|
7302
|
+
const refreshMaxContext = async (providerId, modelId) => {
|
|
7303
|
+
if (!autoCompactor) return;
|
|
7304
|
+
const cap = await capabilitiesFor(modelsRegistry, providerId, modelId).catch(() => void 0);
|
|
7305
|
+
const mc = cap?.maxContext ?? config.context.effectiveMaxContext ?? 2e5;
|
|
7306
|
+
autoCompactor.setMaxContext(mc);
|
|
7307
|
+
};
|
|
5848
7308
|
const updateSpinnerContext = () => {
|
|
5849
7309
|
if (effectiveMaxContext > 0 && lastInputTokens > 0) {
|
|
5850
7310
|
spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
|
|
@@ -5907,17 +7367,20 @@ async function main(argv) {
|
|
|
5907
7367
|
}
|
|
5908
7368
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
5909
7369
|
try {
|
|
5910
|
-
const
|
|
7370
|
+
const savedCfg = config.providers?.[providerId];
|
|
7371
|
+
const resolvedProviderId = savedCfg?.type ?? providerId;
|
|
7372
|
+
const newCfg = savedCfg ?? {
|
|
5911
7373
|
type: providerId,
|
|
5912
7374
|
apiKey: config.apiKey,
|
|
5913
7375
|
baseUrl: config.baseUrl
|
|
5914
7376
|
};
|
|
5915
|
-
const cfgWithType = { ...newCfg, type:
|
|
5916
|
-
const newProvider = config.features.modelsRegistry && providerRegistry.has(
|
|
7377
|
+
const cfgWithType = { ...newCfg, type: resolvedProviderId };
|
|
7378
|
+
const newProvider = config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
|
|
5917
7379
|
context.provider = newProvider;
|
|
5918
7380
|
context.model = modelId;
|
|
5919
7381
|
config = patchConfig(config, { provider: providerId, model: modelId });
|
|
5920
7382
|
configStore.update({ provider: providerId, model: modelId });
|
|
7383
|
+
void refreshMaxContext(resolvedProviderId, modelId);
|
|
5921
7384
|
return null;
|
|
5922
7385
|
} catch (err) {
|
|
5923
7386
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -5925,12 +7388,13 @@ async function main(argv) {
|
|
|
5925
7388
|
};
|
|
5926
7389
|
const directorMode = flags["director"] === true;
|
|
5927
7390
|
let director = null;
|
|
5928
|
-
|
|
5929
|
-
const
|
|
5930
|
-
const
|
|
5931
|
-
const
|
|
5932
|
-
const
|
|
5933
|
-
const
|
|
7391
|
+
let autonomyMode = "off";
|
|
7392
|
+
const fleetRoot = directorMode ? path17.join(wpaths.projectSessions, session.id) : void 0;
|
|
7393
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path17.join(fleetRoot, "fleet.json") : void 0;
|
|
7394
|
+
const sharedScratchpadPath = directorMode ? path17.join(fleetRoot, "shared") : void 0;
|
|
7395
|
+
const subagentSessionsRoot = directorMode ? path17.join(fleetRoot, "subagents") : void 0;
|
|
7396
|
+
const stateCheckpointPath = directorMode ? path17.join(fleetRoot, "director-state.json") : void 0;
|
|
7397
|
+
const fleetRootForPromotion = path17.join(wpaths.projectSessions, session.id);
|
|
5934
7398
|
const multiAgentHost = new MultiAgentHost(
|
|
5935
7399
|
{
|
|
5936
7400
|
container,
|
|
@@ -6001,6 +7465,7 @@ async function main(argv) {
|
|
|
6001
7465
|
metricsSink,
|
|
6002
7466
|
healthRegistry,
|
|
6003
7467
|
planPath,
|
|
7468
|
+
modeStore,
|
|
6004
7469
|
fleetStreamController,
|
|
6005
7470
|
onSpawn: async (description, spawnOpts) => {
|
|
6006
7471
|
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
@@ -6014,8 +7479,19 @@ async function main(argv) {
|
|
|
6014
7479
|
onAgents: () => {
|
|
6015
7480
|
const s = multiAgentHost.status();
|
|
6016
7481
|
const lines = [s.summary];
|
|
7482
|
+
const STATUS_ICON = {
|
|
7483
|
+
running: "\u25CF",
|
|
7484
|
+
idle: "\u25CB",
|
|
7485
|
+
stopped: "\u2298"
|
|
7486
|
+
};
|
|
7487
|
+
for (const a of s.live) {
|
|
7488
|
+
if (a.status === "running" || a.status === "idle") {
|
|
7489
|
+
const task = a.task ? ` \u2014 ${a.task.slice(0, 60)}` : "";
|
|
7490
|
+
lines.push(` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`);
|
|
7491
|
+
}
|
|
7492
|
+
}
|
|
6017
7493
|
for (const p of s.pending) {
|
|
6018
|
-
lines.push(` pending ${p.taskId.slice(0, 8)} \u2192 ${p.description.slice(0, 60)}`);
|
|
7494
|
+
lines.push(` \xB7 pending ${p.taskId.slice(0, 8)} \u2192 ${p.description.slice(0, 60)}`);
|
|
6019
7495
|
}
|
|
6020
7496
|
for (const r of s.completed) {
|
|
6021
7497
|
const fmt = fmtTaskResultLine(r, color);
|
|
@@ -6027,11 +7503,26 @@ async function main(argv) {
|
|
|
6027
7503
|
if (action === "status") {
|
|
6028
7504
|
const s = multiAgentHost.status();
|
|
6029
7505
|
const lines = [color.bold("Fleet status"), ` ${s.summary}`];
|
|
7506
|
+
const STATUS_ICON = {
|
|
7507
|
+
running: "\u25CF",
|
|
7508
|
+
idle: "\u25CB",
|
|
7509
|
+
stopped: "\u2298"
|
|
7510
|
+
};
|
|
7511
|
+
const liveActive = s.live.filter((a) => a.status === "running" || a.status === "idle");
|
|
7512
|
+
if (liveActive.length > 0) {
|
|
7513
|
+
lines.push("", color.dim(" Active"));
|
|
7514
|
+
for (const a of liveActive) {
|
|
7515
|
+
const task = a.task ? ` \xB7 ${a.task.slice(0, 50)}` : "";
|
|
7516
|
+
lines.push(
|
|
7517
|
+
` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`
|
|
7518
|
+
);
|
|
7519
|
+
}
|
|
7520
|
+
}
|
|
6030
7521
|
if (s.pending.length > 0) {
|
|
6031
7522
|
lines.push("", color.dim(" Pending"));
|
|
6032
7523
|
for (const p of s.pending) {
|
|
6033
7524
|
lines.push(
|
|
6034
|
-
` ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`
|
|
7525
|
+
` \xB7 ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`
|
|
6035
7526
|
);
|
|
6036
7527
|
}
|
|
6037
7528
|
}
|
|
@@ -6082,7 +7573,7 @@ async function main(argv) {
|
|
|
6082
7573
|
return `Unknown fleet action: ${action}`;
|
|
6083
7574
|
},
|
|
6084
7575
|
onFleetLog: async (subagentId, mode) => {
|
|
6085
|
-
const subagentsRoot =
|
|
7576
|
+
const subagentsRoot = path17.join(fleetRootForPromotion, "subagents");
|
|
6086
7577
|
let runDirs;
|
|
6087
7578
|
try {
|
|
6088
7579
|
runDirs = await fs14.readdir(subagentsRoot);
|
|
@@ -6091,7 +7582,7 @@ async function main(argv) {
|
|
|
6091
7582
|
}
|
|
6092
7583
|
const found = [];
|
|
6093
7584
|
for (const runId of runDirs) {
|
|
6094
|
-
const runDir =
|
|
7585
|
+
const runDir = path17.join(subagentsRoot, runId);
|
|
6095
7586
|
let files;
|
|
6096
7587
|
try {
|
|
6097
7588
|
files = await fs14.readdir(runDir);
|
|
@@ -6100,7 +7591,7 @@ async function main(argv) {
|
|
|
6100
7591
|
}
|
|
6101
7592
|
for (const f of files) {
|
|
6102
7593
|
if (!f.endsWith(".jsonl")) continue;
|
|
6103
|
-
const full =
|
|
7594
|
+
const full = path17.join(runDir, f);
|
|
6104
7595
|
try {
|
|
6105
7596
|
const stat2 = await fs14.stat(full);
|
|
6106
7597
|
found.push({
|
|
@@ -6197,7 +7688,7 @@ async function main(argv) {
|
|
|
6197
7688
|
}
|
|
6198
7689
|
const dir = await multiAgentHost.ensureDirector();
|
|
6199
7690
|
if (!dir) return "Director is not available.";
|
|
6200
|
-
const dirStatePath =
|
|
7691
|
+
const dirStatePath = path17.join(fleetRootForPromotion, "director-state.json");
|
|
6201
7692
|
const prior = await loadDirectorState(dirStatePath);
|
|
6202
7693
|
if (!prior) {
|
|
6203
7694
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -6268,9 +7759,9 @@ async function main(argv) {
|
|
|
6268
7759
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
6269
7760
|
toolRegistry.register(tool);
|
|
6270
7761
|
}
|
|
6271
|
-
const mp =
|
|
6272
|
-
const sp =
|
|
6273
|
-
const ss =
|
|
7762
|
+
const mp = path17.join(fleetRootForPromotion, "fleet.json");
|
|
7763
|
+
const sp = path17.join(fleetRootForPromotion, "shared");
|
|
7764
|
+
const ss = path17.join(fleetRootForPromotion, "subagents");
|
|
6274
7765
|
const lines = [
|
|
6275
7766
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
6276
7767
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -6297,6 +7788,22 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
6297
7788
|
}
|
|
6298
7789
|
return result.message;
|
|
6299
7790
|
},
|
|
7791
|
+
onYolo: (setTo) => {
|
|
7792
|
+
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
7793
|
+
if (setTo !== void 0) {
|
|
7794
|
+
policy.setYolo(setTo);
|
|
7795
|
+
config = patchConfig(config, { yolo: setTo });
|
|
7796
|
+
return setTo;
|
|
7797
|
+
}
|
|
7798
|
+
return policy.getYolo();
|
|
7799
|
+
},
|
|
7800
|
+
onAutonomy: (setTo) => {
|
|
7801
|
+
if (setTo !== void 0) {
|
|
7802
|
+
autonomyMode = setTo;
|
|
7803
|
+
return setTo;
|
|
7804
|
+
}
|
|
7805
|
+
return autonomyMode;
|
|
7806
|
+
},
|
|
6300
7807
|
onExit: () => {
|
|
6301
7808
|
void mcpRegistry.stopAll();
|
|
6302
7809
|
},
|
|
@@ -6359,7 +7866,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
6359
7866
|
switchProviderAndModel,
|
|
6360
7867
|
director: director ?? null,
|
|
6361
7868
|
fleetRoster: FLEET_ROSTER,
|
|
6362
|
-
fleetStreamController
|
|
7869
|
+
fleetStreamController,
|
|
7870
|
+
getYolo: () => {
|
|
7871
|
+
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
7872
|
+
return policy.getYolo();
|
|
7873
|
+
},
|
|
7874
|
+
getAutonomy: () => autonomyMode,
|
|
7875
|
+
skillLoader: config.features.skills ? skillLoader : void 0
|
|
6363
7876
|
});
|
|
6364
7877
|
}
|
|
6365
7878
|
async function promptRecovery(reader, renderer, abandoned, autoRecover) {
|