@wrongstack/cli 0.9.19 → 0.10.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/data/README.md +77 -0
- package/data/providers.json +1 -0
- package/dist/index.js +775 -50
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
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';
|
|
@@ -13,6 +13,7 @@ import { MCPRegistry } from '@wrongstack/mcp';
|
|
|
13
13
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
14
14
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
15
15
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
16
17
|
import * as readline from 'readline';
|
|
17
18
|
import * as fs10 from 'fs';
|
|
18
19
|
import { writeFileSync } from 'fs';
|
|
@@ -380,12 +381,9 @@ function buildSddCommand(opts) {
|
|
|
380
381
|
name: "sdd",
|
|
381
382
|
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
382
383
|
async run(args) {
|
|
383
|
-
opts.context;
|
|
384
384
|
if (!opts.paths) return { message: "SDD not available \u2014 paths not configured." };
|
|
385
385
|
const specsDir = opts.paths.projectSpecs;
|
|
386
|
-
const graphsDir = opts.paths.projectTaskGraphs;
|
|
387
386
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
388
|
-
new TaskGraphStore({ baseDir: graphsDir });
|
|
389
387
|
const versioning = sddState.getVersioning();
|
|
390
388
|
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
391
389
|
const restJoined = rest.join(" ").trim();
|
|
@@ -1024,8 +1022,8 @@ Start executing the tasks one by one.`
|
|
|
1024
1022
|
return { message: lines2.join("\n") };
|
|
1025
1023
|
}
|
|
1026
1024
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
const stored = await
|
|
1025
|
+
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
1026
|
+
const stored = await graphStore.load(graphId);
|
|
1029
1027
|
if (stored) {
|
|
1030
1028
|
return { message: renderTaskGraph(stored, { compact: false }) };
|
|
1031
1029
|
}
|
|
@@ -1245,8 +1243,8 @@ ${lines.join("\n")}`
|
|
|
1245
1243
|
return { message: "No task graph found. Generate tasks first." };
|
|
1246
1244
|
}
|
|
1247
1245
|
try {
|
|
1248
|
-
const
|
|
1249
|
-
const graph = await
|
|
1246
|
+
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
1247
|
+
const graph = await graphStore.load(graphId);
|
|
1250
1248
|
if (!graph) {
|
|
1251
1249
|
return { message: "Could not load task graph." };
|
|
1252
1250
|
}
|
|
@@ -1659,7 +1657,7 @@ async function checkForUpdate(signal, homeFn) {
|
|
|
1659
1657
|
outdated: isNewer(latest, current),
|
|
1660
1658
|
checkFailed: false
|
|
1661
1659
|
};
|
|
1662
|
-
} catch (
|
|
1660
|
+
} catch (_err) {
|
|
1663
1661
|
if (aborted()) {
|
|
1664
1662
|
return { current, latest: current, outdated: false, checkFailed: true };
|
|
1665
1663
|
}
|
|
@@ -1980,7 +1978,7 @@ async function runWebUI(opts) {
|
|
|
1980
1978
|
}
|
|
1981
1979
|
}
|
|
1982
1980
|
}
|
|
1983
|
-
async function handleUserMessage(ws,
|
|
1981
|
+
async function handleUserMessage(ws, _client, content) {
|
|
1984
1982
|
if (abortController) {
|
|
1985
1983
|
send(ws, {
|
|
1986
1984
|
type: "error",
|
|
@@ -2356,6 +2354,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
2356
2354
|
"recover",
|
|
2357
2355
|
"no-alt-screen",
|
|
2358
2356
|
"alt-screen",
|
|
2357
|
+
"mouse",
|
|
2359
2358
|
"output-json",
|
|
2360
2359
|
"prompt",
|
|
2361
2360
|
"metrics",
|
|
@@ -3553,8 +3552,85 @@ function buildLoadCommand(opts) {
|
|
|
3553
3552
|
return {
|
|
3554
3553
|
name: "resume",
|
|
3555
3554
|
aliases: ["load", "sessions"],
|
|
3556
|
-
description: "List recent sessions.",
|
|
3557
|
-
async run() {
|
|
3555
|
+
description: "List recent sessions, show incomplete ones (--incomplete), or plan a recovery (--recover <id>).",
|
|
3556
|
+
async run(args) {
|
|
3557
|
+
const parts = args.split(/\s+/).filter(Boolean);
|
|
3558
|
+
const showIncomplete = parts.includes("--incomplete") || parts.includes("-i");
|
|
3559
|
+
const recoverIdx = parts.findIndex((p) => p === "--recover");
|
|
3560
|
+
const recoverTarget = recoverIdx >= 0 ? parts[recoverIdx + 1] : void 0;
|
|
3561
|
+
if (recoverTarget) {
|
|
3562
|
+
if (!opts.paths) {
|
|
3563
|
+
return { message: color.yellow("No paths configured \u2014 cannot build a recovery plan.") };
|
|
3564
|
+
}
|
|
3565
|
+
const recovery = new SessionRecovery(opts.paths.projectSessions);
|
|
3566
|
+
const plan = await recovery.recover(recoverTarget);
|
|
3567
|
+
if (!plan) {
|
|
3568
|
+
return {
|
|
3569
|
+
message: color.yellow(
|
|
3570
|
+
`No session log found for ${recoverTarget} (or it is empty).`
|
|
3571
|
+
)
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
const lines2 = [
|
|
3575
|
+
color.bold(`Recovery plan for ${plan.sessionId}`),
|
|
3576
|
+
` Stale: ${plan.stale ? color.yellow("yes") : color.green("no")}`
|
|
3577
|
+
];
|
|
3578
|
+
if (plan.context) {
|
|
3579
|
+
lines2.push(` Last in-flight context: ${color.cyan(plan.context)}`);
|
|
3580
|
+
}
|
|
3581
|
+
if (plan.lastCheckpoint && plan.lastCheckpoint.type === "checkpoint") {
|
|
3582
|
+
const cp = plan.lastCheckpoint;
|
|
3583
|
+
lines2.push(
|
|
3584
|
+
` Last checkpoint: promptIndex=${cp.promptIndex} preview=${color.dim(`"${cp.promptPreview}"`)} at ${color.dim(cp.ts)}`
|
|
3585
|
+
);
|
|
3586
|
+
} else {
|
|
3587
|
+
lines2.push(` Last checkpoint: ${color.dim("(none \u2014 full re-execution)")}`);
|
|
3588
|
+
}
|
|
3589
|
+
lines2.push(
|
|
3590
|
+
` Pending events: ${plan.pendingEvents.length} (the work that would re-run on resume)`
|
|
3591
|
+
);
|
|
3592
|
+
if (plan.pendingEvents.length > 0) {
|
|
3593
|
+
const summary = summarizePending(plan.pendingEvents);
|
|
3594
|
+
lines2.push(...summary);
|
|
3595
|
+
}
|
|
3596
|
+
lines2.push("");
|
|
3597
|
+
lines2.push(
|
|
3598
|
+
color.dim(
|
|
3599
|
+
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."
|
|
3600
|
+
)
|
|
3601
|
+
);
|
|
3602
|
+
return { message: lines2.join("\n") };
|
|
3603
|
+
}
|
|
3604
|
+
if (showIncomplete) {
|
|
3605
|
+
if (!opts.paths) {
|
|
3606
|
+
return { message: color.yellow("No paths configured \u2014 cannot scan for incomplete sessions.") };
|
|
3607
|
+
}
|
|
3608
|
+
const recovery = new SessionRecovery(opts.paths.projectSessions);
|
|
3609
|
+
const resumable = await recovery.listResumable();
|
|
3610
|
+
if (resumable.length === 0) {
|
|
3611
|
+
return {
|
|
3612
|
+
message: color.dim("No incomplete sessions. (Every recorded run ended cleanly.)")
|
|
3613
|
+
};
|
|
3614
|
+
}
|
|
3615
|
+
const lines2 = [
|
|
3616
|
+
color.bold(`${resumable.length} incomplete session(s)`),
|
|
3617
|
+
color.dim(" (process died mid-iteration; the last event was in_flight_start)"),
|
|
3618
|
+
""
|
|
3619
|
+
];
|
|
3620
|
+
for (const s of resumable) {
|
|
3621
|
+
const t = color.dim(s.lastEventTs.slice(0, 19).replace("T", " "));
|
|
3622
|
+
lines2.push(
|
|
3623
|
+
` ${color.cyan(s.sessionId)} ${t} ${color.dim(`${s.eventCount} events`)} ${s.context}`
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
lines2.push("");
|
|
3627
|
+
lines2.push(
|
|
3628
|
+
color.dim(
|
|
3629
|
+
" 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."
|
|
3630
|
+
)
|
|
3631
|
+
);
|
|
3632
|
+
return { message: lines2.join("\n") };
|
|
3633
|
+
}
|
|
3558
3634
|
if (!opts.sessionStore) return { message: "No session store configured." };
|
|
3559
3635
|
const list = await opts.sessionStore.list(10);
|
|
3560
3636
|
if (list.length === 0) return { message: "No saved sessions." };
|
|
@@ -3565,7 +3641,8 @@ function buildLoadCommand(opts) {
|
|
|
3565
3641
|
${lines.join("\n")}
|
|
3566
3642
|
|
|
3567
3643
|
${color.dim(`Resume one with: wstack resume ${list[0]?.id ?? "<id>"}
|
|
3568
|
-
`)}
|
|
3644
|
+
`)}
|
|
3645
|
+
${color.dim("Tip: /resume --incomplete \u2014 list sessions that crashed mid-iteration")}`;
|
|
3569
3646
|
opts.renderer.write(msg);
|
|
3570
3647
|
return { message: msg };
|
|
3571
3648
|
}
|
|
@@ -3589,6 +3666,47 @@ function buildExitCommand(opts) {
|
|
|
3589
3666
|
}
|
|
3590
3667
|
};
|
|
3591
3668
|
}
|
|
3669
|
+
function summarizePending(events) {
|
|
3670
|
+
const lines = [];
|
|
3671
|
+
const cap = 12;
|
|
3672
|
+
for (const ev of events.slice(-cap)) {
|
|
3673
|
+
const t = "ts" in ev ? color.dim(String(ev.ts).slice(11, 19)) : color.dim("--:--:--");
|
|
3674
|
+
const kind = color.cyan(String(ev.type).padEnd(18));
|
|
3675
|
+
lines.push(` ${t} ${kind} ${summariseEvent(ev)}`);
|
|
3676
|
+
}
|
|
3677
|
+
if (events.length > cap) {
|
|
3678
|
+
lines.push(color.dim(` \u2026 and ${events.length - cap} more`));
|
|
3679
|
+
}
|
|
3680
|
+
return lines;
|
|
3681
|
+
}
|
|
3682
|
+
function summariseEvent(ev) {
|
|
3683
|
+
switch (ev.type) {
|
|
3684
|
+
case "user_input": {
|
|
3685
|
+
const text = "text" in ev && typeof ev.text === "string" ? ev.text : Array.isArray(ev.content) ? "\u2026" : "";
|
|
3686
|
+
return color.dim(text.length > 60 ? text.slice(0, 59) + "\u2026" : text);
|
|
3687
|
+
}
|
|
3688
|
+
case "llm_response":
|
|
3689
|
+
return color.dim("(model reply)");
|
|
3690
|
+
case "tool_use":
|
|
3691
|
+
return color.dim(`name=${ev.name ?? "?"}`);
|
|
3692
|
+
case "tool_result":
|
|
3693
|
+
return color.dim(`name=${ev.name ?? "?"}`);
|
|
3694
|
+
case "in_flight_start":
|
|
3695
|
+
return color.dim(`context="${ev.context ?? ""}"`);
|
|
3696
|
+
case "in_flight_end":
|
|
3697
|
+
return color.dim(`reason=${ev.reason ?? "?"}`);
|
|
3698
|
+
case "checkpoint":
|
|
3699
|
+
return color.dim(
|
|
3700
|
+
`promptIndex=${ev.promptIndex ?? "?"}`
|
|
3701
|
+
);
|
|
3702
|
+
case "compaction":
|
|
3703
|
+
return color.dim("(compaction)");
|
|
3704
|
+
case "error":
|
|
3705
|
+
return color.red(String(ev.message ?? ""));
|
|
3706
|
+
default:
|
|
3707
|
+
return color.dim("\u2026");
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3592
3710
|
|
|
3593
3711
|
// src/slash-commands/spawn-agents.ts
|
|
3594
3712
|
function buildSpawnCommand(opts) {
|
|
@@ -4173,8 +4291,48 @@ ${lines.join("\n")}`;
|
|
|
4173
4291
|
}
|
|
4174
4292
|
};
|
|
4175
4293
|
}
|
|
4176
|
-
|
|
4177
|
-
|
|
4294
|
+
async function runModePicker(modeStore, reader) {
|
|
4295
|
+
const modes = await modeStore.listModes();
|
|
4296
|
+
const active = await modeStore.getActiveMode();
|
|
4297
|
+
const activeIdx = modes.findIndex((m) => m.id === active?.id);
|
|
4298
|
+
let cursor = activeIdx >= 0 ? activeIdx : 0;
|
|
4299
|
+
const render = (currentCursor) => {
|
|
4300
|
+
const lines = [];
|
|
4301
|
+
lines.push(`
|
|
4302
|
+
${color.bold(color.amber("WrongStack") + color.dim(" \u2014 Mode Selection"))}
|
|
4303
|
+
`);
|
|
4304
|
+
lines.push(color.dim(" \u2191\u2193 navigate Enter select q quit\n"));
|
|
4305
|
+
lines.push("");
|
|
4306
|
+
for (let i = 0; i < modes.length; i++) {
|
|
4307
|
+
const m = modes[i];
|
|
4308
|
+
const mark = m.id === active?.id ? color.green(" [active]") : "";
|
|
4309
|
+
const prefix = i === currentCursor ? color.bold("\u276F ") : " ";
|
|
4310
|
+
const name = i === currentCursor ? color.bold(m.name) : m.name;
|
|
4311
|
+
lines.push(` ${prefix}${name} ${color.dim(m.description)}${mark}`);
|
|
4312
|
+
}
|
|
4313
|
+
lines.push("");
|
|
4314
|
+
return lines.join("\n");
|
|
4315
|
+
};
|
|
4316
|
+
process.stdout.write(render(cursor));
|
|
4317
|
+
const options = [
|
|
4318
|
+
{ key: "\x1B[A", label: "\u2191", value: "up" },
|
|
4319
|
+
{ key: "\x1B[B", label: "\u2193", value: "down" },
|
|
4320
|
+
{ key: "\r", label: "Enter", value: "enter" },
|
|
4321
|
+
{ key: "q", label: "q", value: "quit" }
|
|
4322
|
+
];
|
|
4323
|
+
while (true) {
|
|
4324
|
+
const answer = await reader.readKey("", options);
|
|
4325
|
+
if (answer === "quit") return void 0;
|
|
4326
|
+
if (answer === "enter") return modes[cursor];
|
|
4327
|
+
if (answer === "up") {
|
|
4328
|
+
cursor = cursor > 0 ? cursor - 1 : modes.length - 1;
|
|
4329
|
+
} else if (answer === "down") {
|
|
4330
|
+
cursor = cursor < modes.length - 1 ? cursor + 1 : 0;
|
|
4331
|
+
}
|
|
4332
|
+
process.stdout.write("\x1B[J");
|
|
4333
|
+
process.stdout.write(render(cursor));
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4178
4336
|
function buildModeCommand(opts) {
|
|
4179
4337
|
return {
|
|
4180
4338
|
name: "mode",
|
|
@@ -4194,7 +4352,7 @@ function buildModeCommand(opts) {
|
|
|
4194
4352
|
" /mode brief Switch to brief mode",
|
|
4195
4353
|
" /mode teach Switch to teach mode"
|
|
4196
4354
|
].join("\n"),
|
|
4197
|
-
async run(args) {
|
|
4355
|
+
async run(args, _ctx) {
|
|
4198
4356
|
const modeStore = opts.modeStore;
|
|
4199
4357
|
if (!modeStore) {
|
|
4200
4358
|
return { message: "Mode store not available in this context." };
|
|
@@ -4202,6 +4360,17 @@ function buildModeCommand(opts) {
|
|
|
4202
4360
|
const modes = await modeStore.listModes();
|
|
4203
4361
|
const active = await modeStore.getActiveMode();
|
|
4204
4362
|
if (!args.trim()) {
|
|
4363
|
+
if (opts.inputReader) {
|
|
4364
|
+
const selected = await runModePicker(modeStore, opts.inputReader);
|
|
4365
|
+
if (!selected) {
|
|
4366
|
+
return { message: "Mode selection cancelled." };
|
|
4367
|
+
}
|
|
4368
|
+
await modeStore.setActiveMode(selected.id);
|
|
4369
|
+
return {
|
|
4370
|
+
message: `Switched to "${selected.name}" mode.
|
|
4371
|
+
${selected.description}`
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4205
4374
|
const lines = [`Current mode: ${active?.name ?? "none"}`, "", "Available modes:"];
|
|
4206
4375
|
for (const m of modes) {
|
|
4207
4376
|
const mark = m.id === active?.id ? " [active]" : "";
|
|
@@ -5306,6 +5475,195 @@ function buildAutoPhaseCommand(opts) {
|
|
|
5306
5475
|
}
|
|
5307
5476
|
};
|
|
5308
5477
|
}
|
|
5478
|
+
function buildCollabCommand(opts) {
|
|
5479
|
+
return {
|
|
5480
|
+
name: "collab",
|
|
5481
|
+
description: "Live collaboration helpers (status / invite / history).",
|
|
5482
|
+
async run(args, ctx) {
|
|
5483
|
+
const parts = args.split(/\s+/).filter(Boolean);
|
|
5484
|
+
const sub = (parts[0] ?? "status").toLowerCase();
|
|
5485
|
+
switch (sub) {
|
|
5486
|
+
case "status":
|
|
5487
|
+
return statusCommand(ctx.session?.id);
|
|
5488
|
+
case "invite":
|
|
5489
|
+
return inviteCommand(ctx.session?.id);
|
|
5490
|
+
case "history":
|
|
5491
|
+
return historyCommand(opts, ctx.session?.id, parts.slice(1));
|
|
5492
|
+
case "annotations":
|
|
5493
|
+
case "notes":
|
|
5494
|
+
return annotationsCommand(opts, ctx.session?.id);
|
|
5495
|
+
case "help":
|
|
5496
|
+
case "--help":
|
|
5497
|
+
case "-h":
|
|
5498
|
+
return helpCommand();
|
|
5499
|
+
default:
|
|
5500
|
+
return {
|
|
5501
|
+
message: color.yellow(
|
|
5502
|
+
`Unknown subcommand: ${sub}. Try: /collab status | invite | history | annotations`
|
|
5503
|
+
)
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
};
|
|
5508
|
+
}
|
|
5509
|
+
function statusCommand(sessionId) {
|
|
5510
|
+
const lines = [];
|
|
5511
|
+
lines.push(color.bold("Live collaboration"));
|
|
5512
|
+
lines.push(` Session: ${color.cyan(sessionId ?? "(none)")}`);
|
|
5513
|
+
lines.push(` Observers: ${color.dim("unknown (check webui)")}`);
|
|
5514
|
+
lines.push("");
|
|
5515
|
+
lines.push(
|
|
5516
|
+
color.dim(
|
|
5517
|
+
" Tip: /collab invite \u2014 print a webui join link for a teammate"
|
|
5518
|
+
)
|
|
5519
|
+
);
|
|
5520
|
+
lines.push(
|
|
5521
|
+
color.dim(" /collab history 20 \u2014 show the last 20 events of this session")
|
|
5522
|
+
);
|
|
5523
|
+
return { message: lines.join("\n") };
|
|
5524
|
+
}
|
|
5525
|
+
function inviteCommand(sessionId) {
|
|
5526
|
+
if (!sessionId) {
|
|
5527
|
+
return { message: color.yellow("No active session to invite to.") };
|
|
5528
|
+
}
|
|
5529
|
+
const url = `http://127.0.0.1:3457/?session=${encodeURIComponent(sessionId)}`;
|
|
5530
|
+
const lines = [
|
|
5531
|
+
color.bold("Live-collaboration invite"),
|
|
5532
|
+
` Session ID: ${color.cyan(sessionId)}`,
|
|
5533
|
+
` Share this URL with a teammate (they must be able to reach your machine):`,
|
|
5534
|
+
` ${color.cyan(url)}`,
|
|
5535
|
+
"",
|
|
5536
|
+
color.dim(
|
|
5537
|
+
' The teammate opens the URL in their browser, then clicks "Join as observer"'
|
|
5538
|
+
),
|
|
5539
|
+
color.dim(" on the panel that appears above the chat. They will see a live mirror"),
|
|
5540
|
+
color.dim(" of tool calls, iterations, and subagent spawns \u2014 read-only."),
|
|
5541
|
+
"",
|
|
5542
|
+
color.dim(" Reminder: expose the port with: pnpm --filter @wrongstack/webui dev")
|
|
5543
|
+
];
|
|
5544
|
+
return { message: lines.join("\n") };
|
|
5545
|
+
}
|
|
5546
|
+
async function historyCommand(opts, sessionId, args) {
|
|
5547
|
+
const limit = Math.max(1, Math.min(200, Number.parseInt(args[0] ?? "20", 10) || 20));
|
|
5548
|
+
if (!sessionId) return { message: color.yellow("No active session.") };
|
|
5549
|
+
if (!opts.sessionStore) {
|
|
5550
|
+
return { message: color.yellow("No session store configured.") };
|
|
5551
|
+
}
|
|
5552
|
+
const { DefaultSessionReader: DefaultSessionReader2 } = await import('@wrongstack/core');
|
|
5553
|
+
const reader = new DefaultSessionReader2({ store: opts.sessionStore });
|
|
5554
|
+
const events = [];
|
|
5555
|
+
try {
|
|
5556
|
+
for await (const ev of reader.replay(sessionId)) {
|
|
5557
|
+
events.push(ev);
|
|
5558
|
+
}
|
|
5559
|
+
} catch (err) {
|
|
5560
|
+
return {
|
|
5561
|
+
message: color.yellow(
|
|
5562
|
+
`Failed to read session: ${err instanceof Error ? err.message : String(err)}`
|
|
5563
|
+
)
|
|
5564
|
+
};
|
|
5565
|
+
}
|
|
5566
|
+
const tail = events.slice(-limit);
|
|
5567
|
+
if (tail.length === 0) {
|
|
5568
|
+
return { message: color.dim("No events recorded yet for this session.") };
|
|
5569
|
+
}
|
|
5570
|
+
const lines = [
|
|
5571
|
+
color.bold(`Last ${tail.length} events of ${sessionId}`),
|
|
5572
|
+
""
|
|
5573
|
+
];
|
|
5574
|
+
for (const raw of tail) {
|
|
5575
|
+
const ev = raw;
|
|
5576
|
+
const t = ev.ts ? color.dim(ev.ts.slice(11, 19)) : color.dim("--:--:--");
|
|
5577
|
+
const kind = color.cyan((ev.type ?? "unknown").padEnd(16));
|
|
5578
|
+
const summary = summarise(ev);
|
|
5579
|
+
lines.push(` ${t} ${kind} ${summary}`);
|
|
5580
|
+
}
|
|
5581
|
+
return { message: lines.join("\n") };
|
|
5582
|
+
}
|
|
5583
|
+
async function annotationsCommand(opts, sessionId) {
|
|
5584
|
+
if (!sessionId) return { message: color.yellow("No active session.") };
|
|
5585
|
+
if (!opts.sessionStore) {
|
|
5586
|
+
return { message: color.yellow("No session store configured.") };
|
|
5587
|
+
}
|
|
5588
|
+
const { DefaultSessionReader: DefaultSessionReader2, AnnotationsStore } = await import('@wrongstack/core');
|
|
5589
|
+
new DefaultSessionReader2({ store: opts.sessionStore });
|
|
5590
|
+
const storeDir = opts.sessionStore.dir;
|
|
5591
|
+
if (!storeDir) {
|
|
5592
|
+
return {
|
|
5593
|
+
message: color.yellow(
|
|
5594
|
+
"Annotations view needs the session store dir; not exposed in this build."
|
|
5595
|
+
)
|
|
5596
|
+
};
|
|
5597
|
+
}
|
|
5598
|
+
const annotations = new AnnotationsStore({ dir: storeDir });
|
|
5599
|
+
const open = await annotations.listOpen(sessionId);
|
|
5600
|
+
if (open.length === 0) {
|
|
5601
|
+
return {
|
|
5602
|
+
message: color.dim("No open annotations for this session.")
|
|
5603
|
+
};
|
|
5604
|
+
}
|
|
5605
|
+
const lines = [
|
|
5606
|
+
color.bold(`${open.length} open annotation(s) for ${sessionId}`),
|
|
5607
|
+
""
|
|
5608
|
+
];
|
|
5609
|
+
for (const a of open) {
|
|
5610
|
+
const t = color.dim(a.createdAt.slice(11, 19));
|
|
5611
|
+
const author = color.cyan(a.authorId.slice(0, 8));
|
|
5612
|
+
const idx = color.dim(`#${a.atEventIndex}`);
|
|
5613
|
+
lines.push(` ${t} ${idx} ${author} ${a.text}`);
|
|
5614
|
+
}
|
|
5615
|
+
return { message: lines.join("\n") };
|
|
5616
|
+
}
|
|
5617
|
+
function summarise(ev) {
|
|
5618
|
+
switch (ev["type"]) {
|
|
5619
|
+
case "user_input": {
|
|
5620
|
+
const text = typeof ev["text"] === "string" ? ev["text"] : "";
|
|
5621
|
+
return color.dim(truncate(text, 80));
|
|
5622
|
+
}
|
|
5623
|
+
case "llm_response": {
|
|
5624
|
+
const text = typeof ev["text"] === "string" ? ev["text"] : "";
|
|
5625
|
+
return color.dim(truncate(text, 80));
|
|
5626
|
+
}
|
|
5627
|
+
case "tool_result": {
|
|
5628
|
+
const name = String(ev["name"] ?? "?");
|
|
5629
|
+
const ok = ev["ok"] === false ? color.red("(failed)") : color.green("(ok)");
|
|
5630
|
+
return `${name} ${ok}`;
|
|
5631
|
+
}
|
|
5632
|
+
case "compaction": {
|
|
5633
|
+
const before = ev["before"];
|
|
5634
|
+
const after = ev["after"];
|
|
5635
|
+
return `tokens ${before}\u2192${after}`;
|
|
5636
|
+
}
|
|
5637
|
+
case "error": {
|
|
5638
|
+
return color.red(String(ev["message"] ?? "(no message)"));
|
|
5639
|
+
}
|
|
5640
|
+
default: {
|
|
5641
|
+
return color.dim(JSON.stringify(ev).slice(0, 80));
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5645
|
+
function truncate(s, n) {
|
|
5646
|
+
if (s.length <= n) return s;
|
|
5647
|
+
return `${s.slice(0, n - 1)}\u2026`;
|
|
5648
|
+
}
|
|
5649
|
+
function helpCommand() {
|
|
5650
|
+
const lines = [
|
|
5651
|
+
color.bold("/collab \u2014 live collaboration (read-only observers)"),
|
|
5652
|
+
"",
|
|
5653
|
+
"Subcommands:",
|
|
5654
|
+
` ${color.cyan("status")} Show current session's collaboration status`,
|
|
5655
|
+
` ${color.cyan("invite")} Print a webui join link for a teammate`,
|
|
5656
|
+
` ${color.cyan("history [N]")} Show the last N events of this session (default 20, max 200)`,
|
|
5657
|
+
` ${color.cyan("annotations")} List open (unresolved) annotations from webui annotators`,
|
|
5658
|
+
` ${color.cyan("help")} Show this help`,
|
|
5659
|
+
"",
|
|
5660
|
+
color.dim(
|
|
5661
|
+
" The observer flow itself lives in the webui: open the URL printed"
|
|
5662
|
+
),
|
|
5663
|
+
color.dim(' by `invite` and click "Join as observer" on the panel above the chat.')
|
|
5664
|
+
];
|
|
5665
|
+
return { message: lines.join("\n") };
|
|
5666
|
+
}
|
|
5309
5667
|
|
|
5310
5668
|
// src/slash-commands/worktree.ts
|
|
5311
5669
|
function buildWorktreeCommand(opts) {
|
|
@@ -5524,6 +5882,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
5524
5882
|
buildAutoPhaseCommand(opts),
|
|
5525
5883
|
buildWorktreeCommand(opts),
|
|
5526
5884
|
buildSettingsCommand(opts),
|
|
5885
|
+
buildCollabCommand(opts),
|
|
5527
5886
|
buildStatuslineCommand({
|
|
5528
5887
|
cwd: opts.cwd,
|
|
5529
5888
|
hiddenItems: opts.statuslineHiddenItems ?? [],
|
|
@@ -7848,9 +8207,9 @@ var updateCmd = async (args, deps) => {
|
|
|
7848
8207
|
cwd,
|
|
7849
8208
|
stdio: "pipe"
|
|
7850
8209
|
});
|
|
7851
|
-
let
|
|
8210
|
+
let _stderr = "";
|
|
7852
8211
|
child.stderr?.on("data", (d) => {
|
|
7853
|
-
|
|
8212
|
+
_stderr += d;
|
|
7854
8213
|
});
|
|
7855
8214
|
child.on("error", reject);
|
|
7856
8215
|
child.on("close", (code) => resolve4({ code: code ?? 0 }));
|
|
@@ -9084,6 +9443,167 @@ ${result.errors.length} error(s):
|
|
|
9084
9443
|
return 1;
|
|
9085
9444
|
}
|
|
9086
9445
|
};
|
|
9446
|
+
var replayCmd = async (args, deps) => {
|
|
9447
|
+
const wpaths = resolveWstackPaths({
|
|
9448
|
+
projectRoot: deps.projectRoot,
|
|
9449
|
+
userHome: deps.userHome
|
|
9450
|
+
});
|
|
9451
|
+
const store = new ReplayLogStore({ dir: wpaths.projectSessions });
|
|
9452
|
+
if (args[0] === "--list" || args[0] === "-l") {
|
|
9453
|
+
const all = await store.list();
|
|
9454
|
+
if (all.length === 0) {
|
|
9455
|
+
deps.renderer.write(
|
|
9456
|
+
color.dim("No replay logs recorded yet. Run with --record to start one.") + "\n"
|
|
9457
|
+
);
|
|
9458
|
+
return 0;
|
|
9459
|
+
}
|
|
9460
|
+
const lines2 = [
|
|
9461
|
+
color.bold(`${all.length} replay log(s)`),
|
|
9462
|
+
color.dim(" Sorted by session id. Each entry has at least one recorded request/response."),
|
|
9463
|
+
""
|
|
9464
|
+
];
|
|
9465
|
+
for (const r of all) {
|
|
9466
|
+
lines2.push(
|
|
9467
|
+
` ${color.cyan(r.sessionId)} ${color.dim(`${r.entryCount} entries`)} ${color.dim(r.path)}`
|
|
9468
|
+
);
|
|
9469
|
+
}
|
|
9470
|
+
lines2.push("");
|
|
9471
|
+
lines2.push(
|
|
9472
|
+
color.dim(
|
|
9473
|
+
" Inspect: wstack replay <sessionId>"
|
|
9474
|
+
)
|
|
9475
|
+
);
|
|
9476
|
+
lines2.push(
|
|
9477
|
+
color.dim(
|
|
9478
|
+
" Replay: wstack --replay <sessionId>"
|
|
9479
|
+
)
|
|
9480
|
+
);
|
|
9481
|
+
deps.renderer.write(lines2.join("\n") + "\n");
|
|
9482
|
+
return 0;
|
|
9483
|
+
}
|
|
9484
|
+
const sessionId = args[0];
|
|
9485
|
+
if (!sessionId) {
|
|
9486
|
+
deps.renderer.writeError(
|
|
9487
|
+
"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"
|
|
9488
|
+
);
|
|
9489
|
+
return 1;
|
|
9490
|
+
}
|
|
9491
|
+
const entries = await store.load(sessionId);
|
|
9492
|
+
if (entries.length === 0) {
|
|
9493
|
+
deps.renderer.write(
|
|
9494
|
+
color.yellow(
|
|
9495
|
+
`No replay entries recorded for session ${sessionId}.
|
|
9496
|
+
Run a session with --record first, then --replay to re-execute it.`
|
|
9497
|
+
) + "\n"
|
|
9498
|
+
);
|
|
9499
|
+
return 0;
|
|
9500
|
+
}
|
|
9501
|
+
const lines = [
|
|
9502
|
+
color.bold(`Replay log for ${sessionId}`),
|
|
9503
|
+
` ${entries.length} recorded request/response pair(s)`,
|
|
9504
|
+
` Log file: ${color.dim(`${sessionId}.replay.jsonl`)}`,
|
|
9505
|
+
"",
|
|
9506
|
+
"To replay this session deterministically:",
|
|
9507
|
+
` ${color.cyan(`wstack --replay ${sessionId}`)}`,
|
|
9508
|
+
"",
|
|
9509
|
+
"Recorded timestamps (oldest first):"
|
|
9510
|
+
];
|
|
9511
|
+
for (const e of entries.slice(-10)) {
|
|
9512
|
+
lines.push(
|
|
9513
|
+
` ${color.dim(e.ts.slice(11, 19))} ${e.hash.slice(0, 16)}\u2026 ${e.request.model}`
|
|
9514
|
+
);
|
|
9515
|
+
}
|
|
9516
|
+
if (entries.length > 10) {
|
|
9517
|
+
lines.push(color.dim(` \u2026 and ${entries.length - 10} more`));
|
|
9518
|
+
}
|
|
9519
|
+
deps.renderer.write(lines.join("\n") + "\n");
|
|
9520
|
+
return 0;
|
|
9521
|
+
};
|
|
9522
|
+
var auditCmd = async (args, deps) => {
|
|
9523
|
+
const wpaths = resolveWstackPaths({
|
|
9524
|
+
projectRoot: deps.projectRoot,
|
|
9525
|
+
userHome: deps.userHome
|
|
9526
|
+
});
|
|
9527
|
+
const log = new ToolAuditLog({ dir: wpaths.projectSessions });
|
|
9528
|
+
if (args[0] === "--list" || args[0] === "-l") {
|
|
9529
|
+
return listAudits(log, wpaths.projectSessions, deps);
|
|
9530
|
+
}
|
|
9531
|
+
const sessionId = args[0];
|
|
9532
|
+
if (!sessionId) {
|
|
9533
|
+
deps.renderer.writeError(
|
|
9534
|
+
"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"
|
|
9535
|
+
);
|
|
9536
|
+
return 1;
|
|
9537
|
+
}
|
|
9538
|
+
const entries = await log.load(sessionId);
|
|
9539
|
+
if (entries.length === 0) {
|
|
9540
|
+
deps.renderer.write(
|
|
9541
|
+
color.yellow(
|
|
9542
|
+
`No audit entries for session ${sessionId}. (The audit log is written when tools are recorded \u2014 check that the session ran with audit enabled.)`
|
|
9543
|
+
) + "\n"
|
|
9544
|
+
);
|
|
9545
|
+
return 0;
|
|
9546
|
+
}
|
|
9547
|
+
const verify = await log.verify(sessionId);
|
|
9548
|
+
const lines = [
|
|
9549
|
+
color.bold(`Audit log for ${sessionId}`),
|
|
9550
|
+
verify.ok ? ` ${color.green("\u2713 Verified")} ${entries.length} entries, chain intact` : ` ${color.red("\u2717 BROKEN")} at entry ${verify.brokenAt}: ${verify.reason}`,
|
|
9551
|
+
"",
|
|
9552
|
+
" Entries (oldest first):"
|
|
9553
|
+
];
|
|
9554
|
+
const cap = 12;
|
|
9555
|
+
for (const e of entries.slice(0, cap)) {
|
|
9556
|
+
const t = color.dim(e.ts.slice(11, 19));
|
|
9557
|
+
const idx = color.dim(`#${e.index}`.padStart(4));
|
|
9558
|
+
const tool = e.toolName;
|
|
9559
|
+
const err = e.isError ? color.red("(error)") : "";
|
|
9560
|
+
lines.push(
|
|
9561
|
+
` ${t} ${idx} ${color.cyan(tool.padEnd(14))} ${err} ${color.dim(e.hash.slice(0, 12) + "\u2026")}`
|
|
9562
|
+
);
|
|
9563
|
+
}
|
|
9564
|
+
if (entries.length > cap) {
|
|
9565
|
+
lines.push(color.dim(` \u2026 and ${entries.length - cap} more`));
|
|
9566
|
+
}
|
|
9567
|
+
deps.renderer.write(lines.join("\n") + "\n");
|
|
9568
|
+
return verify.ok ? 0 : 1;
|
|
9569
|
+
};
|
|
9570
|
+
async function listAudits(log, dir, deps) {
|
|
9571
|
+
const fs22 = await import('fs/promises');
|
|
9572
|
+
let entries;
|
|
9573
|
+
try {
|
|
9574
|
+
entries = await fs22.readdir(dir);
|
|
9575
|
+
} catch {
|
|
9576
|
+
deps.renderer.write(
|
|
9577
|
+
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
9578
|
+
);
|
|
9579
|
+
return 0;
|
|
9580
|
+
}
|
|
9581
|
+
const out = [];
|
|
9582
|
+
for (const name of entries) {
|
|
9583
|
+
if (!name.endsWith(".audit.jsonl")) continue;
|
|
9584
|
+
const sessionId = name.slice(0, -".audit.jsonl".length);
|
|
9585
|
+
const all = await log.load(sessionId);
|
|
9586
|
+
const verify = await log.verify(sessionId);
|
|
9587
|
+
out.push({ sessionId, entryCount: all.length, ok: verify.ok });
|
|
9588
|
+
}
|
|
9589
|
+
out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
9590
|
+
if (out.length === 0) {
|
|
9591
|
+
deps.renderer.write(color.dim("No audit logs recorded yet.") + "\n");
|
|
9592
|
+
return 0;
|
|
9593
|
+
}
|
|
9594
|
+
const lines = [
|
|
9595
|
+
color.bold(`${out.length} audit log(s)`),
|
|
9596
|
+
""
|
|
9597
|
+
];
|
|
9598
|
+
for (const r of out) {
|
|
9599
|
+
const status = r.ok ? color.green("\u2713 intact") : color.red("\u2717 broken");
|
|
9600
|
+
lines.push(
|
|
9601
|
+
` ${color.cyan(r.sessionId)} ${color.dim(`${r.entryCount} entries`)} ${status}`
|
|
9602
|
+
);
|
|
9603
|
+
}
|
|
9604
|
+
deps.renderer.write(lines.join("\n") + "\n");
|
|
9605
|
+
return 0;
|
|
9606
|
+
}
|
|
9087
9607
|
var toolsCmd = async (_args, deps) => {
|
|
9088
9608
|
const reg = deps.toolRegistry;
|
|
9089
9609
|
if (!reg) return 0;
|
|
@@ -9150,6 +9670,8 @@ var subcommands = {
|
|
|
9150
9670
|
sessions: sessionsCmd,
|
|
9151
9671
|
config: configCmd,
|
|
9152
9672
|
rewind: rewindCmd,
|
|
9673
|
+
replay: replayCmd,
|
|
9674
|
+
audit: auditCmd,
|
|
9153
9675
|
tools: toolsCmd,
|
|
9154
9676
|
skills: skillsCmd,
|
|
9155
9677
|
providers: providersCmd,
|
|
@@ -9186,27 +9708,35 @@ function fmtDuration(ms) {
|
|
|
9186
9708
|
const remMin = m - h * 60;
|
|
9187
9709
|
return `${h}h${remMin}m`;
|
|
9188
9710
|
}
|
|
9189
|
-
function fmtTaskResultLine(r,
|
|
9711
|
+
function fmtTaskResultLine(r, color41) {
|
|
9190
9712
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
9191
9713
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
9192
9714
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
9193
9715
|
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}${
|
|
9716
|
+
const errKindChip = errKind ? color41.dim(` [${errKind}]`) : "";
|
|
9717
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color41.dim(errTail)}` : "";
|
|
9196
9718
|
switch (r.status) {
|
|
9197
9719
|
case "success":
|
|
9198
|
-
return { mark:
|
|
9720
|
+
return { mark: color41.green("\u2713"), stats, tail: "" };
|
|
9199
9721
|
case "timeout":
|
|
9200
|
-
return { mark:
|
|
9722
|
+
return { mark: color41.yellow("\u23F1"), stats: `${color41.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
9201
9723
|
case "stopped":
|
|
9202
|
-
return { mark:
|
|
9724
|
+
return { mark: color41.dim("\u2298"), stats: `${color41.dim("stopped")} ${stats}`, tail: errSnip };
|
|
9203
9725
|
case "failed":
|
|
9204
|
-
return { mark:
|
|
9726
|
+
return { mark: color41.red("\u2717"), stats: `${color41.red("failed")} ${stats}`, tail: errSnip };
|
|
9205
9727
|
}
|
|
9206
9728
|
}
|
|
9207
9729
|
|
|
9208
9730
|
// src/boot.ts
|
|
9209
9731
|
init_update_check();
|
|
9732
|
+
var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
|
|
9733
|
+
function resolveBundledOverlayFile() {
|
|
9734
|
+
try {
|
|
9735
|
+
return fileURLToPath(new URL("../data/providers.json", import.meta.url));
|
|
9736
|
+
} catch {
|
|
9737
|
+
return void 0;
|
|
9738
|
+
}
|
|
9739
|
+
}
|
|
9210
9740
|
function resolveBundledSkillsDir() {
|
|
9211
9741
|
try {
|
|
9212
9742
|
const req2 = createRequire(import.meta.url);
|
|
@@ -9222,6 +9752,10 @@ async function boot(argv) {
|
|
|
9222
9752
|
flags["resume"] = positional[1];
|
|
9223
9753
|
positional.splice(0, 2);
|
|
9224
9754
|
}
|
|
9755
|
+
if (positional.length === 0) {
|
|
9756
|
+
if (flags["help"] === true) positional.push("help");
|
|
9757
|
+
else if (flags["version"] === true) positional.push("version");
|
|
9758
|
+
}
|
|
9225
9759
|
let bootResult;
|
|
9226
9760
|
try {
|
|
9227
9761
|
bootResult = await bootConfig(flags);
|
|
@@ -9238,7 +9772,12 @@ async function boot(argv) {
|
|
|
9238
9772
|
const reader = new ReadlineInputReader({ historyFile: wpaths.historyFile });
|
|
9239
9773
|
const modelsRegistry = new DefaultModelsRegistry({
|
|
9240
9774
|
cacheFile: wpaths.modelsCache,
|
|
9241
|
-
ttlSeconds: 24 * 3600
|
|
9775
|
+
ttlSeconds: 24 * 3600,
|
|
9776
|
+
// Curated overlay merged on top of models.dev: fetched from GitHub raw for
|
|
9777
|
+
// freshness, with the bundled file as the offline floor.
|
|
9778
|
+
overlayUrl: GITHUB_PROVIDERS_OVERLAY_URL,
|
|
9779
|
+
overlayFile: resolveBundledOverlayFile(),
|
|
9780
|
+
overlayCacheFile: wpaths.modelsOverlayCache
|
|
9242
9781
|
});
|
|
9243
9782
|
let updateInfo;
|
|
9244
9783
|
if (!flags["no-check"] && !process.env["WRONGSTACK_NO_CHECK"]) {
|
|
@@ -9691,7 +10230,7 @@ async function runRepl(opts) {
|
|
|
9691
10230
|
);
|
|
9692
10231
|
interrupts = 0;
|
|
9693
10232
|
try {
|
|
9694
|
-
|
|
10233
|
+
await engine.runOneIteration();
|
|
9695
10234
|
const afterGoal = await loadGoalSafe(opts);
|
|
9696
10235
|
const last = afterGoal?.journal[afterGoal.journal.length - 1];
|
|
9697
10236
|
if (coord) {
|
|
@@ -9805,7 +10344,7 @@ ${color.dim(taskList2)}
|
|
|
9805
10344
|
}
|
|
9806
10345
|
}
|
|
9807
10346
|
}
|
|
9808
|
-
} catch (
|
|
10347
|
+
} catch (_runErr) {
|
|
9809
10348
|
opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
|
|
9810
10349
|
}
|
|
9811
10350
|
}
|
|
@@ -10391,9 +10930,15 @@ async function execute(deps) {
|
|
|
10391
10930
|
// Default OFF so the terminal's native scrollback works for chat
|
|
10392
10931
|
// history out of the box (mouse wheel / Shift+PgUp). Users who hit
|
|
10393
10932
|
// resize/overlay-leak artifacts can opt back into alt-screen with
|
|
10394
|
-
// `--alt-screen
|
|
10395
|
-
// when both are passed.
|
|
10933
|
+
// `--alt-screen`. `--no-alt-screen` still wins when both are passed.
|
|
10396
10934
|
altScreen: flags["alt-screen"] === true && flags["no-alt-screen"] !== true,
|
|
10935
|
+
// Full mouse mode: clickable pickers + in-app wheel scroll. Opt-in
|
|
10936
|
+
// via --mouse. The TUI combines altScreen || mouse to determine
|
|
10937
|
+
// whether to use the managed viewport (ScrollableHistory + in-app
|
|
10938
|
+
// scroll); --mouse alone enables that without requiring --alt-screen.
|
|
10939
|
+
// Mouse mode takes ownership of the terminal's mouse, suspending
|
|
10940
|
+
// native scroll/copy until `/mouse off`.
|
|
10941
|
+
mouse: flags.mouse === true,
|
|
10397
10942
|
director,
|
|
10398
10943
|
fleetRoster,
|
|
10399
10944
|
onAfterExit: () => {
|
|
@@ -10505,7 +11050,7 @@ async function execute(deps) {
|
|
|
10505
11050
|
fleetStatusLine?.stop();
|
|
10506
11051
|
try {
|
|
10507
11052
|
stats.render(renderer);
|
|
10508
|
-
} catch (
|
|
11053
|
+
} catch (_err) {
|
|
10509
11054
|
}
|
|
10510
11055
|
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
10511
11056
|
await mcpRegistry.stopAll();
|
|
@@ -10535,6 +11080,10 @@ var MultiAgentHost = class {
|
|
|
10535
11080
|
* critic.evaluation) onto the fleet bus without needing the tool registered
|
|
10536
11081
|
* in the host's ToolRegistry. */
|
|
10537
11082
|
fleetEmitTool;
|
|
11083
|
+
/** Own FleetStatusTool — created in buildDirector() so subagents in director
|
|
11084
|
+
* mode can read fleet state (subagent statuses, task progress) without the
|
|
11085
|
+
* tool being in the host's ToolRegistry. */
|
|
11086
|
+
fleetStatusTool;
|
|
10538
11087
|
/** Lazily built alongside the director — produces per-subagent JSONL
|
|
10539
11088
|
* writers under `<sessionsRoot>/<runId>/`. Null without sessionsRoot. */
|
|
10540
11089
|
sessionFactory;
|
|
@@ -10571,7 +11120,7 @@ var MultiAgentHost = class {
|
|
|
10571
11120
|
getDirector() {
|
|
10572
11121
|
return this.director;
|
|
10573
11122
|
}
|
|
10574
|
-
async ensureCoordinator(
|
|
11123
|
+
async ensureCoordinator(_config) {
|
|
10575
11124
|
await this.buildDirector();
|
|
10576
11125
|
}
|
|
10577
11126
|
async buildDirector() {
|
|
@@ -10638,6 +11187,15 @@ var MultiAgentHost = class {
|
|
|
10638
11187
|
totalExtensions: payload.totalExtensions
|
|
10639
11188
|
});
|
|
10640
11189
|
});
|
|
11190
|
+
this.director.fleet.filter("ctx.pct", (e) => {
|
|
11191
|
+
const payload = e.payload;
|
|
11192
|
+
this.deps.events.emit("subagent.ctx_pct", {
|
|
11193
|
+
subagentId: e.subagentId,
|
|
11194
|
+
load: payload.load,
|
|
11195
|
+
tokens: payload.tokens,
|
|
11196
|
+
maxContext: payload.maxContext
|
|
11197
|
+
});
|
|
11198
|
+
});
|
|
10641
11199
|
this.getCoordinator().on(
|
|
10642
11200
|
"task.assigned",
|
|
10643
11201
|
({
|
|
@@ -10652,6 +11210,7 @@ var MultiAgentHost = class {
|
|
|
10652
11210
|
}
|
|
10653
11211
|
);
|
|
10654
11212
|
this.fleetEmitTool = makeFleetEmitTool(this.director);
|
|
11213
|
+
this.fleetStatusTool = makeFleetStatusTool(this.director);
|
|
10655
11214
|
const runner = await this.buildSubagentRunner(config);
|
|
10656
11215
|
this.getCoordinator().setRunner(runner);
|
|
10657
11216
|
}
|
|
@@ -10674,7 +11233,11 @@ var MultiAgentHost = class {
|
|
|
10674
11233
|
makeSubagentFactory(config) {
|
|
10675
11234
|
return async (subCfg) => {
|
|
10676
11235
|
const events = new EventBus();
|
|
10677
|
-
const provider = await this.buildSubagentProvider(
|
|
11236
|
+
const provider = await this.buildSubagentProvider(
|
|
11237
|
+
config,
|
|
11238
|
+
subCfg.provider,
|
|
11239
|
+
subCfg.model ?? config.model
|
|
11240
|
+
);
|
|
10678
11241
|
const subCwd = subCfg.cwd ?? this.deps.cwd;
|
|
10679
11242
|
const baseSystem = await this.deps.systemPromptBuilder.build({
|
|
10680
11243
|
cwd: subCwd,
|
|
@@ -10709,11 +11272,17 @@ var MultiAgentHost = class {
|
|
|
10709
11272
|
}
|
|
10710
11273
|
const tools = subCfg.tools ? [...subCfg.tools] : void 0;
|
|
10711
11274
|
let injectedFleetEmit;
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
11275
|
+
let injectedFleetStatus;
|
|
11276
|
+
if (tools) {
|
|
11277
|
+
const emitIdx = tools.indexOf("fleet_emit");
|
|
11278
|
+
if (emitIdx >= 0 && this.fleetEmitTool) {
|
|
11279
|
+
tools.splice(emitIdx, 1);
|
|
11280
|
+
injectedFleetEmit = this.fleetEmitTool;
|
|
11281
|
+
}
|
|
11282
|
+
const statusIdx = tools.indexOf("fleet_status");
|
|
11283
|
+
if (statusIdx >= 0 && this.fleetStatusTool) {
|
|
11284
|
+
tools.splice(statusIdx, 1);
|
|
11285
|
+
injectedFleetStatus = this.fleetStatusTool;
|
|
10717
11286
|
}
|
|
10718
11287
|
}
|
|
10719
11288
|
const ctx = new Context({
|
|
@@ -10729,6 +11298,7 @@ var MultiAgentHost = class {
|
|
|
10729
11298
|
});
|
|
10730
11299
|
const baseRegistry = this.subagentToolRegistry(tools);
|
|
10731
11300
|
if (injectedFleetEmit) baseRegistry.register(injectedFleetEmit);
|
|
11301
|
+
if (injectedFleetStatus) baseRegistry.register(injectedFleetStatus);
|
|
10732
11302
|
const toolExecutor = new ToolExecutor(baseRegistry, {
|
|
10733
11303
|
permissionPolicy: new AutoApprovePermissionPolicy(),
|
|
10734
11304
|
secretScrubber: this.deps.secretScrubber,
|
|
@@ -10805,17 +11375,28 @@ var MultiAgentHost = class {
|
|
|
10805
11375
|
* not configured (so a typo doesn't crash the whole run — we just
|
|
10806
11376
|
* use the leader and the calling code can decide to error later).
|
|
10807
11377
|
*/
|
|
10808
|
-
async buildSubagentProvider(config, overrideId) {
|
|
11378
|
+
async buildSubagentProvider(config, overrideId, model) {
|
|
10809
11379
|
const providerId = overrideId && config.providers?.[overrideId] ? overrideId : config.provider;
|
|
10810
11380
|
const newCfg = config.providers?.[providerId] ?? {
|
|
10811
11381
|
type: providerId,
|
|
10812
11382
|
apiKey: config.apiKey,
|
|
10813
11383
|
baseUrl: config.baseUrl
|
|
10814
11384
|
};
|
|
10815
|
-
|
|
11385
|
+
const provider = makeProviderFromConfig(providerId, {
|
|
10816
11386
|
...newCfg,
|
|
10817
11387
|
type: providerId
|
|
10818
11388
|
});
|
|
11389
|
+
if (this.deps.modelsRegistry) {
|
|
11390
|
+
const resolvedModel = model ?? config.model;
|
|
11391
|
+
const caps = await capabilitiesFor(
|
|
11392
|
+
this.deps.modelsRegistry,
|
|
11393
|
+
providerId,
|
|
11394
|
+
resolvedModel
|
|
11395
|
+
).catch(() => void 0);
|
|
11396
|
+
const mc = caps?.maxContext ?? config.context?.effectiveMaxContext ?? provider.capabilities.maxContext;
|
|
11397
|
+
if (mc && mc > 0) provider.capabilities.maxContext = mc;
|
|
11398
|
+
}
|
|
11399
|
+
return provider;
|
|
10819
11400
|
}
|
|
10820
11401
|
async spawnACP(subagentId, task, config) {
|
|
10821
11402
|
const taskId = randomUUID();
|
|
@@ -11140,9 +11721,9 @@ function makeConfirmAwaiter(reader) {
|
|
|
11140
11721
|
function stringifyInput(input) {
|
|
11141
11722
|
if (!input || typeof input !== "object") return "";
|
|
11142
11723
|
const obj = input;
|
|
11143
|
-
return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${
|
|
11724
|
+
return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${truncate2(JSON.stringify(v), 80)}`).join(" ");
|
|
11144
11725
|
}
|
|
11145
|
-
function
|
|
11726
|
+
function truncate2(s, max) {
|
|
11146
11727
|
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
11147
11728
|
}
|
|
11148
11729
|
function hasDiff(input) {
|
|
@@ -11150,6 +11731,26 @@ function hasDiff(input) {
|
|
|
11150
11731
|
input && typeof input === "object" && "diff" in input
|
|
11151
11732
|
);
|
|
11152
11733
|
}
|
|
11734
|
+
function bindReplayToContainer(opts) {
|
|
11735
|
+
const { container, wpaths, sessionId, mode, logger } = opts;
|
|
11736
|
+
if (!opts.container.has(TOKENS.ProviderRunner)) {
|
|
11737
|
+
container.bind(TOKENS.ProviderRunner, () => ({
|
|
11738
|
+
run: (o) => runProviderWithRetry(o)
|
|
11739
|
+
}));
|
|
11740
|
+
}
|
|
11741
|
+
const inner = container.resolve(TOKENS.ProviderRunner);
|
|
11742
|
+
const log = new ReplayLogStore({ dir: wpaths.projectSessions });
|
|
11743
|
+
const wrapped = new ReplayProviderRunner(inner, {
|
|
11744
|
+
log,
|
|
11745
|
+
sessionId,
|
|
11746
|
+
mode,
|
|
11747
|
+
logger: logger ? {
|
|
11748
|
+
debug: (m) => logger.debug?.(m),
|
|
11749
|
+
warn: (m) => logger.warn?.(m)
|
|
11750
|
+
} : void 0
|
|
11751
|
+
});
|
|
11752
|
+
container.bind(TOKENS.ProviderRunner, () => wrapped);
|
|
11753
|
+
}
|
|
11153
11754
|
var SessionStats = class {
|
|
11154
11755
|
tokenCounter;
|
|
11155
11756
|
startedAt = Date.now();
|
|
@@ -11577,6 +12178,13 @@ function renderProgress3(ratio, width) {
|
|
|
11577
12178
|
const capped = Math.min(width, filled);
|
|
11578
12179
|
return FILLED2.repeat(capped) + EMPTY2.repeat(width - capped);
|
|
11579
12180
|
}
|
|
12181
|
+
var createSessionEventBridge = (_writer, level) => ({
|
|
12182
|
+
append: async (_e) => {
|
|
12183
|
+
},
|
|
12184
|
+
level: level ?? "standard",
|
|
12185
|
+
allows: () => true
|
|
12186
|
+
});
|
|
12187
|
+
var resolveAuditLevel = (cfg) => cfg?.session?.auditLevel ?? "standard";
|
|
11580
12188
|
function setupPipelines(params) {
|
|
11581
12189
|
const { events, logger } = params;
|
|
11582
12190
|
const pipelines = createDefaultPipelines();
|
|
@@ -11603,11 +12211,13 @@ function setupPipelines(params) {
|
|
|
11603
12211
|
return pipelines;
|
|
11604
12212
|
}
|
|
11605
12213
|
async function setupCompaction(params) {
|
|
11606
|
-
const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
|
|
12214
|
+
const { compactor, events, modelsRegistry, context, config, provider, pipelines, fullConfig, sessionWriter, sessionBridge: providedBridge } = params;
|
|
11607
12215
|
const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
|
|
11608
12216
|
const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
|
|
11609
12217
|
let autoCompactor;
|
|
11610
12218
|
if (config.context.autoCompact !== false) {
|
|
12219
|
+
const auditLevel = resolveAuditLevel(fullConfig ?? config);
|
|
12220
|
+
const sessionBridge = providedBridge ?? createSessionEventBridge(sessionWriter, auditLevel);
|
|
11611
12221
|
autoCompactor = new AutoCompactionMiddleware(
|
|
11612
12222
|
compactor,
|
|
11613
12223
|
effectiveMaxContext,
|
|
@@ -11619,7 +12229,12 @@ async function setupCompaction(params) {
|
|
|
11619
12229
|
soft: config.context.softThreshold,
|
|
11620
12230
|
hard: config.context.hardThreshold
|
|
11621
12231
|
},
|
|
11622
|
-
{
|
|
12232
|
+
{
|
|
12233
|
+
aggressiveOn: "soft",
|
|
12234
|
+
failureMode: "throw_on_hard",
|
|
12235
|
+
events,
|
|
12236
|
+
sessionBridge
|
|
12237
|
+
}
|
|
11623
12238
|
);
|
|
11624
12239
|
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
11625
12240
|
}
|
|
@@ -11628,7 +12243,6 @@ async function setupCompaction(params) {
|
|
|
11628
12243
|
function createAgent(params) {
|
|
11629
12244
|
const secretScrubber = params.container.resolve(TOKENS.SecretScrubber);
|
|
11630
12245
|
const renderer = params.container.has(TOKENS.Renderer) ? params.container.resolve(TOKENS.Renderer) : void 0;
|
|
11631
|
-
params.container.resolve(TOKENS.Logger);
|
|
11632
12246
|
const toolExecutor = new ToolExecutor(params.tools, {
|
|
11633
12247
|
permissionPolicy: params.permissionPolicy ?? params.container.resolve(TOKENS.PermissionPolicy),
|
|
11634
12248
|
secretScrubber,
|
|
@@ -11993,6 +12607,21 @@ async function setupSession(params) {
|
|
|
11993
12607
|
}
|
|
11994
12608
|
|
|
11995
12609
|
// src/index.ts
|
|
12610
|
+
var createSessionEventBridge2 = (_writer, level, _opts) => ({
|
|
12611
|
+
append: async (_e) => {
|
|
12612
|
+
},
|
|
12613
|
+
level: level ?? "standard",
|
|
12614
|
+
allows: () => true
|
|
12615
|
+
});
|
|
12616
|
+
var resolveAuditLevel2 = (cfg) => cfg?.session?.auditLevel ?? "standard";
|
|
12617
|
+
var resolveSessionLoggingConfig = (cfg) => ({
|
|
12618
|
+
auditLevel: resolveAuditLevel2(cfg),
|
|
12619
|
+
sampling: {
|
|
12620
|
+
toolProgress: {
|
|
12621
|
+
sampleRate: cfg?.session?.sampling?.toolProgress?.sampleRate ?? 8
|
|
12622
|
+
}
|
|
12623
|
+
}
|
|
12624
|
+
});
|
|
11996
12625
|
function resolveBundledSkillsDir2() {
|
|
11997
12626
|
try {
|
|
11998
12627
|
const req2 = createRequire(import.meta.url);
|
|
@@ -12012,7 +12641,6 @@ async function main(argv) {
|
|
|
12012
12641
|
wpaths,
|
|
12013
12642
|
cwd,
|
|
12014
12643
|
projectRoot,
|
|
12015
|
-
userHome,
|
|
12016
12644
|
flags,
|
|
12017
12645
|
positional,
|
|
12018
12646
|
modelsRegistry,
|
|
@@ -12054,6 +12682,22 @@ async function main(argv) {
|
|
|
12054
12682
|
compactor: { preserveK: config.context.preserveK, eliseThreshold: config.context.eliseThreshold },
|
|
12055
12683
|
bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
|
|
12056
12684
|
});
|
|
12685
|
+
const replayFlag = flags["replay"];
|
|
12686
|
+
const recordFlag = flags["record"];
|
|
12687
|
+
if (typeof replayFlag === "string" || recordFlag === true) {
|
|
12688
|
+
const sessionId = typeof replayFlag === "string" ? replayFlag : `record-${Date.now()}`;
|
|
12689
|
+
const mode = recordFlag === true ? "record" : "replay";
|
|
12690
|
+
bindReplayToContainer({
|
|
12691
|
+
container,
|
|
12692
|
+
wpaths,
|
|
12693
|
+
sessionId,
|
|
12694
|
+
mode,
|
|
12695
|
+
logger
|
|
12696
|
+
});
|
|
12697
|
+
logger.info(
|
|
12698
|
+
`replay: ProviderRunner bound in '${mode}' mode for session ${sessionId}`
|
|
12699
|
+
);
|
|
12700
|
+
}
|
|
12057
12701
|
const configStore = container.resolve(TOKENS.ConfigStore);
|
|
12058
12702
|
container.bind(TOKENS.PathResolver, () => pathResolver);
|
|
12059
12703
|
container.bind(TOKENS.Renderer, () => renderer);
|
|
@@ -12121,7 +12765,7 @@ async function main(argv) {
|
|
|
12121
12765
|
}
|
|
12122
12766
|
const events = new EventBus();
|
|
12123
12767
|
events.setLogger(logger);
|
|
12124
|
-
const { metricsSink, healthRegistry
|
|
12768
|
+
const { metricsSink, healthRegistry } = (() => {
|
|
12125
12769
|
const ms = setupMetrics({ flags, wpaths, events, logger, config: { provider: config.provider, model: config.model } });
|
|
12126
12770
|
return ms;
|
|
12127
12771
|
})();
|
|
@@ -12200,7 +12844,6 @@ async function main(argv) {
|
|
|
12200
12844
|
});
|
|
12201
12845
|
const session = sessResult.session;
|
|
12202
12846
|
sessionRef.current = session;
|
|
12203
|
-
sessResult.restoredMessages;
|
|
12204
12847
|
const context = sessResult.context;
|
|
12205
12848
|
const attachments = sessResult.attachments;
|
|
12206
12849
|
const recoveryLock = sessResult.recoveryLock;
|
|
@@ -12208,18 +12851,99 @@ async function main(argv) {
|
|
|
12208
12851
|
const planPath = sessResult.planPath;
|
|
12209
12852
|
const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
|
|
12210
12853
|
const priorFleetState = sessResult.priorFleetState;
|
|
12854
|
+
const sessionConfig = resolveSessionLoggingConfig(config);
|
|
12855
|
+
const sessionBridge = createSessionEventBridge2(
|
|
12856
|
+
session,
|
|
12857
|
+
sessionConfig.auditLevel);
|
|
12211
12858
|
const stats = new SessionStats(events, tokenCounter);
|
|
12212
12859
|
const errorRing = [];
|
|
12213
12860
|
events.on("error", (e) => {
|
|
12214
12861
|
const err = e.err;
|
|
12215
12862
|
const code = err && typeof err === "object" && "code" in err && typeof err.code === "string" ? err.code : "UNKNOWN";
|
|
12216
12863
|
const message = e.err instanceof Error ? e.err.message : String(e.err);
|
|
12217
|
-
|
|
12864
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
12865
|
+
errorRing.push({ ts, phase: e.phase, code, message });
|
|
12218
12866
|
if (errorRing.length > 5) errorRing.shift();
|
|
12867
|
+
sessionBridge.append({
|
|
12868
|
+
type: "error",
|
|
12869
|
+
ts,
|
|
12870
|
+
message,
|
|
12871
|
+
phase: e.phase
|
|
12872
|
+
}).catch(() => {
|
|
12873
|
+
});
|
|
12874
|
+
});
|
|
12875
|
+
events.on("tool.started", (e) => {
|
|
12876
|
+
sessionBridge.append({
|
|
12877
|
+
type: "tool_call_start",
|
|
12878
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12879
|
+
name: e.name,
|
|
12880
|
+
id: e.id,
|
|
12881
|
+
input: e.input
|
|
12882
|
+
}).catch(() => {
|
|
12883
|
+
});
|
|
12884
|
+
});
|
|
12885
|
+
events.on("tool.executed", (e) => {
|
|
12886
|
+
sessionBridge.append({
|
|
12887
|
+
type: "tool_call_end",
|
|
12888
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12889
|
+
name: e.name,
|
|
12890
|
+
id: e.id ?? "",
|
|
12891
|
+
durationMs: e.durationMs,
|
|
12892
|
+
outputSize: e.outputBytes ?? 0,
|
|
12893
|
+
ok: e.ok,
|
|
12894
|
+
outputBytes: e.outputBytes,
|
|
12895
|
+
outputTokens: e.outputTokens,
|
|
12896
|
+
outputLines: e.outputLines
|
|
12897
|
+
}).catch(() => {
|
|
12898
|
+
});
|
|
12899
|
+
});
|
|
12900
|
+
events.on("tool.progress", (e) => {
|
|
12901
|
+
sessionBridge.append({
|
|
12902
|
+
type: "tool_progress",
|
|
12903
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12904
|
+
name: e.name,
|
|
12905
|
+
id: e.id,
|
|
12906
|
+
event: { type: e.event.type, text: e.event.text, data: e.event.data }
|
|
12907
|
+
}).catch(() => {
|
|
12908
|
+
});
|
|
12909
|
+
});
|
|
12910
|
+
events.on("provider.retry", (e) => {
|
|
12911
|
+
sessionBridge.append({
|
|
12912
|
+
type: "provider_retry",
|
|
12913
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12914
|
+
providerId: e.providerId,
|
|
12915
|
+
attempt: e.attempt,
|
|
12916
|
+
delayMs: e.delayMs,
|
|
12917
|
+
status: e.status,
|
|
12918
|
+
description: e.description
|
|
12919
|
+
}).catch(() => {
|
|
12920
|
+
});
|
|
12921
|
+
});
|
|
12922
|
+
events.on("provider.error", (e) => {
|
|
12923
|
+
sessionBridge.append({
|
|
12924
|
+
type: "provider_error",
|
|
12925
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12926
|
+
providerId: e.providerId,
|
|
12927
|
+
status: e.status,
|
|
12928
|
+
description: e.description,
|
|
12929
|
+
retryable: e.retryable
|
|
12930
|
+
}).catch(() => {
|
|
12931
|
+
});
|
|
12219
12932
|
});
|
|
12220
12933
|
const pipelines = setupPipelines({ events, logger });
|
|
12221
12934
|
const compactor = container.resolve(TOKENS.Compactor);
|
|
12222
|
-
const { effectiveMaxContext, autoCompactor } = await setupCompaction({
|
|
12935
|
+
const { effectiveMaxContext, autoCompactor } = await setupCompaction({
|
|
12936
|
+
compactor,
|
|
12937
|
+
events,
|
|
12938
|
+
modelsRegistry,
|
|
12939
|
+
context,
|
|
12940
|
+
config,
|
|
12941
|
+
provider,
|
|
12942
|
+
pipelines,
|
|
12943
|
+
fullConfig: config,
|
|
12944
|
+
sessionBridge
|
|
12945
|
+
// share the same bridge for consistent audit logging (compaction + errors + future)
|
|
12946
|
+
});
|
|
12223
12947
|
const refreshMaxContext = async (providerId, modelId) => {
|
|
12224
12948
|
if (!autoCompactor) return;
|
|
12225
12949
|
const cap = await capabilitiesFor(modelsRegistry, providerId, modelId).catch(() => void 0);
|
|
@@ -12316,6 +13040,7 @@ async function main(argv) {
|
|
|
12316
13040
|
toolRegistry,
|
|
12317
13041
|
providerRegistry,
|
|
12318
13042
|
configStore,
|
|
13043
|
+
modelsRegistry,
|
|
12319
13044
|
events,
|
|
12320
13045
|
systemPromptBuilder: promptBuilder,
|
|
12321
13046
|
session,
|