@wrongstack/cli 0.2.0 → 0.3.2
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/README.md +1 -1
- package/dist/index.js +468 -444
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/dist/index.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color,
|
|
2
|
+
import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, DefaultSessionStore, DefaultSkillLoader, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, InputBuilder, decryptConfigSecrets, encryptConfigSecrets, DefaultPluginAPI } from '@wrongstack/core';
|
|
3
3
|
import * as fs3 from 'fs/promises';
|
|
4
4
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
5
5
|
import { writeFileSync } from 'fs';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
|
-
import * as
|
|
7
|
+
import * as path14 from 'path';
|
|
8
8
|
import { MCPRegistry } from '@wrongstack/mcp';
|
|
9
9
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
10
|
+
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
10
11
|
import { rememberTool, forgetTool } from '@wrongstack/tools';
|
|
11
|
-
import {
|
|
12
|
+
import { builtinToolsPack } from '@wrongstack/tools/pack';
|
|
12
13
|
import * as os3 from 'os';
|
|
13
14
|
import * as readline from 'readline';
|
|
14
15
|
import { randomUUID } from 'crypto';
|
|
16
|
+
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
15
17
|
|
|
16
18
|
var __defProp = Object.defineProperty;
|
|
17
19
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -568,6 +570,216 @@ var init_plugin_api_factory = __esm({
|
|
|
568
570
|
"src/plugin-api-factory.ts"() {
|
|
569
571
|
}
|
|
570
572
|
});
|
|
573
|
+
async function setupProvider(params) {
|
|
574
|
+
const { config, modelsRegistry, logger } = params;
|
|
575
|
+
const savedProviderCfg = config.providers?.[config.provider];
|
|
576
|
+
let resolvedProvider = await modelsRegistry.getProvider(config.provider).catch(() => void 0);
|
|
577
|
+
if (!resolvedProvider && savedProviderCfg?.type && savedProviderCfg.type !== config.provider) {
|
|
578
|
+
resolvedProvider = await modelsRegistry.getProvider(savedProviderCfg.type).catch(() => void 0);
|
|
579
|
+
}
|
|
580
|
+
if (!resolvedProvider) {
|
|
581
|
+
if (!savedProviderCfg?.family) {
|
|
582
|
+
logger.warn(
|
|
583
|
+
`Provider "${config.provider}" not found in models.dev. Continuing with raw config.`
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
} else if (resolvedProvider.family === "unsupported" && !savedProviderCfg?.family) {
|
|
587
|
+
throw Object.assign(
|
|
588
|
+
new Error(
|
|
589
|
+
`Provider "${config.provider}" uses an unsupported wire family (${resolvedProvider.npm}). Install a plugin to enable it, or pick a different provider.`
|
|
590
|
+
),
|
|
591
|
+
{ code: "UNSUPPORTED_PROVIDER" }
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
const providerRegistry = new ProviderRegistry();
|
|
595
|
+
if (config.features.modelsRegistry) {
|
|
596
|
+
try {
|
|
597
|
+
const factories = await buildProviderFactoriesFromRegistry({
|
|
598
|
+
registry: modelsRegistry,
|
|
599
|
+
log: logger
|
|
600
|
+
});
|
|
601
|
+
for (const f of factories) providerRegistry.register(f);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`Failed to load models.dev registry: ${err instanceof Error ? err.message : err}
|
|
605
|
+
Try \`wstack models refresh\` once you have network access, or run with --no-features.`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const providerConfig = config.providers?.[config.provider] ?? {
|
|
610
|
+
type: config.provider,
|
|
611
|
+
apiKey: config.apiKey,
|
|
612
|
+
baseUrl: config.baseUrl
|
|
613
|
+
};
|
|
614
|
+
let provider;
|
|
615
|
+
try {
|
|
616
|
+
const cfgWithType = { ...providerConfig, type: config.provider };
|
|
617
|
+
if (config.features.modelsRegistry && providerRegistry.has(config.provider)) {
|
|
618
|
+
provider = providerRegistry.create(cfgWithType);
|
|
619
|
+
} else {
|
|
620
|
+
provider = makeProviderFromConfig(config.provider, cfgWithType);
|
|
621
|
+
}
|
|
622
|
+
} catch (err) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Failed to create provider: ${err instanceof Error ? err.message : err}`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
return { resolvedProvider, provider, providerRegistry };
|
|
628
|
+
}
|
|
629
|
+
async function setupSession(params) {
|
|
630
|
+
const { config, wpaths, projectRoot, cwd, sessionStore, systemPrompt, provider, tokenCounter, renderer, flags, onRecovery } = params;
|
|
631
|
+
let resumeId = typeof flags["resume"] === "string" ? flags["resume"] : void 0;
|
|
632
|
+
const recoveryLock = new RecoveryLock({ dir: wpaths.projectSessions, sessionStore });
|
|
633
|
+
if (!resumeId && !flags["no-recovery"]) {
|
|
634
|
+
const abandoned = await recoveryLock.checkAbandoned();
|
|
635
|
+
if (abandoned && abandoned.messageCount > 0) {
|
|
636
|
+
const choice = await onRecovery(abandoned, !!flags["recover"]);
|
|
637
|
+
if (choice === "resume") resumeId = abandoned.sessionId;
|
|
638
|
+
else if (choice === "delete") {
|
|
639
|
+
await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
|
|
640
|
+
await recoveryLock.clear();
|
|
641
|
+
} else await recoveryLock.clear();
|
|
642
|
+
} else if (abandoned) {
|
|
643
|
+
await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
|
|
644
|
+
await recoveryLock.clear();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
let session;
|
|
648
|
+
let restoredMessages = [];
|
|
649
|
+
if (resumeId) {
|
|
650
|
+
try {
|
|
651
|
+
const resumed = await sessionStore.resume(resumeId);
|
|
652
|
+
session = resumed.writer;
|
|
653
|
+
restoredMessages = resumed.data.messages;
|
|
654
|
+
renderer.writeInfo(`Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
renderer.writeError(`Resume failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
657
|
+
throw Object.assign(new Error("RESUME_FAILED"), { exitCode: 2 });
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
session = await sessionStore.create({ id: "", title: "", model: config.model, provider: config.provider });
|
|
661
|
+
}
|
|
662
|
+
const sessionRef = { current: session };
|
|
663
|
+
await recoveryLock.write(session.id).catch(() => void 0);
|
|
664
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path14.join(wpaths.projectSessions, session.id, "attachments") });
|
|
665
|
+
const queueStore = new QueueStore({ dir: path14.join(wpaths.projectSessions, session.id) });
|
|
666
|
+
const ctxSignal = new AbortController().signal;
|
|
667
|
+
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
668
|
+
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
669
|
+
const todosCheckpointPath = path14.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
670
|
+
if (resumeId) {
|
|
671
|
+
try {
|
|
672
|
+
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
673
|
+
if (restoredTodos && restoredTodos.length > 0) {
|
|
674
|
+
context.state.replaceTodos(restoredTodos);
|
|
675
|
+
renderer.writeInfo(`Restored ${restoredTodos.length} todo${restoredTodos.length === 1 ? "" : "s"} from previous run.`);
|
|
676
|
+
}
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
681
|
+
const planPath = path14.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
682
|
+
context.state.setMeta("plan.path", planPath);
|
|
683
|
+
if (resumeId) {
|
|
684
|
+
try {
|
|
685
|
+
const fleetRoot = path14.join(wpaths.projectSessions, session.id);
|
|
686
|
+
const dirState = await loadDirectorState(path14.join(fleetRoot, "director-state.json"));
|
|
687
|
+
if (dirState) {
|
|
688
|
+
const tCounts = {};
|
|
689
|
+
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
690
|
+
const summary = Object.entries(tCounts).map(([k, v]) => `${v} ${k}`).join(", ");
|
|
691
|
+
renderer.writeInfo(`Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`);
|
|
692
|
+
}
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
const plan = await loadPlan(planPath);
|
|
697
|
+
if (plan && plan.items.length > 0) {
|
|
698
|
+
const open = plan.items.filter((p) => p.status !== "done").length;
|
|
699
|
+
const done = plan.items.length - open;
|
|
700
|
+
renderer.writeInfo(`Plan: ${plan.items.length} item${plan.items.length === 1 ? "" : "s"} (${open} open, ${done} done). Use /plan to review.`);
|
|
701
|
+
}
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint };
|
|
706
|
+
}
|
|
707
|
+
function setupPipelines(params) {
|
|
708
|
+
const { events, logger } = params;
|
|
709
|
+
const pipelines = createDefaultPipelines();
|
|
710
|
+
const installBoundary = (p) => {
|
|
711
|
+
p.setErrorHandler((ev) => {
|
|
712
|
+
const fromPlugin = !!ev.owner && ev.owner !== "core";
|
|
713
|
+
logger.error(
|
|
714
|
+
`Pipeline middleware "${ev.middleware}" crashed (owner=${ev.owner ?? "unknown"}); ${fromPlugin ? "swallowed" : "rethrown"}`,
|
|
715
|
+
ev.err
|
|
716
|
+
);
|
|
717
|
+
events.emit("error", {
|
|
718
|
+
err: ev.err instanceof Error ? ev.err : new Error(String(ev.err)),
|
|
719
|
+
phase: `pipeline:${ev.middleware}`
|
|
720
|
+
});
|
|
721
|
+
return fromPlugin ? "swallow" : "rethrow";
|
|
722
|
+
});
|
|
723
|
+
};
|
|
724
|
+
installBoundary(pipelines.request);
|
|
725
|
+
installBoundary(pipelines.response);
|
|
726
|
+
installBoundary(pipelines.toolCall);
|
|
727
|
+
installBoundary(pipelines.userInput);
|
|
728
|
+
installBoundary(pipelines.assistantOutput);
|
|
729
|
+
installBoundary(pipelines.contextWindow);
|
|
730
|
+
return pipelines;
|
|
731
|
+
}
|
|
732
|
+
async function setupCompaction(params) {
|
|
733
|
+
const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
|
|
734
|
+
const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
|
|
735
|
+
const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
|
|
736
|
+
if (config.context.autoCompact !== false) {
|
|
737
|
+
const autoCompactor = new AutoCompactionMiddleware(
|
|
738
|
+
compactor,
|
|
739
|
+
effectiveMaxContext,
|
|
740
|
+
(ctx) => {
|
|
741
|
+
let total = 0;
|
|
742
|
+
for (const m of ctx.messages) {
|
|
743
|
+
if (typeof m.content === "string") {
|
|
744
|
+
total += Math.ceil(m.content.length / 4);
|
|
745
|
+
} else if (Array.isArray(m.content)) {
|
|
746
|
+
for (const b of m.content) {
|
|
747
|
+
if (b.type === "text") {
|
|
748
|
+
total += Math.ceil(b.text.length / 4);
|
|
749
|
+
} else if (b.type === "tool_use" || b.type === "tool_result") {
|
|
750
|
+
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return total;
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
warn: config.context.warnThreshold,
|
|
759
|
+
soft: config.context.softThreshold,
|
|
760
|
+
hard: config.context.hardThreshold
|
|
761
|
+
},
|
|
762
|
+
{ aggressiveOn: "soft", failureMode: "throw_on_hard", events }
|
|
763
|
+
);
|
|
764
|
+
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
765
|
+
}
|
|
766
|
+
return effectiveMaxContext;
|
|
767
|
+
}
|
|
768
|
+
function createAgent(params) {
|
|
769
|
+
return new Agent({
|
|
770
|
+
container: params.container,
|
|
771
|
+
tools: params.tools,
|
|
772
|
+
providers: params.providers,
|
|
773
|
+
events: params.events,
|
|
774
|
+
pipelines: params.pipelines,
|
|
775
|
+
context: params.context,
|
|
776
|
+
maxIterations: params.config.tools.maxIterations,
|
|
777
|
+
iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
|
|
778
|
+
executionStrategy: params.config.tools.defaultExecutionStrategy,
|
|
779
|
+
perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
|
|
780
|
+
confirmAwaiter: params.confirmAwaiter
|
|
781
|
+
});
|
|
782
|
+
}
|
|
571
783
|
|
|
572
784
|
// src/arg-parser.ts
|
|
573
785
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -693,7 +905,7 @@ function parseSpawnFlags(input) {
|
|
|
693
905
|
return { description: rest.trim(), opts };
|
|
694
906
|
}
|
|
695
907
|
async function bootConfig(flags) {
|
|
696
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
908
|
+
const cwd = typeof flags["cwd"] === "string" ? path14.resolve(flags["cwd"]) : process.cwd();
|
|
697
909
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
698
910
|
const projectRoot = pathResolver.projectRoot;
|
|
699
911
|
const userHome = os3.homedir();
|
|
@@ -760,7 +972,7 @@ var ReadlineInputReader = class {
|
|
|
760
972
|
history = [];
|
|
761
973
|
pending = false;
|
|
762
974
|
constructor(opts = {}) {
|
|
763
|
-
this.historyFile = opts.historyFile ??
|
|
975
|
+
this.historyFile = opts.historyFile ?? path14.join(os3.homedir(), ".wrongstack", "history");
|
|
764
976
|
}
|
|
765
977
|
async loadHistory() {
|
|
766
978
|
try {
|
|
@@ -772,7 +984,7 @@ var ReadlineInputReader = class {
|
|
|
772
984
|
}
|
|
773
985
|
async saveHistory() {
|
|
774
986
|
try {
|
|
775
|
-
await fs3.mkdir(
|
|
987
|
+
await fs3.mkdir(path14.dirname(this.historyFile), { recursive: true });
|
|
776
988
|
await fs3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
777
989
|
} catch {
|
|
778
990
|
}
|
|
@@ -1228,7 +1440,7 @@ async function saveToGlobalConfig(configPath, provider, model) {
|
|
|
1228
1440
|
async function detectProjectFacts(root) {
|
|
1229
1441
|
const facts = { hints: [] };
|
|
1230
1442
|
try {
|
|
1231
|
-
const pkg = JSON.parse(await fs3.readFile(
|
|
1443
|
+
const pkg = JSON.parse(await fs3.readFile(path14.join(root, "package.json"), "utf8"));
|
|
1232
1444
|
const scripts = pkg.scripts ?? {};
|
|
1233
1445
|
const pm = (pkg.packageManager ?? "npm").split("@")[0] ?? "npm";
|
|
1234
1446
|
if (scripts["build"]) facts.build = `${pm} run build`;
|
|
@@ -1240,28 +1452,28 @@ async function detectProjectFacts(root) {
|
|
|
1240
1452
|
} catch {
|
|
1241
1453
|
}
|
|
1242
1454
|
try {
|
|
1243
|
-
await fs3.access(
|
|
1455
|
+
await fs3.access(path14.join(root, "pyproject.toml"));
|
|
1244
1456
|
facts.test ??= "pytest";
|
|
1245
1457
|
facts.lint ??= "ruff check .";
|
|
1246
1458
|
facts.hints.push("pyproject.toml");
|
|
1247
1459
|
} catch {
|
|
1248
1460
|
}
|
|
1249
1461
|
try {
|
|
1250
|
-
await fs3.access(
|
|
1462
|
+
await fs3.access(path14.join(root, "go.mod"));
|
|
1251
1463
|
facts.build ??= "go build ./...";
|
|
1252
1464
|
facts.test ??= "go test ./...";
|
|
1253
1465
|
facts.hints.push("go.mod");
|
|
1254
1466
|
} catch {
|
|
1255
1467
|
}
|
|
1256
1468
|
try {
|
|
1257
|
-
await fs3.access(
|
|
1469
|
+
await fs3.access(path14.join(root, "Cargo.toml"));
|
|
1258
1470
|
facts.build ??= "cargo build";
|
|
1259
1471
|
facts.test ??= "cargo test";
|
|
1260
1472
|
facts.hints.push("Cargo.toml");
|
|
1261
1473
|
} catch {
|
|
1262
1474
|
}
|
|
1263
1475
|
try {
|
|
1264
|
-
await fs3.access(
|
|
1476
|
+
await fs3.access(path14.join(root, "Makefile"));
|
|
1265
1477
|
facts.build ??= "make";
|
|
1266
1478
|
facts.test ??= "make test";
|
|
1267
1479
|
facts.hints.push("Makefile");
|
|
@@ -1398,7 +1610,9 @@ function buildCompactCommand(opts) {
|
|
|
1398
1610
|
}
|
|
1399
1611
|
const aggressive = args.trim() === "aggressive";
|
|
1400
1612
|
const report = await opts.compactor.compact(ctx, { aggressive });
|
|
1401
|
-
const
|
|
1613
|
+
const reductions = report.reductions.map((r) => `${r.phase}: ${r.saved}`).join(", ");
|
|
1614
|
+
const repaired = report.repaired ? `; repaired ${report.repaired.removedToolUses.length} tool_use, ${report.repaired.removedToolResults.length} tool_result, ${report.repaired.removedMessages} empty messages` : "";
|
|
1615
|
+
const msg = `Compaction: ${report.before} -> ${report.after} tokens (${reductions})${repaired}`;
|
|
1402
1616
|
opts.renderer.writeInfo(msg);
|
|
1403
1617
|
return { message: msg };
|
|
1404
1618
|
}
|
|
@@ -1412,15 +1626,68 @@ function buildContextCommand(opts) {
|
|
|
1412
1626
|
help: [
|
|
1413
1627
|
"Usage:",
|
|
1414
1628
|
" /context Show counts: messages, est. tokens, tool calls, todos, read files.",
|
|
1415
|
-
" /context detail As above, plus model, cwd, projectRoot, and the file list."
|
|
1629
|
+
" /context detail As above, plus model, cwd, projectRoot, and the file list.",
|
|
1630
|
+
" /context repair Repair orphan tool_use/tool_result blocks after manual compaction.",
|
|
1631
|
+
" /context mode List context-window modes.",
|
|
1632
|
+
" /context mode <id> Switch context-window mode for this session."
|
|
1416
1633
|
].join("\n"),
|
|
1417
1634
|
async run(args, ctx) {
|
|
1635
|
+
const trimmed = args.trim();
|
|
1636
|
+
if (trimmed === "mode" || trimmed === "modes") {
|
|
1637
|
+
const active = readPolicy(ctx)?.id ?? "balanced";
|
|
1638
|
+
const msg2 = `${color.bold("Context Window Modes")}
|
|
1639
|
+
${formatContextWindowModeList(active)}`;
|
|
1640
|
+
opts.renderer.write(`${msg2}
|
|
1641
|
+
`);
|
|
1642
|
+
return { message: msg2 };
|
|
1643
|
+
}
|
|
1644
|
+
if (trimmed === "repair") {
|
|
1645
|
+
const before = ctx.messages.length;
|
|
1646
|
+
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
1647
|
+
if (repaired.report.changed) {
|
|
1648
|
+
ctx.state.replaceMessages(repaired.messages);
|
|
1649
|
+
}
|
|
1650
|
+
const msg2 = repaired.report.changed ? [
|
|
1651
|
+
`${color.green("Context repaired")}`,
|
|
1652
|
+
` messages: ${before} -> ${ctx.messages.length}`,
|
|
1653
|
+
` tool_use: removed ${repaired.report.removedToolUses.length}`,
|
|
1654
|
+
` tool_result: removed ${repaired.report.removedToolResults.length}`,
|
|
1655
|
+
` empty msgs: removed ${repaired.report.removedMessages}`
|
|
1656
|
+
].join("\n") : "Context repair: no orphan tool_use/tool_result blocks found.";
|
|
1657
|
+
opts.renderer.write(`${msg2}
|
|
1658
|
+
`);
|
|
1659
|
+
return { message: msg2 };
|
|
1660
|
+
}
|
|
1661
|
+
if (trimmed.startsWith("mode ")) {
|
|
1662
|
+
const id = trimmed.slice("mode ".length).trim();
|
|
1663
|
+
const mode = getContextWindowMode(id);
|
|
1664
|
+
if (!mode) {
|
|
1665
|
+
const msg3 = `Unknown context mode "${id}". Use /context mode to list modes.`;
|
|
1666
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
1667
|
+
`);
|
|
1668
|
+
return { message: msg3 };
|
|
1669
|
+
}
|
|
1670
|
+
const policy2 = resolveContextWindowPolicy({}, mode.id);
|
|
1671
|
+
ctx.meta["contextWindowMode"] = policy2.id;
|
|
1672
|
+
ctx.meta["contextWindowPolicy"] = policy2;
|
|
1673
|
+
const msg2 = [
|
|
1674
|
+
`${color.green("Context mode set:")} ${policy2.id} (${policy2.name})`,
|
|
1675
|
+
` thresholds: warn ${pct(policy2.thresholds.warn)}, soft ${pct(policy2.thresholds.soft)}, hard ${pct(policy2.thresholds.hard)}`,
|
|
1676
|
+
` preserve: last ${policy2.preserveK} user/assistant messages`,
|
|
1677
|
+
` elide: old tool results >= ${policy2.eliseThreshold.toLocaleString()} tokens`
|
|
1678
|
+
].join("\n");
|
|
1679
|
+
opts.renderer.write(`${msg2}
|
|
1680
|
+
`);
|
|
1681
|
+
return { message: msg2 };
|
|
1682
|
+
}
|
|
1418
1683
|
const messages = ctx.messages;
|
|
1419
|
-
const detailed =
|
|
1684
|
+
const detailed = trimmed === "detail";
|
|
1685
|
+
const policy = readPolicy(ctx);
|
|
1420
1686
|
const lines = [
|
|
1421
1687
|
`${color.bold("Context Window")}`,
|
|
1422
1688
|
` messages: ${messages.length} total (${countTurnPairs(messages)} user+assistant pairs)`,
|
|
1423
|
-
` tokens (
|
|
1689
|
+
` tokens (est): ${estimateTokens(messages).toLocaleString()} (chars / 4 estimate)`,
|
|
1690
|
+
` mode: ${policy ? `${policy.id} (${policy.name})` : "balanced"}`,
|
|
1424
1691
|
` system prompt: ${ctx.systemPrompt.length} block${ctx.systemPrompt.length !== 1 ? "s" : ""}`,
|
|
1425
1692
|
` tools: ${countToolUses(messages)} calls made, ${countToolResults(messages)} results in history`,
|
|
1426
1693
|
` read files: ${ctx.readFiles.size} files`,
|
|
@@ -1428,6 +1695,7 @@ function buildContextCommand(opts) {
|
|
|
1428
1695
|
];
|
|
1429
1696
|
if (detailed) {
|
|
1430
1697
|
lines.push(
|
|
1698
|
+
` thresholds: warn ${pct(policy?.thresholds.warn ?? 0.6)}, soft ${pct(policy?.thresholds.soft ?? 0.75)}, hard ${pct(policy?.thresholds.hard ?? 0.9)}`,
|
|
1431
1699
|
` model: ${ctx.model}`,
|
|
1432
1700
|
` cwd: ${ctx.cwd}`,
|
|
1433
1701
|
` projectRoot: ${ctx.projectRoot}`,
|
|
@@ -1442,6 +1710,13 @@ function buildContextCommand(opts) {
|
|
|
1442
1710
|
}
|
|
1443
1711
|
};
|
|
1444
1712
|
}
|
|
1713
|
+
function readPolicy(ctx) {
|
|
1714
|
+
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
1715
|
+
return policy && typeof policy === "object" ? policy : null;
|
|
1716
|
+
}
|
|
1717
|
+
function pct(n) {
|
|
1718
|
+
return `${Math.round(n * 100)}%`;
|
|
1719
|
+
}
|
|
1445
1720
|
|
|
1446
1721
|
// src/slash-commands/diag-stats.ts
|
|
1447
1722
|
function buildDiagCommand(opts) {
|
|
@@ -1643,8 +1918,8 @@ function buildInitCommand(opts) {
|
|
|
1643
1918
|
description: "Scaffold .wrongstack/AGENTS.md in the current project.",
|
|
1644
1919
|
async run(args, ctx) {
|
|
1645
1920
|
const force = args.trim() === "--force";
|
|
1646
|
-
const dir =
|
|
1647
|
-
const file =
|
|
1921
|
+
const dir = path14.join(ctx.projectRoot, ".wrongstack");
|
|
1922
|
+
const file = path14.join(dir, "AGENTS.md");
|
|
1648
1923
|
try {
|
|
1649
1924
|
await fs3.access(file);
|
|
1650
1925
|
if (!force) {
|
|
@@ -2043,13 +2318,13 @@ var MANIFESTS = [
|
|
|
2043
2318
|
];
|
|
2044
2319
|
async function detectProjectKind(projectRoot) {
|
|
2045
2320
|
try {
|
|
2046
|
-
await fs3.access(
|
|
2321
|
+
await fs3.access(path14.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
2047
2322
|
return "initialized";
|
|
2048
2323
|
} catch {
|
|
2049
2324
|
}
|
|
2050
2325
|
for (const m of MANIFESTS) {
|
|
2051
2326
|
try {
|
|
2052
|
-
await fs3.access(
|
|
2327
|
+
await fs3.access(path14.join(projectRoot, m));
|
|
2053
2328
|
return "project";
|
|
2054
2329
|
} catch {
|
|
2055
2330
|
}
|
|
@@ -2057,8 +2332,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
2057
2332
|
return "empty";
|
|
2058
2333
|
}
|
|
2059
2334
|
async function scaffoldAgentsMd(projectRoot) {
|
|
2060
|
-
const dir =
|
|
2061
|
-
const file =
|
|
2335
|
+
const dir = path14.join(projectRoot, ".wrongstack");
|
|
2336
|
+
const file = path14.join(dir, "AGENTS.md");
|
|
2062
2337
|
const facts = await detectProjectFacts(projectRoot);
|
|
2063
2338
|
const body = renderAgentsTemplate(facts);
|
|
2064
2339
|
await fs3.mkdir(dir, { recursive: true });
|
|
@@ -2071,7 +2346,7 @@ async function runProjectCheck(opts) {
|
|
|
2071
2346
|
if (kind === "initialized") {
|
|
2072
2347
|
renderer.write(
|
|
2073
2348
|
`
|
|
2074
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
2349
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path14.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
2075
2350
|
`
|
|
2076
2351
|
);
|
|
2077
2352
|
return true;
|
|
@@ -2367,14 +2642,14 @@ function summarize(value, name) {
|
|
|
2367
2642
|
if (typeof v === "object" && v !== null) {
|
|
2368
2643
|
const o = v;
|
|
2369
2644
|
if (name === "edit") {
|
|
2370
|
-
const
|
|
2645
|
+
const path15 = typeof o["path"] === "string" ? o["path"] : "";
|
|
2371
2646
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
2372
|
-
return `${
|
|
2647
|
+
return `${path15} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
2373
2648
|
}
|
|
2374
2649
|
if (name === "write") {
|
|
2375
|
-
const
|
|
2650
|
+
const path15 = typeof o["path"] === "string" ? o["path"] : "";
|
|
2376
2651
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
2377
|
-
return bytes !== void 0 ? `${
|
|
2652
|
+
return bytes !== void 0 ? `${path15} ${bytes}B` : path15;
|
|
2378
2653
|
}
|
|
2379
2654
|
if (typeof o["count"] === "number") {
|
|
2380
2655
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -3137,7 +3412,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
3137
3412
|
}
|
|
3138
3413
|
try {
|
|
3139
3414
|
await fs3.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
3140
|
-
const probe =
|
|
3415
|
+
const probe = path14.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
3141
3416
|
await fs3.writeFile(probe, "");
|
|
3142
3417
|
await fs3.unlink(probe);
|
|
3143
3418
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -3240,8 +3515,8 @@ var exportCmd = async (args, deps) => {
|
|
|
3240
3515
|
return 1;
|
|
3241
3516
|
}
|
|
3242
3517
|
if (output) {
|
|
3243
|
-
await fs3.mkdir(
|
|
3244
|
-
await fs3.writeFile(
|
|
3518
|
+
await fs3.mkdir(path14.dirname(path14.resolve(deps.cwd, output)), { recursive: true });
|
|
3519
|
+
await fs3.writeFile(path14.resolve(deps.cwd, output), rendered, "utf8");
|
|
3245
3520
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
3246
3521
|
`);
|
|
3247
3522
|
} else {
|
|
@@ -3302,8 +3577,8 @@ var initCmd = async (_args, deps) => {
|
|
|
3302
3577
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
3303
3578
|
if (apiKey) config.apiKey = apiKey;
|
|
3304
3579
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(config, null, 2));
|
|
3305
|
-
await fs3.mkdir(
|
|
3306
|
-
const agentsFile =
|
|
3580
|
+
await fs3.mkdir(path14.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
3581
|
+
const agentsFile = path14.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
3307
3582
|
try {
|
|
3308
3583
|
await fs3.access(agentsFile);
|
|
3309
3584
|
} catch {
|
|
@@ -3317,86 +3592,7 @@ var initCmd = async (_args, deps) => {
|
|
|
3317
3592
|
deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
|
|
3318
3593
|
return 0;
|
|
3319
3594
|
};
|
|
3320
|
-
var BUILT_IN_MCP =
|
|
3321
|
-
filesystem: {
|
|
3322
|
-
name: "filesystem",
|
|
3323
|
-
transport: "stdio",
|
|
3324
|
-
command: "npx",
|
|
3325
|
-
args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
|
|
3326
|
-
permission: "confirm",
|
|
3327
|
-
description: "Read, write, and navigate the local filesystem"
|
|
3328
|
-
},
|
|
3329
|
-
github: {
|
|
3330
|
-
name: "github",
|
|
3331
|
-
transport: "stdio",
|
|
3332
|
-
command: "npx",
|
|
3333
|
-
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
3334
|
-
permission: "confirm",
|
|
3335
|
-
description: "GitHub API \u2014 issues, PRs, repos, search"
|
|
3336
|
-
},
|
|
3337
|
-
context7: {
|
|
3338
|
-
name: "context7",
|
|
3339
|
-
transport: "streamable-http",
|
|
3340
|
-
url: "https://server.context7.ai/mcp",
|
|
3341
|
-
permission: "confirm",
|
|
3342
|
-
description: "Codebase-aware documentation and Q&A"
|
|
3343
|
-
},
|
|
3344
|
-
"brave-search": {
|
|
3345
|
-
name: "brave-search",
|
|
3346
|
-
transport: "stdio",
|
|
3347
|
-
command: "npx",
|
|
3348
|
-
args: ["-y", "@modelcontextprotocol/server-brave-search"],
|
|
3349
|
-
permission: "confirm",
|
|
3350
|
-
description: "Web search (Brave)"
|
|
3351
|
-
},
|
|
3352
|
-
block: {
|
|
3353
|
-
name: "block",
|
|
3354
|
-
transport: "stdio",
|
|
3355
|
-
command: "npx",
|
|
3356
|
-
args: ["-y", "@modelcontextprotocol/server-block"],
|
|
3357
|
-
permission: "confirm",
|
|
3358
|
-
description: "Postgres database via SQL"
|
|
3359
|
-
},
|
|
3360
|
-
everart: {
|
|
3361
|
-
name: "everart",
|
|
3362
|
-
transport: "stdio",
|
|
3363
|
-
command: "npx",
|
|
3364
|
-
args: ["-y", "@modelcontextprotocol/server-everart"],
|
|
3365
|
-
permission: "confirm",
|
|
3366
|
-
description: "AI image generation"
|
|
3367
|
-
},
|
|
3368
|
-
slack: {
|
|
3369
|
-
name: "slack",
|
|
3370
|
-
transport: "stdio",
|
|
3371
|
-
command: "npx",
|
|
3372
|
-
args: ["-y", "@modelcontextprotocol/server-slack"],
|
|
3373
|
-
permission: "confirm",
|
|
3374
|
-
description: "Slack messaging & channels"
|
|
3375
|
-
},
|
|
3376
|
-
aws: {
|
|
3377
|
-
name: "aws",
|
|
3378
|
-
transport: "stdio",
|
|
3379
|
-
command: "npx",
|
|
3380
|
-
args: ["-y", "@modelcontextprotocol/server-aws"],
|
|
3381
|
-
permission: "confirm",
|
|
3382
|
-
description: "AWS \u2014 EC2, S3, Lambda, IAM"
|
|
3383
|
-
},
|
|
3384
|
-
"google-maps": {
|
|
3385
|
-
name: "google-maps",
|
|
3386
|
-
transport: "stdio",
|
|
3387
|
-
command: "npx",
|
|
3388
|
-
args: ["-y", "@modelcontextprotocol/server-google-maps"],
|
|
3389
|
-
permission: "confirm",
|
|
3390
|
-
description: "Google Maps \u2014 directions, geocoding, places"
|
|
3391
|
-
},
|
|
3392
|
-
sentinel: {
|
|
3393
|
-
name: "sentinel",
|
|
3394
|
-
transport: "streamable-http",
|
|
3395
|
-
url: "https://mcp.sentinel.ai",
|
|
3396
|
-
permission: "deny",
|
|
3397
|
-
description: "Security vulnerability scanning"
|
|
3398
|
-
}
|
|
3399
|
-
};
|
|
3595
|
+
var BUILT_IN_MCP = allServers();
|
|
3400
3596
|
var mcpCmd = async (args, deps) => {
|
|
3401
3597
|
const sub = args[0];
|
|
3402
3598
|
if (!sub || sub === "list") {
|
|
@@ -3459,7 +3655,7 @@ async function addMcpServer(args, deps) {
|
|
|
3459
3655
|
return 1;
|
|
3460
3656
|
}
|
|
3461
3657
|
const serverCfg = { ...factory };
|
|
3462
|
-
|
|
3658
|
+
serverCfg.enabled = enable;
|
|
3463
3659
|
let existing = {};
|
|
3464
3660
|
try {
|
|
3465
3661
|
existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
|
|
@@ -3531,7 +3727,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
3531
3727
|
return 0;
|
|
3532
3728
|
};
|
|
3533
3729
|
var projectsCmd = async (_args, deps) => {
|
|
3534
|
-
const projectsRoot =
|
|
3730
|
+
const projectsRoot = path14.join(deps.paths.globalRoot, "projects");
|
|
3535
3731
|
try {
|
|
3536
3732
|
const entries = await fs3.readdir(projectsRoot);
|
|
3537
3733
|
if (entries.length === 0) {
|
|
@@ -3541,7 +3737,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
3541
3737
|
for (const hash of entries) {
|
|
3542
3738
|
try {
|
|
3543
3739
|
const meta = JSON.parse(
|
|
3544
|
-
await fs3.readFile(
|
|
3740
|
+
await fs3.readFile(path14.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
3545
3741
|
);
|
|
3546
3742
|
deps.renderer.write(
|
|
3547
3743
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -3839,7 +4035,7 @@ function resolveBundledSkillsDir() {
|
|
|
3839
4035
|
try {
|
|
3840
4036
|
const req2 = createRequire(import.meta.url);
|
|
3841
4037
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
3842
|
-
return
|
|
4038
|
+
return path14.join(path14.dirname(corePkg), "skills");
|
|
3843
4039
|
} catch {
|
|
3844
4040
|
return void 0;
|
|
3845
4041
|
}
|
|
@@ -3876,7 +4072,10 @@ async function boot(argv) {
|
|
|
3876
4072
|
bundledDir: resolveBundledSkillsDir()
|
|
3877
4073
|
});
|
|
3878
4074
|
const toolRegistryForSubcmd = new ToolRegistry();
|
|
3879
|
-
|
|
4075
|
+
toolRegistryForSubcmd.registerAllOrThrow(
|
|
4076
|
+
[...builtinToolsPack.tools ?? []],
|
|
4077
|
+
builtinToolsPack.name
|
|
4078
|
+
);
|
|
3880
4079
|
const code = await subcommands[first](positional.slice(1), {
|
|
3881
4080
|
config,
|
|
3882
4081
|
renderer,
|
|
@@ -4005,6 +4204,10 @@ async function runRepl(opts) {
|
|
|
4005
4204
|
continue;
|
|
4006
4205
|
}
|
|
4007
4206
|
interrupts = 0;
|
|
4207
|
+
if (trimmed === "/image" || trimmed === "/paste-image" || raw === "\x1Bv") {
|
|
4208
|
+
await pasteClipboardImage(builder, opts);
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
4008
4211
|
if (trimmed.startsWith("/")) {
|
|
4009
4212
|
try {
|
|
4010
4213
|
const res = await opts.slashRegistry.dispatch(trimmed, opts.agent.ctx);
|
|
@@ -4029,7 +4232,23 @@ async function runRepl(opts) {
|
|
|
4029
4232
|
const startedAt = Date.now();
|
|
4030
4233
|
const before = opts.tokenCounter?.total();
|
|
4031
4234
|
const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
|
|
4032
|
-
const
|
|
4235
|
+
const routed = blocks.some((block) => block.type === "image") ? await routeImagesForModel(blocks, {
|
|
4236
|
+
supportsVision: opts.supportsVision ? await opts.supportsVision() : opts.agent.ctx.provider.capabilities.vision,
|
|
4237
|
+
adapters: opts.visionAdapters ?? [],
|
|
4238
|
+
ctx: opts.agent.ctx,
|
|
4239
|
+
signal: runCtrl.signal,
|
|
4240
|
+
providerId: opts.agent.ctx.provider.id,
|
|
4241
|
+
model: opts.agent.ctx.model
|
|
4242
|
+
}) : { blocks, route: "none", convertedImages: 0 };
|
|
4243
|
+
if (routed.route === "adapter") {
|
|
4244
|
+
opts.renderer.write(
|
|
4245
|
+
color.dim(
|
|
4246
|
+
` \u21B3 image analyzed via ${routed.adapterName ?? "vision adapter"} (${routed.convertedImages} image${routed.convertedImages === 1 ? "" : "s"})
|
|
4247
|
+
`
|
|
4248
|
+
)
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4251
|
+
const result = await opts.agent.run(routed.blocks, { signal: runCtrl.signal });
|
|
4033
4252
|
if (result.status === "aborted") {
|
|
4034
4253
|
opts.renderer.writeWarning("Aborted.");
|
|
4035
4254
|
} else if (result.status === "failed") {
|
|
@@ -4068,6 +4287,23 @@ ${color.dim(
|
|
|
4068
4287
|
});
|
|
4069
4288
|
}
|
|
4070
4289
|
}
|
|
4290
|
+
async function pasteClipboardImage(builder, opts) {
|
|
4291
|
+
try {
|
|
4292
|
+
const img = await readClipboardImage();
|
|
4293
|
+
if (!img) {
|
|
4294
|
+
opts.renderer.write(color.dim(" no image on clipboard\n"));
|
|
4295
|
+
return;
|
|
4296
|
+
}
|
|
4297
|
+
const placeholder = await builder.appendImage(img.base64, img.mediaType);
|
|
4298
|
+
const kb = (img.bytes / 1024).toFixed(0);
|
|
4299
|
+
opts.renderer.write(color.dim(` \u21B3 ${placeholder} (PNG ${kb}KB)
|
|
4300
|
+
`));
|
|
4301
|
+
} catch (err) {
|
|
4302
|
+
opts.renderer.writeError(
|
|
4303
|
+
`Clipboard image error: ${err instanceof Error ? err.message : String(err)}`
|
|
4304
|
+
);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4071
4307
|
async function readPossiblyMultiline(opts) {
|
|
4072
4308
|
const firstPrompt = theme2.primary("\u203A ");
|
|
4073
4309
|
const contPrompt = color.dim("\xB7 ");
|
|
@@ -4093,9 +4329,9 @@ var FILLED = "\u2588";
|
|
|
4093
4329
|
var EMPTY = "\u2591";
|
|
4094
4330
|
function renderContextChip(used, max) {
|
|
4095
4331
|
const ratio = Math.max(0, Math.min(1, used / max));
|
|
4096
|
-
const
|
|
4332
|
+
const pct2 = Math.round(ratio * 100);
|
|
4097
4333
|
const bar = renderProgress(ratio, 6);
|
|
4098
|
-
return `${bar} ${
|
|
4334
|
+
return `${bar} ${pct2}% (${fmtTok(used)}/${fmtTok(max)})`;
|
|
4099
4335
|
}
|
|
4100
4336
|
function renderProgress(ratio, width) {
|
|
4101
4337
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
@@ -4139,6 +4375,7 @@ async function execute(deps) {
|
|
|
4139
4375
|
queueStore,
|
|
4140
4376
|
context,
|
|
4141
4377
|
stats,
|
|
4378
|
+
detachTodosCheckpoint,
|
|
4142
4379
|
savedProviderCfg,
|
|
4143
4380
|
resolvedProvider,
|
|
4144
4381
|
getPickableProviders,
|
|
@@ -4149,6 +4386,15 @@ async function execute(deps) {
|
|
|
4149
4386
|
} = deps;
|
|
4150
4387
|
let code = 0;
|
|
4151
4388
|
try {
|
|
4389
|
+
const visionAdapters = () => createToolVisionAdapters(agent.tools);
|
|
4390
|
+
const supportsVision = async () => {
|
|
4391
|
+
try {
|
|
4392
|
+
const caps = await capabilitiesFor(modelsRegistry, context.provider.id, context.model);
|
|
4393
|
+
return caps.vision;
|
|
4394
|
+
} catch {
|
|
4395
|
+
return context.provider.capabilities.vision;
|
|
4396
|
+
}
|
|
4397
|
+
};
|
|
4152
4398
|
const promptFlag = typeof flags["prompt"] === "string" ? flags["prompt"] : void 0;
|
|
4153
4399
|
if (promptFlag) {
|
|
4154
4400
|
positional.unshift(promptFlag);
|
|
@@ -4233,6 +4479,8 @@ async function execute(deps) {
|
|
|
4233
4479
|
slashRegistry,
|
|
4234
4480
|
attachments,
|
|
4235
4481
|
tokenCounter,
|
|
4482
|
+
visionAdapters,
|
|
4483
|
+
supportsVision,
|
|
4236
4484
|
model: context.model,
|
|
4237
4485
|
banner: !flags["no-banner"],
|
|
4238
4486
|
queueStore,
|
|
@@ -4284,9 +4532,11 @@ async function execute(deps) {
|
|
|
4284
4532
|
reader,
|
|
4285
4533
|
slashRegistry,
|
|
4286
4534
|
tokenCounter,
|
|
4535
|
+
visionAdapters,
|
|
4536
|
+
supportsVision,
|
|
4287
4537
|
attachments,
|
|
4288
4538
|
effectiveMaxContext,
|
|
4289
|
-
projectName:
|
|
4539
|
+
projectName: path14.basename(projectRoot) || void 0
|
|
4290
4540
|
});
|
|
4291
4541
|
await webuiPromise;
|
|
4292
4542
|
} else {
|
|
@@ -4296,13 +4546,16 @@ async function execute(deps) {
|
|
|
4296
4546
|
reader,
|
|
4297
4547
|
slashRegistry,
|
|
4298
4548
|
tokenCounter,
|
|
4549
|
+
visionAdapters,
|
|
4550
|
+
supportsVision,
|
|
4299
4551
|
attachments,
|
|
4300
4552
|
effectiveMaxContext,
|
|
4301
|
-
projectName:
|
|
4553
|
+
projectName: path14.basename(projectRoot) || void 0
|
|
4302
4554
|
});
|
|
4303
4555
|
}
|
|
4304
4556
|
} finally {
|
|
4305
4557
|
stats.render(renderer);
|
|
4558
|
+
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
4306
4559
|
await mcpRegistry.stopAll();
|
|
4307
4560
|
await session.append({
|
|
4308
4561
|
type: "session_end",
|
|
@@ -4555,7 +4808,7 @@ var MultiAgentHost = class {
|
|
|
4555
4808
|
model: opts?.model,
|
|
4556
4809
|
tools: opts?.tools
|
|
4557
4810
|
};
|
|
4558
|
-
const transcriptPath = this.sessionFactory ?
|
|
4811
|
+
const transcriptPath = this.sessionFactory ? path14.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
4559
4812
|
if (this.director) {
|
|
4560
4813
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
4561
4814
|
const taskId2 = randomUUID();
|
|
@@ -4707,16 +4960,16 @@ var MultiAgentHost = class {
|
|
|
4707
4960
|
}
|
|
4708
4961
|
this.opts.directorMode = true;
|
|
4709
4962
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
4710
|
-
this.opts.manifestPath =
|
|
4963
|
+
this.opts.manifestPath = path14.join(this.opts.fleetRoot, "fleet.json");
|
|
4711
4964
|
}
|
|
4712
4965
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
4713
|
-
this.opts.sharedScratchpadPath =
|
|
4966
|
+
this.opts.sharedScratchpadPath = path14.join(this.opts.fleetRoot, "shared");
|
|
4714
4967
|
}
|
|
4715
4968
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
4716
|
-
this.opts.sessionsRoot =
|
|
4969
|
+
this.opts.sessionsRoot = path14.join(this.opts.fleetRoot, "subagents");
|
|
4717
4970
|
}
|
|
4718
4971
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
4719
|
-
this.opts.stateCheckpointPath =
|
|
4972
|
+
this.opts.stateCheckpointPath = path14.join(this.opts.fleetRoot, "director-state.json");
|
|
4720
4973
|
}
|
|
4721
4974
|
await this.ensureDirector();
|
|
4722
4975
|
return this.director ?? null;
|
|
@@ -4836,11 +5089,11 @@ var SessionStats = class {
|
|
|
4836
5089
|
if (e.name === "bash") this.bashCommands++;
|
|
4837
5090
|
else if (e.name === "fetch") this.fetches++;
|
|
4838
5091
|
if (!e.ok) return;
|
|
4839
|
-
const
|
|
4840
|
-
if (e.name === "read" &&
|
|
4841
|
-
else if (e.name === "edit" &&
|
|
4842
|
-
else if (e.name === "write" &&
|
|
4843
|
-
this.writtenPaths.add(
|
|
5092
|
+
const path15 = typeof input?.path === "string" ? input.path : void 0;
|
|
5093
|
+
if (e.name === "read" && path15) this.readPaths.add(path15);
|
|
5094
|
+
else if (e.name === "edit" && path15) this.editedPaths.add(path15);
|
|
5095
|
+
else if (e.name === "write" && path15) {
|
|
5096
|
+
this.writtenPaths.add(path15);
|
|
4844
5097
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
4845
5098
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
4846
5099
|
}
|
|
@@ -4877,9 +5130,9 @@ var SessionStats = class {
|
|
|
4877
5130
|
);
|
|
4878
5131
|
const cache = this.tokenCounter.cacheStats();
|
|
4879
5132
|
if (cache.readTokens > 0 || cache.writeTokens > 0) {
|
|
4880
|
-
const
|
|
5133
|
+
const pct2 = (cache.hitRatio * 100).toFixed(1);
|
|
4881
5134
|
lines.push(
|
|
4882
|
-
` Prompt cache: ${
|
|
5135
|
+
` Prompt cache: ${pct2}% hit ${color.dim(`(${fmtTok(cache.readTokens)} read / ${fmtTok(cache.writeTokens)} write)`)}`
|
|
4883
5136
|
);
|
|
4884
5137
|
}
|
|
4885
5138
|
if (cost.total > 0) {
|
|
@@ -5006,10 +5259,10 @@ var Spinner = class {
|
|
|
5006
5259
|
};
|
|
5007
5260
|
function renderContextChip2(ctx) {
|
|
5008
5261
|
const ratio = Math.max(0, Math.min(1, ctx.used / ctx.max));
|
|
5009
|
-
const
|
|
5262
|
+
const pct2 = Math.round(ratio * 100);
|
|
5010
5263
|
const chipColor = ratio >= 0.85 ? color.red : ratio >= 0.65 ? color.yellow : color.cyan;
|
|
5011
5264
|
const bar = renderProgress2(ratio, 8);
|
|
5012
|
-
return color.dim("ctx ") + chipColor(bar) + chipColor(` ${
|
|
5265
|
+
return color.dim("ctx ") + chipColor(bar) + chipColor(` ${pct2}%`) + color.dim(` (${fmtTok(ctx.used)}/${fmtTok(ctx.max)})`);
|
|
5013
5266
|
}
|
|
5014
5267
|
function renderProgress2(ratio, width) {
|
|
5015
5268
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
@@ -5023,7 +5276,7 @@ function resolveBundledSkillsDir2() {
|
|
|
5023
5276
|
try {
|
|
5024
5277
|
const req2 = createRequire(import.meta.url);
|
|
5025
5278
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
5026
|
-
return
|
|
5279
|
+
return path14.join(path14.dirname(corePkg), "skills");
|
|
5027
5280
|
} catch {
|
|
5028
5281
|
return void 0;
|
|
5029
5282
|
}
|
|
@@ -5046,52 +5299,35 @@ async function main(argv) {
|
|
|
5046
5299
|
logger
|
|
5047
5300
|
} = ctx;
|
|
5048
5301
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
5049
|
-
const
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5302
|
+
const container = createDefaultContainer({
|
|
5303
|
+
config,
|
|
5304
|
+
wpaths,
|
|
5305
|
+
logger,
|
|
5306
|
+
modelsRegistry,
|
|
5307
|
+
permission: { yolo: config.yolo, promptDelegate: makePromptDelegate(reader) },
|
|
5308
|
+
compactor: { preserveK: config.context.preserveK, eliseThreshold: config.context.eliseThreshold },
|
|
5309
|
+
bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
|
|
5310
|
+
});
|
|
5311
|
+
const configStore = container.resolve(TOKENS.ConfigStore);
|
|
5312
|
+
container.bind(TOKENS.PathResolver, () => pathResolver);
|
|
5313
|
+
container.bind(TOKENS.Renderer, () => renderer);
|
|
5314
|
+
container.bind(TOKENS.InputReader, () => reader);
|
|
5315
|
+
const modeStore = container.resolve(TOKENS.ModeStore);
|
|
5316
|
+
const activeMode = await modeStore.getActiveMode();
|
|
5317
|
+
let resolvedProvider;
|
|
5318
|
+
let providerRegistry;
|
|
5319
|
+
let provider;
|
|
5320
|
+
try {
|
|
5321
|
+
const result = await setupProvider({ config, modelsRegistry, logger });
|
|
5322
|
+
resolvedProvider = result.resolvedProvider;
|
|
5323
|
+
providerRegistry = result.providerRegistry;
|
|
5324
|
+
provider = result.provider;
|
|
5325
|
+
} catch (err) {
|
|
5326
|
+
process.stderr.write(`${err instanceof Error ? err.message : err}
|
|
5327
|
+
`);
|
|
5065
5328
|
await reader.close();
|
|
5066
5329
|
return 2;
|
|
5067
5330
|
}
|
|
5068
|
-
const container = new Container();
|
|
5069
|
-
const configStore = new DefaultConfigStore(config);
|
|
5070
|
-
container.bind(TOKENS.ConfigStore, () => configStore);
|
|
5071
|
-
container.bind(TOKENS.Logger, () => logger);
|
|
5072
|
-
container.bind(TOKENS.PathResolver, () => pathResolver);
|
|
5073
|
-
container.bind(TOKENS.SecretScrubber, () => new DefaultSecretScrubber());
|
|
5074
|
-
container.bind(TOKENS.RetryPolicy, () => new DefaultRetryPolicy());
|
|
5075
|
-
container.bind(TOKENS.ErrorHandler, () => new DefaultErrorHandler());
|
|
5076
|
-
container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
|
|
5077
|
-
container.bind(
|
|
5078
|
-
TOKENS.TokenCounter,
|
|
5079
|
-
() => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider })
|
|
5080
|
-
);
|
|
5081
|
-
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
5082
|
-
container.bind(TOKENS.ModeStore, () => modeStore);
|
|
5083
|
-
container.bind(
|
|
5084
|
-
TOKENS.SessionStore,
|
|
5085
|
-
() => new DefaultSessionStore({ dir: wpaths.projectSessions })
|
|
5086
|
-
);
|
|
5087
|
-
const memoryStore = new DefaultMemoryStore({ paths: wpaths });
|
|
5088
|
-
container.bind(TOKENS.MemoryStore, () => memoryStore);
|
|
5089
|
-
const skillLoader = new DefaultSkillLoader({
|
|
5090
|
-
paths: wpaths,
|
|
5091
|
-
bundledDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
|
|
5092
|
-
});
|
|
5093
|
-
container.bind(TOKENS.SkillLoader, () => skillLoader);
|
|
5094
|
-
const activeMode = await modeStore.getActiveMode();
|
|
5095
5331
|
const modeId = activeMode?.id ?? "default";
|
|
5096
5332
|
const modePrompt = activeMode?.prompt ?? "";
|
|
5097
5333
|
const resolvedModel = await modelsRegistry.getModel(config.provider, config.model);
|
|
@@ -5101,6 +5337,8 @@ async function main(argv) {
|
|
|
5101
5337
|
supportsVision: resolvedModel.capabilities.vision,
|
|
5102
5338
|
supportsReasoning: resolvedModel.capabilities.reasoning
|
|
5103
5339
|
} : void 0;
|
|
5340
|
+
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
5341
|
+
const skillLoader = container.resolve(TOKENS.SkillLoader);
|
|
5104
5342
|
const sessionRef = {};
|
|
5105
5343
|
container.bind(
|
|
5106
5344
|
TOKENS.SystemPromptBuilder,
|
|
@@ -5111,48 +5349,11 @@ async function main(argv) {
|
|
|
5111
5349
|
modeId,
|
|
5112
5350
|
modePrompt,
|
|
5113
5351
|
modelCapabilities,
|
|
5114
|
-
|
|
5115
|
-
// is created, then resolves to the per-session plan JSON path.
|
|
5116
|
-
planPath: () => sessionRef.current ? path13.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
5117
|
-
})
|
|
5118
|
-
);
|
|
5119
|
-
container.bind(TOKENS.Renderer, () => renderer);
|
|
5120
|
-
container.bind(TOKENS.InputReader, () => reader);
|
|
5121
|
-
container.bind(
|
|
5122
|
-
TOKENS.PermissionPolicy,
|
|
5123
|
-
() => new DefaultPermissionPolicy({
|
|
5124
|
-
trustFile: wpaths.projectTrust,
|
|
5125
|
-
yolo: config.yolo,
|
|
5126
|
-
promptDelegate: makePromptDelegate(reader)
|
|
5127
|
-
})
|
|
5128
|
-
);
|
|
5129
|
-
container.bind(
|
|
5130
|
-
TOKENS.Compactor,
|
|
5131
|
-
() => new HybridCompactor({
|
|
5132
|
-
preserveK: config.context.preserveK,
|
|
5133
|
-
eliseThreshold: config.context.eliseThreshold
|
|
5352
|
+
planPath: () => sessionRef.current ? path14.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
5134
5353
|
})
|
|
5135
5354
|
);
|
|
5136
|
-
const providerRegistry = new ProviderRegistry();
|
|
5137
|
-
if (config.features.modelsRegistry) {
|
|
5138
|
-
try {
|
|
5139
|
-
const factories = await buildProviderFactoriesFromRegistry({
|
|
5140
|
-
registry: modelsRegistry,
|
|
5141
|
-
log: logger
|
|
5142
|
-
});
|
|
5143
|
-
for (const f of factories) providerRegistry.register(f);
|
|
5144
|
-
} catch (err) {
|
|
5145
|
-
process.stderr.write(
|
|
5146
|
-
`Failed to load models.dev registry: ${err instanceof Error ? err.message : err}
|
|
5147
|
-
Try \`wstack models refresh\` once you have network access, or run with --no-features.
|
|
5148
|
-
`
|
|
5149
|
-
);
|
|
5150
|
-
await reader.close();
|
|
5151
|
-
return 2;
|
|
5152
|
-
}
|
|
5153
|
-
}
|
|
5154
5355
|
const toolRegistry = new ToolRegistry();
|
|
5155
|
-
|
|
5356
|
+
toolRegistry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
5156
5357
|
toolRegistry.registerDefault(
|
|
5157
5358
|
createContextManagerTool({ compactor: container.resolve(TOKENS.Compactor) })
|
|
5158
5359
|
);
|
|
@@ -5193,7 +5394,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5193
5394
|
const dumpMetrics = () => {
|
|
5194
5395
|
if (!metricsSink) return;
|
|
5195
5396
|
try {
|
|
5196
|
-
const out =
|
|
5397
|
+
const out = path14.join(wpaths.projectSessions, "metrics.json");
|
|
5197
5398
|
const snap = metricsSink.snapshot();
|
|
5198
5399
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
5199
5400
|
} catch {
|
|
@@ -5278,27 +5479,6 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5278
5479
|
process.stderr.write(color.red(` \u2717 ${p.description}
|
|
5279
5480
|
`));
|
|
5280
5481
|
});
|
|
5281
|
-
const providerConfig = config.providers?.[config.provider] ?? {
|
|
5282
|
-
type: config.provider,
|
|
5283
|
-
apiKey: config.apiKey,
|
|
5284
|
-
baseUrl: config.baseUrl
|
|
5285
|
-
};
|
|
5286
|
-
let provider;
|
|
5287
|
-
try {
|
|
5288
|
-
const cfgWithType = { ...providerConfig, type: config.provider };
|
|
5289
|
-
if (config.features.modelsRegistry && providerRegistry.has(config.provider)) {
|
|
5290
|
-
provider = providerRegistry.create(cfgWithType);
|
|
5291
|
-
} else {
|
|
5292
|
-
provider = makeProviderFromConfig(config.provider, cfgWithType);
|
|
5293
|
-
}
|
|
5294
|
-
} catch (err) {
|
|
5295
|
-
process.stderr.write(
|
|
5296
|
-
`Failed to create provider: ${err instanceof Error ? err.message : err}
|
|
5297
|
-
`
|
|
5298
|
-
);
|
|
5299
|
-
await reader.close();
|
|
5300
|
-
return 2;
|
|
5301
|
-
}
|
|
5302
5482
|
const promptBuilder = container.resolve(TOKENS.SystemPromptBuilder);
|
|
5303
5483
|
const systemPrompt = await promptBuilder.build({
|
|
5304
5484
|
cwd,
|
|
@@ -5308,59 +5488,29 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5308
5488
|
model: config.model
|
|
5309
5489
|
});
|
|
5310
5490
|
const sessionStore = container.resolve(TOKENS.SessionStore);
|
|
5311
|
-
|
|
5312
|
-
const
|
|
5313
|
-
|
|
5314
|
-
|
|
5491
|
+
const tokenCounter = container.resolve(TOKENS.TokenCounter);
|
|
5492
|
+
const sessResult = await setupSession({
|
|
5493
|
+
config: { model: config.model, provider: config.provider },
|
|
5494
|
+
wpaths,
|
|
5495
|
+
projectRoot,
|
|
5496
|
+
cwd,
|
|
5497
|
+
sessionStore,
|
|
5498
|
+
systemPrompt,
|
|
5499
|
+
provider,
|
|
5500
|
+
tokenCounter,
|
|
5501
|
+
renderer,
|
|
5502
|
+
flags,
|
|
5503
|
+
onRecovery: (abandoned, autoRecover) => promptRecovery(reader, renderer, abandoned, autoRecover)
|
|
5315
5504
|
});
|
|
5316
|
-
|
|
5317
|
-
const abandoned = await recoveryLock.checkAbandoned();
|
|
5318
|
-
if (abandoned && abandoned.messageCount > 0) {
|
|
5319
|
-
const choice = await promptRecovery(reader, renderer, abandoned, !!flags["recover"]);
|
|
5320
|
-
if (choice === "resume") {
|
|
5321
|
-
resumeId = abandoned.sessionId;
|
|
5322
|
-
} else if (choice === "delete") {
|
|
5323
|
-
await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
|
|
5324
|
-
await recoveryLock.clear();
|
|
5325
|
-
} else {
|
|
5326
|
-
await recoveryLock.clear();
|
|
5327
|
-
}
|
|
5328
|
-
} else if (abandoned) {
|
|
5329
|
-
await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
|
|
5330
|
-
await recoveryLock.clear();
|
|
5331
|
-
}
|
|
5332
|
-
}
|
|
5333
|
-
let session;
|
|
5334
|
-
let restoredMessages = [];
|
|
5335
|
-
if (resumeId) {
|
|
5336
|
-
try {
|
|
5337
|
-
const resumed = await sessionStore.resume(resumeId);
|
|
5338
|
-
session = resumed.writer;
|
|
5339
|
-
restoredMessages = resumed.data.messages;
|
|
5340
|
-
renderer.writeInfo(
|
|
5341
|
-
`Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`
|
|
5342
|
-
);
|
|
5343
|
-
} catch (err) {
|
|
5344
|
-
renderer.writeError(`Resume failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5345
|
-
return 2;
|
|
5346
|
-
}
|
|
5347
|
-
} else {
|
|
5348
|
-
session = await sessionStore.create({
|
|
5349
|
-
id: "",
|
|
5350
|
-
title: "",
|
|
5351
|
-
model: config.model,
|
|
5352
|
-
provider: config.provider
|
|
5353
|
-
});
|
|
5354
|
-
}
|
|
5505
|
+
const session = sessResult.session;
|
|
5355
5506
|
sessionRef.current = session;
|
|
5356
|
-
|
|
5357
|
-
const
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
const queueStore =
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
const tokenCounter = container.resolve(TOKENS.TokenCounter);
|
|
5507
|
+
sessResult.restoredMessages;
|
|
5508
|
+
const context = sessResult.context;
|
|
5509
|
+
const attachments = sessResult.attachments;
|
|
5510
|
+
const recoveryLock = sessResult.recoveryLock;
|
|
5511
|
+
const queueStore = sessResult.queueStore;
|
|
5512
|
+
const planPath = sessResult.planPath;
|
|
5513
|
+
const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
|
|
5364
5514
|
const stats = new SessionStats(events, tokenCounter);
|
|
5365
5515
|
const errorRing = [];
|
|
5366
5516
|
events.on("error", (e) => {
|
|
@@ -5370,150 +5520,15 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5370
5520
|
errorRing.push({ ts: (/* @__PURE__ */ new Date()).toISOString(), phase: e.phase, code, message });
|
|
5371
5521
|
if (errorRing.length > 5) errorRing.shift();
|
|
5372
5522
|
});
|
|
5373
|
-
const
|
|
5374
|
-
const context = new Context({
|
|
5375
|
-
systemPrompt,
|
|
5376
|
-
provider,
|
|
5377
|
-
session,
|
|
5378
|
-
signal: ctxSignal,
|
|
5379
|
-
tokenCounter,
|
|
5380
|
-
cwd,
|
|
5381
|
-
projectRoot,
|
|
5382
|
-
model: config.model
|
|
5383
|
-
});
|
|
5384
|
-
if (restoredMessages.length > 0) {
|
|
5385
|
-
context.state.replaceMessages(restoredMessages);
|
|
5386
|
-
}
|
|
5387
|
-
const todosCheckpointPath = path13.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
5388
|
-
if (resumeId) {
|
|
5389
|
-
try {
|
|
5390
|
-
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
5391
|
-
if (restoredTodos && restoredTodos.length > 0) {
|
|
5392
|
-
context.state.replaceTodos(restoredTodos);
|
|
5393
|
-
renderer.writeInfo(
|
|
5394
|
-
`Restored ${restoredTodos.length} todo${restoredTodos.length === 1 ? "" : "s"} from previous run.`
|
|
5395
|
-
);
|
|
5396
|
-
}
|
|
5397
|
-
} catch {
|
|
5398
|
-
}
|
|
5399
|
-
}
|
|
5400
|
-
attachTodosCheckpoint(
|
|
5401
|
-
context.state,
|
|
5402
|
-
todosCheckpointPath,
|
|
5403
|
-
session.id
|
|
5404
|
-
);
|
|
5405
|
-
const planPath = path13.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
5406
|
-
context.state.setMeta("plan.path", planPath);
|
|
5407
|
-
if (resumeId) {
|
|
5408
|
-
try {
|
|
5409
|
-
const fleetRoot2 = path13.join(wpaths.projectSessions, session.id);
|
|
5410
|
-
const dirState = await loadDirectorState(path13.join(fleetRoot2, "director-state.json"));
|
|
5411
|
-
if (dirState) {
|
|
5412
|
-
const tCounts = {};
|
|
5413
|
-
for (const t of dirState.tasks) {
|
|
5414
|
-
tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
5415
|
-
}
|
|
5416
|
-
const summary = Object.entries(tCounts).map(([k, v]) => `${v} ${k}`).join(", ");
|
|
5417
|
-
renderer.writeInfo(
|
|
5418
|
-
`Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`
|
|
5419
|
-
);
|
|
5420
|
-
}
|
|
5421
|
-
} catch {
|
|
5422
|
-
}
|
|
5423
|
-
try {
|
|
5424
|
-
const plan = await loadPlan(planPath);
|
|
5425
|
-
if (plan && plan.items.length > 0) {
|
|
5426
|
-
const open = plan.items.filter((p) => p.status !== "done").length;
|
|
5427
|
-
const done = plan.items.length - open;
|
|
5428
|
-
renderer.writeInfo(
|
|
5429
|
-
`Plan: ${plan.items.length} item${plan.items.length === 1 ? "" : "s"} (${open} open, ${done} done). Use /plan to review.`
|
|
5430
|
-
);
|
|
5431
|
-
}
|
|
5432
|
-
} catch {
|
|
5433
|
-
}
|
|
5434
|
-
}
|
|
5435
|
-
const pipelines = createDefaultPipelines();
|
|
5436
|
-
const installBoundary = (p) => {
|
|
5437
|
-
p.setErrorHandler((ev) => {
|
|
5438
|
-
const fromPlugin = !!ev.owner && ev.owner !== "core";
|
|
5439
|
-
logger.error(
|
|
5440
|
-
`Pipeline middleware "${ev.middleware}" crashed (owner=${ev.owner ?? "unknown"}); ${fromPlugin ? "swallowed" : "rethrown"}`,
|
|
5441
|
-
ev.err
|
|
5442
|
-
);
|
|
5443
|
-
events.emit("error", {
|
|
5444
|
-
err: ev.err instanceof Error ? ev.err : new Error(String(ev.err)),
|
|
5445
|
-
phase: `pipeline:${ev.middleware}`
|
|
5446
|
-
});
|
|
5447
|
-
return fromPlugin ? "swallow" : "rethrow";
|
|
5448
|
-
});
|
|
5449
|
-
};
|
|
5450
|
-
installBoundary(pipelines.request);
|
|
5451
|
-
installBoundary(pipelines.response);
|
|
5452
|
-
installBoundary(pipelines.toolCall);
|
|
5453
|
-
installBoundary(pipelines.userInput);
|
|
5454
|
-
installBoundary(pipelines.assistantOutput);
|
|
5455
|
-
installBoundary(pipelines.contextWindow);
|
|
5523
|
+
const pipelines = setupPipelines({ events, logger });
|
|
5456
5524
|
const compactor = container.resolve(TOKENS.Compactor);
|
|
5457
|
-
const
|
|
5458
|
-
() => void 0
|
|
5459
|
-
);
|
|
5460
|
-
const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
|
|
5525
|
+
const effectiveMaxContext = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
|
|
5461
5526
|
const updateSpinnerContext = () => {
|
|
5462
5527
|
if (effectiveMaxContext > 0 && lastInputTokens > 0) {
|
|
5463
5528
|
spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
|
|
5464
|
-
} else
|
|
5465
|
-
spinner.setContext(void 0);
|
|
5466
|
-
}
|
|
5529
|
+
} else spinner.setContext(void 0);
|
|
5467
5530
|
};
|
|
5468
|
-
|
|
5469
|
-
const autoCompactor = new AutoCompactionMiddleware(
|
|
5470
|
-
compactor,
|
|
5471
|
-
effectiveMaxContext,
|
|
5472
|
-
(ctx2) => {
|
|
5473
|
-
const msgs = ctx2.messages;
|
|
5474
|
-
let total = 0;
|
|
5475
|
-
for (const m of msgs) {
|
|
5476
|
-
if (typeof m.content === "string") total += Math.ceil(m.content.length / 4);
|
|
5477
|
-
else if (Array.isArray(m.content)) {
|
|
5478
|
-
for (const b of m.content) {
|
|
5479
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
5480
|
-
else if (b.type === "tool_use" || b.type === "tool_result") {
|
|
5481
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
5482
|
-
}
|
|
5483
|
-
}
|
|
5484
|
-
}
|
|
5485
|
-
}
|
|
5486
|
-
return total;
|
|
5487
|
-
},
|
|
5488
|
-
{
|
|
5489
|
-
warn: config.context.warnThreshold,
|
|
5490
|
-
soft: config.context.softThreshold,
|
|
5491
|
-
hard: config.context.hardThreshold
|
|
5492
|
-
},
|
|
5493
|
-
{
|
|
5494
|
-
aggressiveOn: "soft",
|
|
5495
|
-
failureMode: "throw_on_hard",
|
|
5496
|
-
events
|
|
5497
|
-
}
|
|
5498
|
-
);
|
|
5499
|
-
pipelines.contextWindow.use({
|
|
5500
|
-
name: "AutoCompaction",
|
|
5501
|
-
handler: autoCompactor.handler()
|
|
5502
|
-
});
|
|
5503
|
-
}
|
|
5504
|
-
const agent = new Agent({
|
|
5505
|
-
container,
|
|
5506
|
-
tools: toolRegistry,
|
|
5507
|
-
providers: providerRegistry,
|
|
5508
|
-
events,
|
|
5509
|
-
pipelines,
|
|
5510
|
-
context,
|
|
5511
|
-
maxIterations: config.tools.maxIterations,
|
|
5512
|
-
iterationTimeoutMs: config.tools.iterationTimeoutMs,
|
|
5513
|
-
executionStrategy: config.tools.defaultExecutionStrategy,
|
|
5514
|
-
perIterationOutputCapBytes: config.tools.perIterationOutputCapBytes,
|
|
5515
|
-
confirmAwaiter: makeConfirmAwaiter(reader)
|
|
5516
|
-
});
|
|
5531
|
+
const agent = createAgent({ container, tools: toolRegistry, providers: providerRegistry, events, pipelines, context, config, confirmAwaiter: makeConfirmAwaiter(reader) });
|
|
5517
5532
|
const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger });
|
|
5518
5533
|
if (config.features.mcp) {
|
|
5519
5534
|
for (const cfg of Object.values(config.mcpServers ?? {})) {
|
|
@@ -5554,7 +5569,14 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5554
5569
|
slashCommandRegistry: slashRegistry,
|
|
5555
5570
|
mcpRegistry,
|
|
5556
5571
|
config,
|
|
5557
|
-
log: logger
|
|
5572
|
+
log: logger,
|
|
5573
|
+
extensions: agent.extensions,
|
|
5574
|
+
sessionWriter: {
|
|
5575
|
+
transcriptPath: context.session.transcriptPath,
|
|
5576
|
+
append: (e) => context.session.append(e)
|
|
5577
|
+
},
|
|
5578
|
+
metricsSink,
|
|
5579
|
+
configStore
|
|
5558
5580
|
})
|
|
5559
5581
|
});
|
|
5560
5582
|
}
|
|
@@ -5579,12 +5601,12 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5579
5601
|
};
|
|
5580
5602
|
const directorMode = flags["director"] === true;
|
|
5581
5603
|
let director = null;
|
|
5582
|
-
const fleetRoot = directorMode ?
|
|
5583
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
5584
|
-
const sharedScratchpadPath = directorMode ?
|
|
5585
|
-
const subagentSessionsRoot = directorMode ?
|
|
5586
|
-
const stateCheckpointPath = directorMode ?
|
|
5587
|
-
const fleetRootForPromotion =
|
|
5604
|
+
const fleetRoot = directorMode ? path14.join(wpaths.projectSessions, session.id) : void 0;
|
|
5605
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path14.join(fleetRoot, "fleet.json") : void 0;
|
|
5606
|
+
const sharedScratchpadPath = directorMode ? path14.join(fleetRoot, "shared") : void 0;
|
|
5607
|
+
const subagentSessionsRoot = directorMode ? path14.join(fleetRoot, "subagents") : void 0;
|
|
5608
|
+
const stateCheckpointPath = directorMode ? path14.join(fleetRoot, "director-state.json") : void 0;
|
|
5609
|
+
const fleetRootForPromotion = path14.join(wpaths.projectSessions, session.id);
|
|
5588
5610
|
const multiAgentHost = new MultiAgentHost(
|
|
5589
5611
|
{
|
|
5590
5612
|
container,
|
|
@@ -5736,7 +5758,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5736
5758
|
return `Unknown fleet action: ${action}`;
|
|
5737
5759
|
},
|
|
5738
5760
|
onFleetLog: async (subagentId, mode) => {
|
|
5739
|
-
const subagentsRoot =
|
|
5761
|
+
const subagentsRoot = path14.join(fleetRootForPromotion, "subagents");
|
|
5740
5762
|
let runDirs;
|
|
5741
5763
|
try {
|
|
5742
5764
|
runDirs = await fs3.readdir(subagentsRoot);
|
|
@@ -5745,7 +5767,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5745
5767
|
}
|
|
5746
5768
|
const found = [];
|
|
5747
5769
|
for (const runId of runDirs) {
|
|
5748
|
-
const runDir =
|
|
5770
|
+
const runDir = path14.join(subagentsRoot, runId);
|
|
5749
5771
|
let files;
|
|
5750
5772
|
try {
|
|
5751
5773
|
files = await fs3.readdir(runDir);
|
|
@@ -5754,7 +5776,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5754
5776
|
}
|
|
5755
5777
|
for (const f of files) {
|
|
5756
5778
|
if (!f.endsWith(".jsonl")) continue;
|
|
5757
|
-
const full =
|
|
5779
|
+
const full = path14.join(runDir, f);
|
|
5758
5780
|
try {
|
|
5759
5781
|
const stat2 = await fs3.stat(full);
|
|
5760
5782
|
found.push({
|
|
@@ -5851,7 +5873,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5851
5873
|
}
|
|
5852
5874
|
const dir = await multiAgentHost.ensureDirector();
|
|
5853
5875
|
if (!dir) return "Director is not available.";
|
|
5854
|
-
const dirStatePath =
|
|
5876
|
+
const dirStatePath = path14.join(fleetRootForPromotion, "director-state.json");
|
|
5855
5877
|
const prior = await loadDirectorState(dirStatePath);
|
|
5856
5878
|
if (!prior) {
|
|
5857
5879
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -5922,9 +5944,9 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5922
5944
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
5923
5945
|
toolRegistry.register(tool);
|
|
5924
5946
|
}
|
|
5925
|
-
const mp =
|
|
5926
|
-
const sp =
|
|
5927
|
-
const ss =
|
|
5947
|
+
const mp = path14.join(fleetRootForPromotion, "fleet.json");
|
|
5948
|
+
const sp = path14.join(fleetRootForPromotion, "shared");
|
|
5949
|
+
const ss = path14.join(fleetRootForPromotion, "subagents");
|
|
5928
5950
|
const lines = [
|
|
5929
5951
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
5930
5952
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -5967,6 +5989,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5967
5989
|
onStats: () => stats.format()
|
|
5968
5990
|
});
|
|
5969
5991
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
5992
|
+
const savedProviderCfg = config.providers?.[config.provider];
|
|
5970
5993
|
return execute({
|
|
5971
5994
|
agent,
|
|
5972
5995
|
events,
|
|
@@ -5988,6 +6011,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5988
6011
|
queueStore,
|
|
5989
6012
|
context,
|
|
5990
6013
|
stats,
|
|
6014
|
+
detachTodosCheckpoint,
|
|
5991
6015
|
savedProviderCfg,
|
|
5992
6016
|
resolvedProvider: resolvedProvider ?? void 0,
|
|
5993
6017
|
getPickableProviders: () => buildPickableProviders(modelsRegistry, config),
|