@wrongstack/cli 0.9.19 → 0.9.20
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 +614 -40
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as path24 from 'path';
|
|
3
3
|
import * as fsp3 from 'fs/promises';
|
|
4
|
-
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, InputBuilder, FsError, ERROR_CODES, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
4
|
+
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, InputBuilder, FsError, ERROR_CODES, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
6
|
import * as os3 from 'os';
|
|
7
7
|
import os3__default from 'os';
|
|
@@ -380,12 +380,9 @@ function buildSddCommand(opts) {
|
|
|
380
380
|
name: "sdd",
|
|
381
381
|
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
382
382
|
async run(args) {
|
|
383
|
-
opts.context;
|
|
384
383
|
if (!opts.paths) return { message: "SDD not available \u2014 paths not configured." };
|
|
385
384
|
const specsDir = opts.paths.projectSpecs;
|
|
386
|
-
const graphsDir = opts.paths.projectTaskGraphs;
|
|
387
385
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
388
|
-
new TaskGraphStore({ baseDir: graphsDir });
|
|
389
386
|
const versioning = sddState.getVersioning();
|
|
390
387
|
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
391
388
|
const restJoined = rest.join(" ").trim();
|
|
@@ -1024,8 +1021,8 @@ Start executing the tasks one by one.`
|
|
|
1024
1021
|
return { message: lines2.join("\n") };
|
|
1025
1022
|
}
|
|
1026
1023
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
const stored = await
|
|
1024
|
+
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
1025
|
+
const stored = await graphStore.load(graphId);
|
|
1029
1026
|
if (stored) {
|
|
1030
1027
|
return { message: renderTaskGraph(stored, { compact: false }) };
|
|
1031
1028
|
}
|
|
@@ -1245,8 +1242,8 @@ ${lines.join("\n")}`
|
|
|
1245
1242
|
return { message: "No task graph found. Generate tasks first." };
|
|
1246
1243
|
}
|
|
1247
1244
|
try {
|
|
1248
|
-
const
|
|
1249
|
-
const graph = await
|
|
1245
|
+
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
1246
|
+
const graph = await graphStore.load(graphId);
|
|
1250
1247
|
if (!graph) {
|
|
1251
1248
|
return { message: "Could not load task graph." };
|
|
1252
1249
|
}
|
|
@@ -1659,7 +1656,7 @@ async function checkForUpdate(signal, homeFn) {
|
|
|
1659
1656
|
outdated: isNewer(latest, current),
|
|
1660
1657
|
checkFailed: false
|
|
1661
1658
|
};
|
|
1662
|
-
} catch (
|
|
1659
|
+
} catch (_err) {
|
|
1663
1660
|
if (aborted()) {
|
|
1664
1661
|
return { current, latest: current, outdated: false, checkFailed: true };
|
|
1665
1662
|
}
|
|
@@ -1980,7 +1977,7 @@ async function runWebUI(opts) {
|
|
|
1980
1977
|
}
|
|
1981
1978
|
}
|
|
1982
1979
|
}
|
|
1983
|
-
async function handleUserMessage(ws,
|
|
1980
|
+
async function handleUserMessage(ws, _client, content) {
|
|
1984
1981
|
if (abortController) {
|
|
1985
1982
|
send(ws, {
|
|
1986
1983
|
type: "error",
|
|
@@ -3553,8 +3550,85 @@ function buildLoadCommand(opts) {
|
|
|
3553
3550
|
return {
|
|
3554
3551
|
name: "resume",
|
|
3555
3552
|
aliases: ["load", "sessions"],
|
|
3556
|
-
description: "List recent sessions.",
|
|
3557
|
-
async run() {
|
|
3553
|
+
description: "List recent sessions, show incomplete ones (--incomplete), or plan a recovery (--recover <id>).",
|
|
3554
|
+
async run(args) {
|
|
3555
|
+
const parts = args.split(/\s+/).filter(Boolean);
|
|
3556
|
+
const showIncomplete = parts.includes("--incomplete") || parts.includes("-i");
|
|
3557
|
+
const recoverIdx = parts.findIndex((p) => p === "--recover");
|
|
3558
|
+
const recoverTarget = recoverIdx >= 0 ? parts[recoverIdx + 1] : void 0;
|
|
3559
|
+
if (recoverTarget) {
|
|
3560
|
+
if (!opts.paths) {
|
|
3561
|
+
return { message: color.yellow("No paths configured \u2014 cannot build a recovery plan.") };
|
|
3562
|
+
}
|
|
3563
|
+
const recovery = new SessionRecovery(opts.paths.projectSessions);
|
|
3564
|
+
const plan = await recovery.recover(recoverTarget);
|
|
3565
|
+
if (!plan) {
|
|
3566
|
+
return {
|
|
3567
|
+
message: color.yellow(
|
|
3568
|
+
`No session log found for ${recoverTarget} (or it is empty).`
|
|
3569
|
+
)
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
const lines2 = [
|
|
3573
|
+
color.bold(`Recovery plan for ${plan.sessionId}`),
|
|
3574
|
+
` Stale: ${plan.stale ? color.yellow("yes") : color.green("no")}`
|
|
3575
|
+
];
|
|
3576
|
+
if (plan.context) {
|
|
3577
|
+
lines2.push(` Last in-flight context: ${color.cyan(plan.context)}`);
|
|
3578
|
+
}
|
|
3579
|
+
if (plan.lastCheckpoint && plan.lastCheckpoint.type === "checkpoint") {
|
|
3580
|
+
const cp = plan.lastCheckpoint;
|
|
3581
|
+
lines2.push(
|
|
3582
|
+
` Last checkpoint: promptIndex=${cp.promptIndex} preview=${color.dim(`"${cp.promptPreview}"`)} at ${color.dim(cp.ts)}`
|
|
3583
|
+
);
|
|
3584
|
+
} else {
|
|
3585
|
+
lines2.push(` Last checkpoint: ${color.dim("(none \u2014 full re-execution)")}`);
|
|
3586
|
+
}
|
|
3587
|
+
lines2.push(
|
|
3588
|
+
` Pending events: ${plan.pendingEvents.length} (the work that would re-run on resume)`
|
|
3589
|
+
);
|
|
3590
|
+
if (plan.pendingEvents.length > 0) {
|
|
3591
|
+
const summary = summarizePending(plan.pendingEvents);
|
|
3592
|
+
lines2.push(...summary);
|
|
3593
|
+
}
|
|
3594
|
+
lines2.push("");
|
|
3595
|
+
lines2.push(
|
|
3596
|
+
color.dim(
|
|
3597
|
+
plan.stale ? " This session crashed mid-iteration. The full re-execution kernel is coming in a follow-up; for now use this plan to decide whether to start fresh." : " This session ended cleanly; the plan above describes the most recent turn(s) for context."
|
|
3598
|
+
)
|
|
3599
|
+
);
|
|
3600
|
+
return { message: lines2.join("\n") };
|
|
3601
|
+
}
|
|
3602
|
+
if (showIncomplete) {
|
|
3603
|
+
if (!opts.paths) {
|
|
3604
|
+
return { message: color.yellow("No paths configured \u2014 cannot scan for incomplete sessions.") };
|
|
3605
|
+
}
|
|
3606
|
+
const recovery = new SessionRecovery(opts.paths.projectSessions);
|
|
3607
|
+
const resumable = await recovery.listResumable();
|
|
3608
|
+
if (resumable.length === 0) {
|
|
3609
|
+
return {
|
|
3610
|
+
message: color.dim("No incomplete sessions. (Every recorded run ended cleanly.)")
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
const lines2 = [
|
|
3614
|
+
color.bold(`${resumable.length} incomplete session(s)`),
|
|
3615
|
+
color.dim(" (process died mid-iteration; the last event was in_flight_start)"),
|
|
3616
|
+
""
|
|
3617
|
+
];
|
|
3618
|
+
for (const s of resumable) {
|
|
3619
|
+
const t = color.dim(s.lastEventTs.slice(0, 19).replace("T", " "));
|
|
3620
|
+
lines2.push(
|
|
3621
|
+
` ${color.cyan(s.sessionId)} ${t} ${color.dim(`${s.eventCount} events`)} ${s.context}`
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
lines2.push("");
|
|
3625
|
+
lines2.push(
|
|
3626
|
+
color.dim(
|
|
3627
|
+
" Resuming re-executes the in-flight work from the last checkpoint. Full resume is coming in a follow-up; for now use this list to identify crashes and decide whether to restart fresh."
|
|
3628
|
+
)
|
|
3629
|
+
);
|
|
3630
|
+
return { message: lines2.join("\n") };
|
|
3631
|
+
}
|
|
3558
3632
|
if (!opts.sessionStore) return { message: "No session store configured." };
|
|
3559
3633
|
const list = await opts.sessionStore.list(10);
|
|
3560
3634
|
if (list.length === 0) return { message: "No saved sessions." };
|
|
@@ -3565,7 +3639,8 @@ function buildLoadCommand(opts) {
|
|
|
3565
3639
|
${lines.join("\n")}
|
|
3566
3640
|
|
|
3567
3641
|
${color.dim(`Resume one with: wstack resume ${list[0]?.id ?? "<id>"}
|
|
3568
|
-
`)}
|
|
3642
|
+
`)}
|
|
3643
|
+
${color.dim("Tip: /resume --incomplete \u2014 list sessions that crashed mid-iteration")}`;
|
|
3569
3644
|
opts.renderer.write(msg);
|
|
3570
3645
|
return { message: msg };
|
|
3571
3646
|
}
|
|
@@ -3589,6 +3664,47 @@ function buildExitCommand(opts) {
|
|
|
3589
3664
|
}
|
|
3590
3665
|
};
|
|
3591
3666
|
}
|
|
3667
|
+
function summarizePending(events) {
|
|
3668
|
+
const lines = [];
|
|
3669
|
+
const cap = 12;
|
|
3670
|
+
for (const ev of events.slice(-cap)) {
|
|
3671
|
+
const t = "ts" in ev ? color.dim(String(ev.ts).slice(11, 19)) : color.dim("--:--:--");
|
|
3672
|
+
const kind = color.cyan(String(ev.type).padEnd(18));
|
|
3673
|
+
lines.push(` ${t} ${kind} ${summariseEvent(ev)}`);
|
|
3674
|
+
}
|
|
3675
|
+
if (events.length > cap) {
|
|
3676
|
+
lines.push(color.dim(` \u2026 and ${events.length - cap} more`));
|
|
3677
|
+
}
|
|
3678
|
+
return lines;
|
|
3679
|
+
}
|
|
3680
|
+
function summariseEvent(ev) {
|
|
3681
|
+
switch (ev.type) {
|
|
3682
|
+
case "user_input": {
|
|
3683
|
+
const text = "text" in ev && typeof ev.text === "string" ? ev.text : Array.isArray(ev.content) ? "\u2026" : "";
|
|
3684
|
+
return color.dim(text.length > 60 ? text.slice(0, 59) + "\u2026" : text);
|
|
3685
|
+
}
|
|
3686
|
+
case "llm_response":
|
|
3687
|
+
return color.dim("(model reply)");
|
|
3688
|
+
case "tool_use":
|
|
3689
|
+
return color.dim(`name=${ev.name ?? "?"}`);
|
|
3690
|
+
case "tool_result":
|
|
3691
|
+
return color.dim(`name=${ev.name ?? "?"}`);
|
|
3692
|
+
case "in_flight_start":
|
|
3693
|
+
return color.dim(`context="${ev.context ?? ""}"`);
|
|
3694
|
+
case "in_flight_end":
|
|
3695
|
+
return color.dim(`reason=${ev.reason ?? "?"}`);
|
|
3696
|
+
case "checkpoint":
|
|
3697
|
+
return color.dim(
|
|
3698
|
+
`promptIndex=${ev.promptIndex ?? "?"}`
|
|
3699
|
+
);
|
|
3700
|
+
case "compaction":
|
|
3701
|
+
return color.dim("(compaction)");
|
|
3702
|
+
case "error":
|
|
3703
|
+
return color.red(String(ev.message ?? ""));
|
|
3704
|
+
default:
|
|
3705
|
+
return color.dim("\u2026");
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3592
3708
|
|
|
3593
3709
|
// src/slash-commands/spawn-agents.ts
|
|
3594
3710
|
function buildSpawnCommand(opts) {
|
|
@@ -4173,8 +4289,48 @@ ${lines.join("\n")}`;
|
|
|
4173
4289
|
}
|
|
4174
4290
|
};
|
|
4175
4291
|
}
|
|
4176
|
-
|
|
4177
|
-
|
|
4292
|
+
async function runModePicker(modeStore, reader) {
|
|
4293
|
+
const modes = await modeStore.listModes();
|
|
4294
|
+
const active = await modeStore.getActiveMode();
|
|
4295
|
+
const activeIdx = modes.findIndex((m) => m.id === active?.id);
|
|
4296
|
+
let cursor = activeIdx >= 0 ? activeIdx : 0;
|
|
4297
|
+
const render = (currentCursor) => {
|
|
4298
|
+
const lines = [];
|
|
4299
|
+
lines.push(`
|
|
4300
|
+
${color.bold(color.amber("WrongStack") + color.dim(" \u2014 Mode Selection"))}
|
|
4301
|
+
`);
|
|
4302
|
+
lines.push(color.dim(" \u2191\u2193 navigate Enter select q quit\n"));
|
|
4303
|
+
lines.push("");
|
|
4304
|
+
for (let i = 0; i < modes.length; i++) {
|
|
4305
|
+
const m = modes[i];
|
|
4306
|
+
const mark = m.id === active?.id ? color.green(" [active]") : "";
|
|
4307
|
+
const prefix = i === currentCursor ? color.bold("\u276F ") : " ";
|
|
4308
|
+
const name = i === currentCursor ? color.bold(m.name) : m.name;
|
|
4309
|
+
lines.push(` ${prefix}${name} ${color.dim(m.description)}${mark}`);
|
|
4310
|
+
}
|
|
4311
|
+
lines.push("");
|
|
4312
|
+
return lines.join("\n");
|
|
4313
|
+
};
|
|
4314
|
+
process.stdout.write(render(cursor));
|
|
4315
|
+
const options = [
|
|
4316
|
+
{ key: "\x1B[A", label: "\u2191", value: "up" },
|
|
4317
|
+
{ key: "\x1B[B", label: "\u2193", value: "down" },
|
|
4318
|
+
{ key: "\r", label: "Enter", value: "enter" },
|
|
4319
|
+
{ key: "q", label: "q", value: "quit" }
|
|
4320
|
+
];
|
|
4321
|
+
while (true) {
|
|
4322
|
+
const answer = await reader.readKey("", options);
|
|
4323
|
+
if (answer === "quit") return void 0;
|
|
4324
|
+
if (answer === "enter") return modes[cursor];
|
|
4325
|
+
if (answer === "up") {
|
|
4326
|
+
cursor = cursor > 0 ? cursor - 1 : modes.length - 1;
|
|
4327
|
+
} else if (answer === "down") {
|
|
4328
|
+
cursor = cursor < modes.length - 1 ? cursor + 1 : 0;
|
|
4329
|
+
}
|
|
4330
|
+
process.stdout.write("\x1B[J");
|
|
4331
|
+
process.stdout.write(render(cursor));
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4178
4334
|
function buildModeCommand(opts) {
|
|
4179
4335
|
return {
|
|
4180
4336
|
name: "mode",
|
|
@@ -4194,7 +4350,7 @@ function buildModeCommand(opts) {
|
|
|
4194
4350
|
" /mode brief Switch to brief mode",
|
|
4195
4351
|
" /mode teach Switch to teach mode"
|
|
4196
4352
|
].join("\n"),
|
|
4197
|
-
async run(args) {
|
|
4353
|
+
async run(args, _ctx) {
|
|
4198
4354
|
const modeStore = opts.modeStore;
|
|
4199
4355
|
if (!modeStore) {
|
|
4200
4356
|
return { message: "Mode store not available in this context." };
|
|
@@ -4202,6 +4358,17 @@ function buildModeCommand(opts) {
|
|
|
4202
4358
|
const modes = await modeStore.listModes();
|
|
4203
4359
|
const active = await modeStore.getActiveMode();
|
|
4204
4360
|
if (!args.trim()) {
|
|
4361
|
+
if (opts.inputReader) {
|
|
4362
|
+
const selected = await runModePicker(modeStore, opts.inputReader);
|
|
4363
|
+
if (!selected) {
|
|
4364
|
+
return { message: "Mode selection cancelled." };
|
|
4365
|
+
}
|
|
4366
|
+
await modeStore.setActiveMode(selected.id);
|
|
4367
|
+
return {
|
|
4368
|
+
message: `Switched to "${selected.name}" mode.
|
|
4369
|
+
${selected.description}`
|
|
4370
|
+
};
|
|
4371
|
+
}
|
|
4205
4372
|
const lines = [`Current mode: ${active?.name ?? "none"}`, "", "Available modes:"];
|
|
4206
4373
|
for (const m of modes) {
|
|
4207
4374
|
const mark = m.id === active?.id ? " [active]" : "";
|
|
@@ -5306,6 +5473,195 @@ function buildAutoPhaseCommand(opts) {
|
|
|
5306
5473
|
}
|
|
5307
5474
|
};
|
|
5308
5475
|
}
|
|
5476
|
+
function buildCollabCommand(opts) {
|
|
5477
|
+
return {
|
|
5478
|
+
name: "collab",
|
|
5479
|
+
description: "Live collaboration helpers (status / invite / history).",
|
|
5480
|
+
async run(args, ctx) {
|
|
5481
|
+
const parts = args.split(/\s+/).filter(Boolean);
|
|
5482
|
+
const sub = (parts[0] ?? "status").toLowerCase();
|
|
5483
|
+
switch (sub) {
|
|
5484
|
+
case "status":
|
|
5485
|
+
return statusCommand(ctx.session?.id);
|
|
5486
|
+
case "invite":
|
|
5487
|
+
return inviteCommand(ctx.session?.id);
|
|
5488
|
+
case "history":
|
|
5489
|
+
return historyCommand(opts, ctx.session?.id, parts.slice(1));
|
|
5490
|
+
case "annotations":
|
|
5491
|
+
case "notes":
|
|
5492
|
+
return annotationsCommand(opts, ctx.session?.id);
|
|
5493
|
+
case "help":
|
|
5494
|
+
case "--help":
|
|
5495
|
+
case "-h":
|
|
5496
|
+
return helpCommand();
|
|
5497
|
+
default:
|
|
5498
|
+
return {
|
|
5499
|
+
message: color.yellow(
|
|
5500
|
+
`Unknown subcommand: ${sub}. Try: /collab status | invite | history | annotations`
|
|
5501
|
+
)
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
};
|
|
5506
|
+
}
|
|
5507
|
+
function statusCommand(sessionId) {
|
|
5508
|
+
const lines = [];
|
|
5509
|
+
lines.push(color.bold("Live collaboration"));
|
|
5510
|
+
lines.push(` Session: ${color.cyan(sessionId ?? "(none)")}`);
|
|
5511
|
+
lines.push(` Observers: ${color.dim("unknown (check webui)")}`);
|
|
5512
|
+
lines.push("");
|
|
5513
|
+
lines.push(
|
|
5514
|
+
color.dim(
|
|
5515
|
+
" Tip: /collab invite \u2014 print a webui join link for a teammate"
|
|
5516
|
+
)
|
|
5517
|
+
);
|
|
5518
|
+
lines.push(
|
|
5519
|
+
color.dim(" /collab history 20 \u2014 show the last 20 events of this session")
|
|
5520
|
+
);
|
|
5521
|
+
return { message: lines.join("\n") };
|
|
5522
|
+
}
|
|
5523
|
+
function inviteCommand(sessionId) {
|
|
5524
|
+
if (!sessionId) {
|
|
5525
|
+
return { message: color.yellow("No active session to invite to.") };
|
|
5526
|
+
}
|
|
5527
|
+
const url = `http://127.0.0.1:3457/?session=${encodeURIComponent(sessionId)}`;
|
|
5528
|
+
const lines = [
|
|
5529
|
+
color.bold("Live-collaboration invite"),
|
|
5530
|
+
` Session ID: ${color.cyan(sessionId)}`,
|
|
5531
|
+
` Share this URL with a teammate (they must be able to reach your machine):`,
|
|
5532
|
+
` ${color.cyan(url)}`,
|
|
5533
|
+
"",
|
|
5534
|
+
color.dim(
|
|
5535
|
+
' The teammate opens the URL in their browser, then clicks "Join as observer"'
|
|
5536
|
+
),
|
|
5537
|
+
color.dim(" on the panel that appears above the chat. They will see a live mirror"),
|
|
5538
|
+
color.dim(" of tool calls, iterations, and subagent spawns \u2014 read-only."),
|
|
5539
|
+
"",
|
|
5540
|
+
color.dim(" Reminder: expose the port with: pnpm --filter @wrongstack/webui dev")
|
|
5541
|
+
];
|
|
5542
|
+
return { message: lines.join("\n") };
|
|
5543
|
+
}
|
|
5544
|
+
async function historyCommand(opts, sessionId, args) {
|
|
5545
|
+
const limit = Math.max(1, Math.min(200, Number.parseInt(args[0] ?? "20", 10) || 20));
|
|
5546
|
+
if (!sessionId) return { message: color.yellow("No active session.") };
|
|
5547
|
+
if (!opts.sessionStore) {
|
|
5548
|
+
return { message: color.yellow("No session store configured.") };
|
|
5549
|
+
}
|
|
5550
|
+
const { DefaultSessionReader: DefaultSessionReader2 } = await import('@wrongstack/core');
|
|
5551
|
+
const reader = new DefaultSessionReader2({ store: opts.sessionStore });
|
|
5552
|
+
const events = [];
|
|
5553
|
+
try {
|
|
5554
|
+
for await (const ev of reader.replay(sessionId)) {
|
|
5555
|
+
events.push(ev);
|
|
5556
|
+
}
|
|
5557
|
+
} catch (err) {
|
|
5558
|
+
return {
|
|
5559
|
+
message: color.yellow(
|
|
5560
|
+
`Failed to read session: ${err instanceof Error ? err.message : String(err)}`
|
|
5561
|
+
)
|
|
5562
|
+
};
|
|
5563
|
+
}
|
|
5564
|
+
const tail = events.slice(-limit);
|
|
5565
|
+
if (tail.length === 0) {
|
|
5566
|
+
return { message: color.dim("No events recorded yet for this session.") };
|
|
5567
|
+
}
|
|
5568
|
+
const lines = [
|
|
5569
|
+
color.bold(`Last ${tail.length} events of ${sessionId}`),
|
|
5570
|
+
""
|
|
5571
|
+
];
|
|
5572
|
+
for (const raw of tail) {
|
|
5573
|
+
const ev = raw;
|
|
5574
|
+
const t = ev.ts ? color.dim(ev.ts.slice(11, 19)) : color.dim("--:--:--");
|
|
5575
|
+
const kind = color.cyan((ev.type ?? "unknown").padEnd(16));
|
|
5576
|
+
const summary = summarise(ev);
|
|
5577
|
+
lines.push(` ${t} ${kind} ${summary}`);
|
|
5578
|
+
}
|
|
5579
|
+
return { message: lines.join("\n") };
|
|
5580
|
+
}
|
|
5581
|
+
async function annotationsCommand(opts, sessionId) {
|
|
5582
|
+
if (!sessionId) return { message: color.yellow("No active session.") };
|
|
5583
|
+
if (!opts.sessionStore) {
|
|
5584
|
+
return { message: color.yellow("No session store configured.") };
|
|
5585
|
+
}
|
|
5586
|
+
const { DefaultSessionReader: DefaultSessionReader2, AnnotationsStore } = await import('@wrongstack/core');
|
|
5587
|
+
new DefaultSessionReader2({ store: opts.sessionStore });
|
|
5588
|
+
const storeDir = opts.sessionStore.dir;
|
|
5589
|
+
if (!storeDir) {
|
|
5590
|
+
return {
|
|
5591
|
+
message: color.yellow(
|
|
5592
|
+
"Annotations view needs the session store dir; not exposed in this build."
|
|
5593
|
+
)
|
|
5594
|
+
};
|
|
5595
|
+
}
|
|
5596
|
+
const annotations = new AnnotationsStore({ dir: storeDir });
|
|
5597
|
+
const open = await annotations.listOpen(sessionId);
|
|
5598
|
+
if (open.length === 0) {
|
|
5599
|
+
return {
|
|
5600
|
+
message: color.dim("No open annotations for this session.")
|
|
5601
|
+
};
|
|
5602
|
+
}
|
|
5603
|
+
const lines = [
|
|
5604
|
+
color.bold(`${open.length} open annotation(s) for ${sessionId}`),
|
|
5605
|
+
""
|
|
5606
|
+
];
|
|
5607
|
+
for (const a of open) {
|
|
5608
|
+
const t = color.dim(a.createdAt.slice(11, 19));
|
|
5609
|
+
const author = color.cyan(a.authorId.slice(0, 8));
|
|
5610
|
+
const idx = color.dim(`#${a.atEventIndex}`);
|
|
5611
|
+
lines.push(` ${t} ${idx} ${author} ${a.text}`);
|
|
5612
|
+
}
|
|
5613
|
+
return { message: lines.join("\n") };
|
|
5614
|
+
}
|
|
5615
|
+
function summarise(ev) {
|
|
5616
|
+
switch (ev["type"]) {
|
|
5617
|
+
case "user_input": {
|
|
5618
|
+
const text = typeof ev["text"] === "string" ? ev["text"] : "";
|
|
5619
|
+
return color.dim(truncate(text, 80));
|
|
5620
|
+
}
|
|
5621
|
+
case "llm_response": {
|
|
5622
|
+
const text = typeof ev["text"] === "string" ? ev["text"] : "";
|
|
5623
|
+
return color.dim(truncate(text, 80));
|
|
5624
|
+
}
|
|
5625
|
+
case "tool_result": {
|
|
5626
|
+
const name = String(ev["name"] ?? "?");
|
|
5627
|
+
const ok = ev["ok"] === false ? color.red("(failed)") : color.green("(ok)");
|
|
5628
|
+
return `${name} ${ok}`;
|
|
5629
|
+
}
|
|
5630
|
+
case "compaction": {
|
|
5631
|
+
const before = ev["before"];
|
|
5632
|
+
const after = ev["after"];
|
|
5633
|
+
return `tokens ${before}\u2192${after}`;
|
|
5634
|
+
}
|
|
5635
|
+
case "error": {
|
|
5636
|
+
return color.red(String(ev["message"] ?? "(no message)"));
|
|
5637
|
+
}
|
|
5638
|
+
default: {
|
|
5639
|
+
return color.dim(JSON.stringify(ev).slice(0, 80));
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
function truncate(s, n) {
|
|
5644
|
+
if (s.length <= n) return s;
|
|
5645
|
+
return `${s.slice(0, n - 1)}\u2026`;
|
|
5646
|
+
}
|
|
5647
|
+
function helpCommand() {
|
|
5648
|
+
const lines = [
|
|
5649
|
+
color.bold("/collab \u2014 live collaboration (read-only observers)"),
|
|
5650
|
+
"",
|
|
5651
|
+
"Subcommands:",
|
|
5652
|
+
` ${color.cyan("status")} Show current session's collaboration status`,
|
|
5653
|
+
` ${color.cyan("invite")} Print a webui join link for a teammate`,
|
|
5654
|
+
` ${color.cyan("history [N]")} Show the last N events of this session (default 20, max 200)`,
|
|
5655
|
+
` ${color.cyan("annotations")} List open (unresolved) annotations from webui annotators`,
|
|
5656
|
+
` ${color.cyan("help")} Show this help`,
|
|
5657
|
+
"",
|
|
5658
|
+
color.dim(
|
|
5659
|
+
" The observer flow itself lives in the webui: open the URL printed"
|
|
5660
|
+
),
|
|
5661
|
+
color.dim(' by `invite` and click "Join as observer" on the panel above the chat.')
|
|
5662
|
+
];
|
|
5663
|
+
return { message: lines.join("\n") };
|
|
5664
|
+
}
|
|
5309
5665
|
|
|
5310
5666
|
// src/slash-commands/worktree.ts
|
|
5311
5667
|
function buildWorktreeCommand(opts) {
|
|
@@ -5524,6 +5880,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
5524
5880
|
buildAutoPhaseCommand(opts),
|
|
5525
5881
|
buildWorktreeCommand(opts),
|
|
5526
5882
|
buildSettingsCommand(opts),
|
|
5883
|
+
buildCollabCommand(opts),
|
|
5527
5884
|
buildStatuslineCommand({
|
|
5528
5885
|
cwd: opts.cwd,
|
|
5529
5886
|
hiddenItems: opts.statuslineHiddenItems ?? [],
|
|
@@ -7848,9 +8205,9 @@ var updateCmd = async (args, deps) => {
|
|
|
7848
8205
|
cwd,
|
|
7849
8206
|
stdio: "pipe"
|
|
7850
8207
|
});
|
|
7851
|
-
let
|
|
8208
|
+
let _stderr = "";
|
|
7852
8209
|
child.stderr?.on("data", (d) => {
|
|
7853
|
-
|
|
8210
|
+
_stderr += d;
|
|
7854
8211
|
});
|
|
7855
8212
|
child.on("error", reject);
|
|
7856
8213
|
child.on("close", (code) => resolve4({ code: code ?? 0 }));
|
|
@@ -9084,6 +9441,167 @@ ${result.errors.length} error(s):
|
|
|
9084
9441
|
return 1;
|
|
9085
9442
|
}
|
|
9086
9443
|
};
|
|
9444
|
+
var replayCmd = async (args, deps) => {
|
|
9445
|
+
const wpaths = resolveWstackPaths({
|
|
9446
|
+
projectRoot: deps.projectRoot,
|
|
9447
|
+
userHome: deps.userHome
|
|
9448
|
+
});
|
|
9449
|
+
const store = new ReplayLogStore({ dir: wpaths.projectSessions });
|
|
9450
|
+
if (args[0] === "--list" || args[0] === "-l") {
|
|
9451
|
+
const all = await store.list();
|
|
9452
|
+
if (all.length === 0) {
|
|
9453
|
+
deps.renderer.write(
|
|
9454
|
+
color.dim("No replay logs recorded yet. Run with --record to start one.") + "\n"
|
|
9455
|
+
);
|
|
9456
|
+
return 0;
|
|
9457
|
+
}
|
|
9458
|
+
const lines2 = [
|
|
9459
|
+
color.bold(`${all.length} replay log(s)`),
|
|
9460
|
+
color.dim(" Sorted by session id. Each entry has at least one recorded request/response."),
|
|
9461
|
+
""
|
|
9462
|
+
];
|
|
9463
|
+
for (const r of all) {
|
|
9464
|
+
lines2.push(
|
|
9465
|
+
` ${color.cyan(r.sessionId)} ${color.dim(`${r.entryCount} entries`)} ${color.dim(r.path)}`
|
|
9466
|
+
);
|
|
9467
|
+
}
|
|
9468
|
+
lines2.push("");
|
|
9469
|
+
lines2.push(
|
|
9470
|
+
color.dim(
|
|
9471
|
+
" Inspect: wstack replay <sessionId>"
|
|
9472
|
+
)
|
|
9473
|
+
);
|
|
9474
|
+
lines2.push(
|
|
9475
|
+
color.dim(
|
|
9476
|
+
" Replay: wstack --replay <sessionId>"
|
|
9477
|
+
)
|
|
9478
|
+
);
|
|
9479
|
+
deps.renderer.write(lines2.join("\n") + "\n");
|
|
9480
|
+
return 0;
|
|
9481
|
+
}
|
|
9482
|
+
const sessionId = args[0];
|
|
9483
|
+
if (!sessionId) {
|
|
9484
|
+
deps.renderer.writeError(
|
|
9485
|
+
"Usage: wstack replay <sessionId>\n wstack replay --list\n\nLists recorded provider responses for the session. To actually\nre-run the agent with frozen responses, use:\n wstack --replay " + color.cyan("<sessionId>") + "\n wstack --record # start a fresh recording\n"
|
|
9486
|
+
);
|
|
9487
|
+
return 1;
|
|
9488
|
+
}
|
|
9489
|
+
const entries = await store.load(sessionId);
|
|
9490
|
+
if (entries.length === 0) {
|
|
9491
|
+
deps.renderer.write(
|
|
9492
|
+
color.yellow(
|
|
9493
|
+
`No replay entries recorded for session ${sessionId}.
|
|
9494
|
+
Run a session with --record first, then --replay to re-execute it.`
|
|
9495
|
+
) + "\n"
|
|
9496
|
+
);
|
|
9497
|
+
return 0;
|
|
9498
|
+
}
|
|
9499
|
+
const lines = [
|
|
9500
|
+
color.bold(`Replay log for ${sessionId}`),
|
|
9501
|
+
` ${entries.length} recorded request/response pair(s)`,
|
|
9502
|
+
` Log file: ${color.dim(`${sessionId}.replay.jsonl`)}`,
|
|
9503
|
+
"",
|
|
9504
|
+
"To replay this session deterministically:",
|
|
9505
|
+
` ${color.cyan(`wstack --replay ${sessionId}`)}`,
|
|
9506
|
+
"",
|
|
9507
|
+
"Recorded timestamps (oldest first):"
|
|
9508
|
+
];
|
|
9509
|
+
for (const e of entries.slice(-10)) {
|
|
9510
|
+
lines.push(
|
|
9511
|
+
` ${color.dim(e.ts.slice(11, 19))} ${e.hash.slice(0, 16)}\u2026 ${e.request.model}`
|
|
9512
|
+
);
|
|
9513
|
+
}
|
|
9514
|
+
if (entries.length > 10) {
|
|
9515
|
+
lines.push(color.dim(` \u2026 and ${entries.length - 10} more`));
|
|
9516
|
+
}
|
|
9517
|
+
deps.renderer.write(lines.join("\n") + "\n");
|
|
9518
|
+
return 0;
|
|
9519
|
+
};
|
|
9520
|
+
var auditCmd = async (args, deps) => {
|
|
9521
|
+
const wpaths = resolveWstackPaths({
|
|
9522
|
+
projectRoot: deps.projectRoot,
|
|
9523
|
+
userHome: deps.userHome
|
|
9524
|
+
});
|
|
9525
|
+
const log = new ToolAuditLog({ dir: wpaths.projectSessions });
|
|
9526
|
+
if (args[0] === "--list" || args[0] === "-l") {
|
|
9527
|
+
return listAudits(log, wpaths.projectSessions, deps);
|
|
9528
|
+
}
|
|
9529
|
+
const sessionId = args[0];
|
|
9530
|
+
if (!sessionId) {
|
|
9531
|
+
deps.renderer.writeError(
|
|
9532
|
+
"Usage: wstack audit <sessionId>\n wstack audit --list\n\nInspects a session's tamper-evident tool audit log. Each entry\nis chained to the previous via SHA-256; any post-hoc modification\nbreaks the chain. Use this to verify a session was not tampered\nwith after recording.\n"
|
|
9533
|
+
);
|
|
9534
|
+
return 1;
|
|
9535
|
+
}
|
|
9536
|
+
const entries = await log.load(sessionId);
|
|
9537
|
+
if (entries.length === 0) {
|
|
9538
|
+
deps.renderer.write(
|
|
9539
|
+
color.yellow(
|
|
9540
|
+
`No audit entries for session ${sessionId}. (The audit log is written when tools are recorded \u2014 check that the session ran with audit enabled.)`
|
|
9541
|
+
) + "\n"
|
|
9542
|
+
);
|
|
9543
|
+
return 0;
|
|
9544
|
+
}
|
|
9545
|
+
const verify = await log.verify(sessionId);
|
|
9546
|
+
const lines = [
|
|
9547
|
+
color.bold(`Audit log for ${sessionId}`),
|
|
9548
|
+
verify.ok ? ` ${color.green("\u2713 Verified")} ${entries.length} entries, chain intact` : ` ${color.red("\u2717 BROKEN")} at entry ${verify.brokenAt}: ${verify.reason}`,
|
|
9549
|
+
"",
|
|
9550
|
+
" Entries (oldest first):"
|
|
9551
|
+
];
|
|
9552
|
+
const cap = 12;
|
|
9553
|
+
for (const e of entries.slice(0, cap)) {
|
|
9554
|
+
const t = color.dim(e.ts.slice(11, 19));
|
|
9555
|
+
const idx = color.dim(`#${e.index}`.padStart(4));
|
|
9556
|
+
const tool = e.toolName;
|
|
9557
|
+
const err = e.isError ? color.red("(error)") : "";
|
|
9558
|
+
lines.push(
|
|
9559
|
+
` ${t} ${idx} ${color.cyan(tool.padEnd(14))} ${err} ${color.dim(e.hash.slice(0, 12) + "\u2026")}`
|
|
9560
|
+
);
|
|
9561
|
+
}
|
|
9562
|
+
if (entries.length > cap) {
|
|
9563
|
+
lines.push(color.dim(` \u2026 and ${entries.length - cap} more`));
|
|
9564
|
+
}
|
|
9565
|
+
deps.renderer.write(lines.join("\n") + "\n");
|
|
9566
|
+
return verify.ok ? 0 : 1;
|
|
9567
|
+
};
|
|
9568
|
+
async function listAudits(log, dir, deps) {
|
|
9569
|
+
const fs22 = await import('fs/promises');
|
|
9570
|
+
let entries;
|
|
9571
|
+
try {
|
|
9572
|
+
entries = await fs22.readdir(dir);
|
|
9573
|
+
} catch {
|
|
9574
|
+
deps.renderer.write(
|
|
9575
|
+
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
9576
|
+
);
|
|
9577
|
+
return 0;
|
|
9578
|
+
}
|
|
9579
|
+
const out = [];
|
|
9580
|
+
for (const name of entries) {
|
|
9581
|
+
if (!name.endsWith(".audit.jsonl")) continue;
|
|
9582
|
+
const sessionId = name.slice(0, -".audit.jsonl".length);
|
|
9583
|
+
const all = await log.load(sessionId);
|
|
9584
|
+
const verify = await log.verify(sessionId);
|
|
9585
|
+
out.push({ sessionId, entryCount: all.length, ok: verify.ok });
|
|
9586
|
+
}
|
|
9587
|
+
out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
9588
|
+
if (out.length === 0) {
|
|
9589
|
+
deps.renderer.write(color.dim("No audit logs recorded yet.") + "\n");
|
|
9590
|
+
return 0;
|
|
9591
|
+
}
|
|
9592
|
+
const lines = [
|
|
9593
|
+
color.bold(`${out.length} audit log(s)`),
|
|
9594
|
+
""
|
|
9595
|
+
];
|
|
9596
|
+
for (const r of out) {
|
|
9597
|
+
const status = r.ok ? color.green("\u2713 intact") : color.red("\u2717 broken");
|
|
9598
|
+
lines.push(
|
|
9599
|
+
` ${color.cyan(r.sessionId)} ${color.dim(`${r.entryCount} entries`)} ${status}`
|
|
9600
|
+
);
|
|
9601
|
+
}
|
|
9602
|
+
deps.renderer.write(lines.join("\n") + "\n");
|
|
9603
|
+
return 0;
|
|
9604
|
+
}
|
|
9087
9605
|
var toolsCmd = async (_args, deps) => {
|
|
9088
9606
|
const reg = deps.toolRegistry;
|
|
9089
9607
|
if (!reg) return 0;
|
|
@@ -9150,6 +9668,8 @@ var subcommands = {
|
|
|
9150
9668
|
sessions: sessionsCmd,
|
|
9151
9669
|
config: configCmd,
|
|
9152
9670
|
rewind: rewindCmd,
|
|
9671
|
+
replay: replayCmd,
|
|
9672
|
+
audit: auditCmd,
|
|
9153
9673
|
tools: toolsCmd,
|
|
9154
9674
|
skills: skillsCmd,
|
|
9155
9675
|
providers: providersCmd,
|
|
@@ -9186,22 +9706,22 @@ function fmtDuration(ms) {
|
|
|
9186
9706
|
const remMin = m - h * 60;
|
|
9187
9707
|
return `${h}h${remMin}m`;
|
|
9188
9708
|
}
|
|
9189
|
-
function fmtTaskResultLine(r,
|
|
9709
|
+
function fmtTaskResultLine(r, color41) {
|
|
9190
9710
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
9191
9711
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
9192
9712
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
9193
9713
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
9194
|
-
const errKindChip = errKind ?
|
|
9195
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
9714
|
+
const errKindChip = errKind ? color41.dim(` [${errKind}]`) : "";
|
|
9715
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color41.dim(errTail)}` : "";
|
|
9196
9716
|
switch (r.status) {
|
|
9197
9717
|
case "success":
|
|
9198
|
-
return { mark:
|
|
9718
|
+
return { mark: color41.green("\u2713"), stats, tail: "" };
|
|
9199
9719
|
case "timeout":
|
|
9200
|
-
return { mark:
|
|
9720
|
+
return { mark: color41.yellow("\u23F1"), stats: `${color41.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
9201
9721
|
case "stopped":
|
|
9202
|
-
return { mark:
|
|
9722
|
+
return { mark: color41.dim("\u2298"), stats: `${color41.dim("stopped")} ${stats}`, tail: errSnip };
|
|
9203
9723
|
case "failed":
|
|
9204
|
-
return { mark:
|
|
9724
|
+
return { mark: color41.red("\u2717"), stats: `${color41.red("failed")} ${stats}`, tail: errSnip };
|
|
9205
9725
|
}
|
|
9206
9726
|
}
|
|
9207
9727
|
|
|
@@ -9691,7 +10211,7 @@ async function runRepl(opts) {
|
|
|
9691
10211
|
);
|
|
9692
10212
|
interrupts = 0;
|
|
9693
10213
|
try {
|
|
9694
|
-
|
|
10214
|
+
await engine.runOneIteration();
|
|
9695
10215
|
const afterGoal = await loadGoalSafe(opts);
|
|
9696
10216
|
const last = afterGoal?.journal[afterGoal.journal.length - 1];
|
|
9697
10217
|
if (coord) {
|
|
@@ -9805,7 +10325,7 @@ ${color.dim(taskList2)}
|
|
|
9805
10325
|
}
|
|
9806
10326
|
}
|
|
9807
10327
|
}
|
|
9808
|
-
} catch (
|
|
10328
|
+
} catch (_runErr) {
|
|
9809
10329
|
opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
|
|
9810
10330
|
}
|
|
9811
10331
|
}
|
|
@@ -10505,7 +11025,7 @@ async function execute(deps) {
|
|
|
10505
11025
|
fleetStatusLine?.stop();
|
|
10506
11026
|
try {
|
|
10507
11027
|
stats.render(renderer);
|
|
10508
|
-
} catch (
|
|
11028
|
+
} catch (_err) {
|
|
10509
11029
|
}
|
|
10510
11030
|
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
10511
11031
|
await mcpRegistry.stopAll();
|
|
@@ -10535,6 +11055,10 @@ var MultiAgentHost = class {
|
|
|
10535
11055
|
* critic.evaluation) onto the fleet bus without needing the tool registered
|
|
10536
11056
|
* in the host's ToolRegistry. */
|
|
10537
11057
|
fleetEmitTool;
|
|
11058
|
+
/** Own FleetStatusTool — created in buildDirector() so subagents in director
|
|
11059
|
+
* mode can read fleet state (subagent statuses, task progress) without the
|
|
11060
|
+
* tool being in the host's ToolRegistry. */
|
|
11061
|
+
fleetStatusTool;
|
|
10538
11062
|
/** Lazily built alongside the director — produces per-subagent JSONL
|
|
10539
11063
|
* writers under `<sessionsRoot>/<runId>/`. Null without sessionsRoot. */
|
|
10540
11064
|
sessionFactory;
|
|
@@ -10571,7 +11095,7 @@ var MultiAgentHost = class {
|
|
|
10571
11095
|
getDirector() {
|
|
10572
11096
|
return this.director;
|
|
10573
11097
|
}
|
|
10574
|
-
async ensureCoordinator(
|
|
11098
|
+
async ensureCoordinator(_config) {
|
|
10575
11099
|
await this.buildDirector();
|
|
10576
11100
|
}
|
|
10577
11101
|
async buildDirector() {
|
|
@@ -10638,6 +11162,15 @@ var MultiAgentHost = class {
|
|
|
10638
11162
|
totalExtensions: payload.totalExtensions
|
|
10639
11163
|
});
|
|
10640
11164
|
});
|
|
11165
|
+
this.director.fleet.filter("ctx.pct", (e) => {
|
|
11166
|
+
const payload = e.payload;
|
|
11167
|
+
this.deps.events.emit("subagent.ctx_pct", {
|
|
11168
|
+
subagentId: e.subagentId,
|
|
11169
|
+
load: payload.load,
|
|
11170
|
+
tokens: payload.tokens,
|
|
11171
|
+
maxContext: payload.maxContext
|
|
11172
|
+
});
|
|
11173
|
+
});
|
|
10641
11174
|
this.getCoordinator().on(
|
|
10642
11175
|
"task.assigned",
|
|
10643
11176
|
({
|
|
@@ -10652,6 +11185,7 @@ var MultiAgentHost = class {
|
|
|
10652
11185
|
}
|
|
10653
11186
|
);
|
|
10654
11187
|
this.fleetEmitTool = makeFleetEmitTool(this.director);
|
|
11188
|
+
this.fleetStatusTool = makeFleetStatusTool(this.director);
|
|
10655
11189
|
const runner = await this.buildSubagentRunner(config);
|
|
10656
11190
|
this.getCoordinator().setRunner(runner);
|
|
10657
11191
|
}
|
|
@@ -10709,11 +11243,17 @@ var MultiAgentHost = class {
|
|
|
10709
11243
|
}
|
|
10710
11244
|
const tools = subCfg.tools ? [...subCfg.tools] : void 0;
|
|
10711
11245
|
let injectedFleetEmit;
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
11246
|
+
let injectedFleetStatus;
|
|
11247
|
+
if (tools) {
|
|
11248
|
+
const emitIdx = tools.indexOf("fleet_emit");
|
|
11249
|
+
if (emitIdx >= 0 && this.fleetEmitTool) {
|
|
11250
|
+
tools.splice(emitIdx, 1);
|
|
11251
|
+
injectedFleetEmit = this.fleetEmitTool;
|
|
11252
|
+
}
|
|
11253
|
+
const statusIdx = tools.indexOf("fleet_status");
|
|
11254
|
+
if (statusIdx >= 0 && this.fleetStatusTool) {
|
|
11255
|
+
tools.splice(statusIdx, 1);
|
|
11256
|
+
injectedFleetStatus = this.fleetStatusTool;
|
|
10717
11257
|
}
|
|
10718
11258
|
}
|
|
10719
11259
|
const ctx = new Context({
|
|
@@ -10729,6 +11269,7 @@ var MultiAgentHost = class {
|
|
|
10729
11269
|
});
|
|
10730
11270
|
const baseRegistry = this.subagentToolRegistry(tools);
|
|
10731
11271
|
if (injectedFleetEmit) baseRegistry.register(injectedFleetEmit);
|
|
11272
|
+
if (injectedFleetStatus) baseRegistry.register(injectedFleetStatus);
|
|
10732
11273
|
const toolExecutor = new ToolExecutor(baseRegistry, {
|
|
10733
11274
|
permissionPolicy: new AutoApprovePermissionPolicy(),
|
|
10734
11275
|
secretScrubber: this.deps.secretScrubber,
|
|
@@ -11140,9 +11681,9 @@ function makeConfirmAwaiter(reader) {
|
|
|
11140
11681
|
function stringifyInput(input) {
|
|
11141
11682
|
if (!input || typeof input !== "object") return "";
|
|
11142
11683
|
const obj = input;
|
|
11143
|
-
return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${
|
|
11684
|
+
return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${truncate2(JSON.stringify(v), 80)}`).join(" ");
|
|
11144
11685
|
}
|
|
11145
|
-
function
|
|
11686
|
+
function truncate2(s, max) {
|
|
11146
11687
|
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
11147
11688
|
}
|
|
11148
11689
|
function hasDiff(input) {
|
|
@@ -11150,6 +11691,26 @@ function hasDiff(input) {
|
|
|
11150
11691
|
input && typeof input === "object" && "diff" in input
|
|
11151
11692
|
);
|
|
11152
11693
|
}
|
|
11694
|
+
function bindReplayToContainer(opts) {
|
|
11695
|
+
const { container, wpaths, sessionId, mode, logger } = opts;
|
|
11696
|
+
if (!opts.container.has(TOKENS.ProviderRunner)) {
|
|
11697
|
+
container.bind(TOKENS.ProviderRunner, () => ({
|
|
11698
|
+
run: (o) => runProviderWithRetry(o)
|
|
11699
|
+
}));
|
|
11700
|
+
}
|
|
11701
|
+
const inner = container.resolve(TOKENS.ProviderRunner);
|
|
11702
|
+
const log = new ReplayLogStore({ dir: wpaths.projectSessions });
|
|
11703
|
+
const wrapped = new ReplayProviderRunner(inner, {
|
|
11704
|
+
log,
|
|
11705
|
+
sessionId,
|
|
11706
|
+
mode,
|
|
11707
|
+
logger: logger ? {
|
|
11708
|
+
debug: (m) => logger.debug?.(m),
|
|
11709
|
+
warn: (m) => logger.warn?.(m)
|
|
11710
|
+
} : void 0
|
|
11711
|
+
});
|
|
11712
|
+
container.bind(TOKENS.ProviderRunner, () => wrapped);
|
|
11713
|
+
}
|
|
11153
11714
|
var SessionStats = class {
|
|
11154
11715
|
tokenCounter;
|
|
11155
11716
|
startedAt = Date.now();
|
|
@@ -11628,7 +12189,6 @@ async function setupCompaction(params) {
|
|
|
11628
12189
|
function createAgent(params) {
|
|
11629
12190
|
const secretScrubber = params.container.resolve(TOKENS.SecretScrubber);
|
|
11630
12191
|
const renderer = params.container.has(TOKENS.Renderer) ? params.container.resolve(TOKENS.Renderer) : void 0;
|
|
11631
|
-
params.container.resolve(TOKENS.Logger);
|
|
11632
12192
|
const toolExecutor = new ToolExecutor(params.tools, {
|
|
11633
12193
|
permissionPolicy: params.permissionPolicy ?? params.container.resolve(TOKENS.PermissionPolicy),
|
|
11634
12194
|
secretScrubber,
|
|
@@ -12012,7 +12572,6 @@ async function main(argv) {
|
|
|
12012
12572
|
wpaths,
|
|
12013
12573
|
cwd,
|
|
12014
12574
|
projectRoot,
|
|
12015
|
-
userHome,
|
|
12016
12575
|
flags,
|
|
12017
12576
|
positional,
|
|
12018
12577
|
modelsRegistry,
|
|
@@ -12054,6 +12613,22 @@ async function main(argv) {
|
|
|
12054
12613
|
compactor: { preserveK: config.context.preserveK, eliseThreshold: config.context.eliseThreshold },
|
|
12055
12614
|
bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
|
|
12056
12615
|
});
|
|
12616
|
+
const replayFlag = flags["replay"];
|
|
12617
|
+
const recordFlag = flags["record"];
|
|
12618
|
+
if (typeof replayFlag === "string" || recordFlag === true) {
|
|
12619
|
+
const sessionId = typeof replayFlag === "string" ? replayFlag : `record-${Date.now()}`;
|
|
12620
|
+
const mode = recordFlag === true ? "record" : "replay";
|
|
12621
|
+
bindReplayToContainer({
|
|
12622
|
+
container,
|
|
12623
|
+
wpaths,
|
|
12624
|
+
sessionId,
|
|
12625
|
+
mode,
|
|
12626
|
+
logger
|
|
12627
|
+
});
|
|
12628
|
+
logger.info(
|
|
12629
|
+
`replay: ProviderRunner bound in '${mode}' mode for session ${sessionId}`
|
|
12630
|
+
);
|
|
12631
|
+
}
|
|
12057
12632
|
const configStore = container.resolve(TOKENS.ConfigStore);
|
|
12058
12633
|
container.bind(TOKENS.PathResolver, () => pathResolver);
|
|
12059
12634
|
container.bind(TOKENS.Renderer, () => renderer);
|
|
@@ -12121,7 +12696,7 @@ async function main(argv) {
|
|
|
12121
12696
|
}
|
|
12122
12697
|
const events = new EventBus();
|
|
12123
12698
|
events.setLogger(logger);
|
|
12124
|
-
const { metricsSink, healthRegistry
|
|
12699
|
+
const { metricsSink, healthRegistry } = (() => {
|
|
12125
12700
|
const ms = setupMetrics({ flags, wpaths, events, logger, config: { provider: config.provider, model: config.model } });
|
|
12126
12701
|
return ms;
|
|
12127
12702
|
})();
|
|
@@ -12200,7 +12775,6 @@ async function main(argv) {
|
|
|
12200
12775
|
});
|
|
12201
12776
|
const session = sessResult.session;
|
|
12202
12777
|
sessionRef.current = session;
|
|
12203
|
-
sessResult.restoredMessages;
|
|
12204
12778
|
const context = sessResult.context;
|
|
12205
12779
|
const attachments = sessResult.attachments;
|
|
12206
12780
|
const recoveryLock = sessResult.recoveryLock;
|