@wrongstack/cli 0.3.1 → 0.3.3
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 +454 -440
- 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
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
|
}
|
|
@@ -4008,6 +4204,10 @@ async function runRepl(opts) {
|
|
|
4008
4204
|
continue;
|
|
4009
4205
|
}
|
|
4010
4206
|
interrupts = 0;
|
|
4207
|
+
if (trimmed === "/image" || trimmed === "/paste-image" || raw === "\x1Bv") {
|
|
4208
|
+
await pasteClipboardImage(builder, opts);
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
4011
4211
|
if (trimmed.startsWith("/")) {
|
|
4012
4212
|
try {
|
|
4013
4213
|
const res = await opts.slashRegistry.dispatch(trimmed, opts.agent.ctx);
|
|
@@ -4032,7 +4232,23 @@ async function runRepl(opts) {
|
|
|
4032
4232
|
const startedAt = Date.now();
|
|
4033
4233
|
const before = opts.tokenCounter?.total();
|
|
4034
4234
|
const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
|
|
4035
|
-
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 });
|
|
4036
4252
|
if (result.status === "aborted") {
|
|
4037
4253
|
opts.renderer.writeWarning("Aborted.");
|
|
4038
4254
|
} else if (result.status === "failed") {
|
|
@@ -4071,6 +4287,23 @@ ${color.dim(
|
|
|
4071
4287
|
});
|
|
4072
4288
|
}
|
|
4073
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
|
+
}
|
|
4074
4307
|
async function readPossiblyMultiline(opts) {
|
|
4075
4308
|
const firstPrompt = theme2.primary("\u203A ");
|
|
4076
4309
|
const contPrompt = color.dim("\xB7 ");
|
|
@@ -4096,9 +4329,9 @@ var FILLED = "\u2588";
|
|
|
4096
4329
|
var EMPTY = "\u2591";
|
|
4097
4330
|
function renderContextChip(used, max) {
|
|
4098
4331
|
const ratio = Math.max(0, Math.min(1, used / max));
|
|
4099
|
-
const
|
|
4332
|
+
const pct2 = Math.round(ratio * 100);
|
|
4100
4333
|
const bar = renderProgress(ratio, 6);
|
|
4101
|
-
return `${bar} ${
|
|
4334
|
+
return `${bar} ${pct2}% (${fmtTok(used)}/${fmtTok(max)})`;
|
|
4102
4335
|
}
|
|
4103
4336
|
function renderProgress(ratio, width) {
|
|
4104
4337
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
@@ -4142,6 +4375,7 @@ async function execute(deps) {
|
|
|
4142
4375
|
queueStore,
|
|
4143
4376
|
context,
|
|
4144
4377
|
stats,
|
|
4378
|
+
detachTodosCheckpoint,
|
|
4145
4379
|
savedProviderCfg,
|
|
4146
4380
|
resolvedProvider,
|
|
4147
4381
|
getPickableProviders,
|
|
@@ -4152,6 +4386,15 @@ async function execute(deps) {
|
|
|
4152
4386
|
} = deps;
|
|
4153
4387
|
let code = 0;
|
|
4154
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
|
+
};
|
|
4155
4398
|
const promptFlag = typeof flags["prompt"] === "string" ? flags["prompt"] : void 0;
|
|
4156
4399
|
if (promptFlag) {
|
|
4157
4400
|
positional.unshift(promptFlag);
|
|
@@ -4236,6 +4479,8 @@ async function execute(deps) {
|
|
|
4236
4479
|
slashRegistry,
|
|
4237
4480
|
attachments,
|
|
4238
4481
|
tokenCounter,
|
|
4482
|
+
visionAdapters,
|
|
4483
|
+
supportsVision,
|
|
4239
4484
|
model: context.model,
|
|
4240
4485
|
banner: !flags["no-banner"],
|
|
4241
4486
|
queueStore,
|
|
@@ -4287,9 +4532,11 @@ async function execute(deps) {
|
|
|
4287
4532
|
reader,
|
|
4288
4533
|
slashRegistry,
|
|
4289
4534
|
tokenCounter,
|
|
4535
|
+
visionAdapters,
|
|
4536
|
+
supportsVision,
|
|
4290
4537
|
attachments,
|
|
4291
4538
|
effectiveMaxContext,
|
|
4292
|
-
projectName:
|
|
4539
|
+
projectName: path14.basename(projectRoot) || void 0
|
|
4293
4540
|
});
|
|
4294
4541
|
await webuiPromise;
|
|
4295
4542
|
} else {
|
|
@@ -4299,13 +4546,16 @@ async function execute(deps) {
|
|
|
4299
4546
|
reader,
|
|
4300
4547
|
slashRegistry,
|
|
4301
4548
|
tokenCounter,
|
|
4549
|
+
visionAdapters,
|
|
4550
|
+
supportsVision,
|
|
4302
4551
|
attachments,
|
|
4303
4552
|
effectiveMaxContext,
|
|
4304
|
-
projectName:
|
|
4553
|
+
projectName: path14.basename(projectRoot) || void 0
|
|
4305
4554
|
});
|
|
4306
4555
|
}
|
|
4307
4556
|
} finally {
|
|
4308
4557
|
stats.render(renderer);
|
|
4558
|
+
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
4309
4559
|
await mcpRegistry.stopAll();
|
|
4310
4560
|
await session.append({
|
|
4311
4561
|
type: "session_end",
|
|
@@ -4558,7 +4808,7 @@ var MultiAgentHost = class {
|
|
|
4558
4808
|
model: opts?.model,
|
|
4559
4809
|
tools: opts?.tools
|
|
4560
4810
|
};
|
|
4561
|
-
const transcriptPath = this.sessionFactory ?
|
|
4811
|
+
const transcriptPath = this.sessionFactory ? path14.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
4562
4812
|
if (this.director) {
|
|
4563
4813
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
4564
4814
|
const taskId2 = randomUUID();
|
|
@@ -4710,16 +4960,16 @@ var MultiAgentHost = class {
|
|
|
4710
4960
|
}
|
|
4711
4961
|
this.opts.directorMode = true;
|
|
4712
4962
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
4713
|
-
this.opts.manifestPath =
|
|
4963
|
+
this.opts.manifestPath = path14.join(this.opts.fleetRoot, "fleet.json");
|
|
4714
4964
|
}
|
|
4715
4965
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
4716
|
-
this.opts.sharedScratchpadPath =
|
|
4966
|
+
this.opts.sharedScratchpadPath = path14.join(this.opts.fleetRoot, "shared");
|
|
4717
4967
|
}
|
|
4718
4968
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
4719
|
-
this.opts.sessionsRoot =
|
|
4969
|
+
this.opts.sessionsRoot = path14.join(this.opts.fleetRoot, "subagents");
|
|
4720
4970
|
}
|
|
4721
4971
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
4722
|
-
this.opts.stateCheckpointPath =
|
|
4972
|
+
this.opts.stateCheckpointPath = path14.join(this.opts.fleetRoot, "director-state.json");
|
|
4723
4973
|
}
|
|
4724
4974
|
await this.ensureDirector();
|
|
4725
4975
|
return this.director ?? null;
|
|
@@ -4839,11 +5089,11 @@ var SessionStats = class {
|
|
|
4839
5089
|
if (e.name === "bash") this.bashCommands++;
|
|
4840
5090
|
else if (e.name === "fetch") this.fetches++;
|
|
4841
5091
|
if (!e.ok) return;
|
|
4842
|
-
const
|
|
4843
|
-
if (e.name === "read" &&
|
|
4844
|
-
else if (e.name === "edit" &&
|
|
4845
|
-
else if (e.name === "write" &&
|
|
4846
|
-
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);
|
|
4847
5097
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
4848
5098
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
4849
5099
|
}
|
|
@@ -4880,9 +5130,9 @@ var SessionStats = class {
|
|
|
4880
5130
|
);
|
|
4881
5131
|
const cache = this.tokenCounter.cacheStats();
|
|
4882
5132
|
if (cache.readTokens > 0 || cache.writeTokens > 0) {
|
|
4883
|
-
const
|
|
5133
|
+
const pct2 = (cache.hitRatio * 100).toFixed(1);
|
|
4884
5134
|
lines.push(
|
|
4885
|
-
` Prompt cache: ${
|
|
5135
|
+
` Prompt cache: ${pct2}% hit ${color.dim(`(${fmtTok(cache.readTokens)} read / ${fmtTok(cache.writeTokens)} write)`)}`
|
|
4886
5136
|
);
|
|
4887
5137
|
}
|
|
4888
5138
|
if (cost.total > 0) {
|
|
@@ -5009,10 +5259,10 @@ var Spinner = class {
|
|
|
5009
5259
|
};
|
|
5010
5260
|
function renderContextChip2(ctx) {
|
|
5011
5261
|
const ratio = Math.max(0, Math.min(1, ctx.used / ctx.max));
|
|
5012
|
-
const
|
|
5262
|
+
const pct2 = Math.round(ratio * 100);
|
|
5013
5263
|
const chipColor = ratio >= 0.85 ? color.red : ratio >= 0.65 ? color.yellow : color.cyan;
|
|
5014
5264
|
const bar = renderProgress2(ratio, 8);
|
|
5015
|
-
return color.dim("ctx ") + chipColor(bar) + chipColor(` ${
|
|
5265
|
+
return color.dim("ctx ") + chipColor(bar) + chipColor(` ${pct2}%`) + color.dim(` (${fmtTok(ctx.used)}/${fmtTok(ctx.max)})`);
|
|
5016
5266
|
}
|
|
5017
5267
|
function renderProgress2(ratio, width) {
|
|
5018
5268
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
@@ -5026,7 +5276,7 @@ function resolveBundledSkillsDir2() {
|
|
|
5026
5276
|
try {
|
|
5027
5277
|
const req2 = createRequire(import.meta.url);
|
|
5028
5278
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
5029
|
-
return
|
|
5279
|
+
return path14.join(path14.dirname(corePkg), "skills");
|
|
5030
5280
|
} catch {
|
|
5031
5281
|
return void 0;
|
|
5032
5282
|
}
|
|
@@ -5049,52 +5299,35 @@ async function main(argv) {
|
|
|
5049
5299
|
logger
|
|
5050
5300
|
} = ctx;
|
|
5051
5301
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
5052
|
-
const
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
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
|
+
`);
|
|
5068
5328
|
await reader.close();
|
|
5069
5329
|
return 2;
|
|
5070
5330
|
}
|
|
5071
|
-
const container = new Container();
|
|
5072
|
-
const configStore = new DefaultConfigStore(config);
|
|
5073
|
-
container.bind(TOKENS.ConfigStore, () => configStore);
|
|
5074
|
-
container.bind(TOKENS.Logger, () => logger);
|
|
5075
|
-
container.bind(TOKENS.PathResolver, () => pathResolver);
|
|
5076
|
-
container.bind(TOKENS.SecretScrubber, () => new DefaultSecretScrubber());
|
|
5077
|
-
container.bind(TOKENS.RetryPolicy, () => new DefaultRetryPolicy());
|
|
5078
|
-
container.bind(TOKENS.ErrorHandler, () => new DefaultErrorHandler());
|
|
5079
|
-
container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
|
|
5080
|
-
container.bind(
|
|
5081
|
-
TOKENS.TokenCounter,
|
|
5082
|
-
() => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider })
|
|
5083
|
-
);
|
|
5084
|
-
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
5085
|
-
container.bind(TOKENS.ModeStore, () => modeStore);
|
|
5086
|
-
container.bind(
|
|
5087
|
-
TOKENS.SessionStore,
|
|
5088
|
-
() => new DefaultSessionStore({ dir: wpaths.projectSessions })
|
|
5089
|
-
);
|
|
5090
|
-
const memoryStore = new DefaultMemoryStore({ paths: wpaths });
|
|
5091
|
-
container.bind(TOKENS.MemoryStore, () => memoryStore);
|
|
5092
|
-
const skillLoader = new DefaultSkillLoader({
|
|
5093
|
-
paths: wpaths,
|
|
5094
|
-
bundledDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
|
|
5095
|
-
});
|
|
5096
|
-
container.bind(TOKENS.SkillLoader, () => skillLoader);
|
|
5097
|
-
const activeMode = await modeStore.getActiveMode();
|
|
5098
5331
|
const modeId = activeMode?.id ?? "default";
|
|
5099
5332
|
const modePrompt = activeMode?.prompt ?? "";
|
|
5100
5333
|
const resolvedModel = await modelsRegistry.getModel(config.provider, config.model);
|
|
@@ -5104,6 +5337,8 @@ async function main(argv) {
|
|
|
5104
5337
|
supportsVision: resolvedModel.capabilities.vision,
|
|
5105
5338
|
supportsReasoning: resolvedModel.capabilities.reasoning
|
|
5106
5339
|
} : void 0;
|
|
5340
|
+
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
5341
|
+
const skillLoader = container.resolve(TOKENS.SkillLoader);
|
|
5107
5342
|
const sessionRef = {};
|
|
5108
5343
|
container.bind(
|
|
5109
5344
|
TOKENS.SystemPromptBuilder,
|
|
@@ -5114,46 +5349,9 @@ async function main(argv) {
|
|
|
5114
5349
|
modeId,
|
|
5115
5350
|
modePrompt,
|
|
5116
5351
|
modelCapabilities,
|
|
5117
|
-
|
|
5118
|
-
// is created, then resolves to the per-session plan JSON path.
|
|
5119
|
-
planPath: () => sessionRef.current ? path13.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
5120
|
-
})
|
|
5121
|
-
);
|
|
5122
|
-
container.bind(TOKENS.Renderer, () => renderer);
|
|
5123
|
-
container.bind(TOKENS.InputReader, () => reader);
|
|
5124
|
-
container.bind(
|
|
5125
|
-
TOKENS.PermissionPolicy,
|
|
5126
|
-
() => new DefaultPermissionPolicy({
|
|
5127
|
-
trustFile: wpaths.projectTrust,
|
|
5128
|
-
yolo: config.yolo,
|
|
5129
|
-
promptDelegate: makePromptDelegate(reader)
|
|
5130
|
-
})
|
|
5131
|
-
);
|
|
5132
|
-
container.bind(
|
|
5133
|
-
TOKENS.Compactor,
|
|
5134
|
-
() => new HybridCompactor({
|
|
5135
|
-
preserveK: config.context.preserveK,
|
|
5136
|
-
eliseThreshold: config.context.eliseThreshold
|
|
5352
|
+
planPath: () => sessionRef.current ? path14.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
5137
5353
|
})
|
|
5138
5354
|
);
|
|
5139
|
-
const providerRegistry = new ProviderRegistry();
|
|
5140
|
-
if (config.features.modelsRegistry) {
|
|
5141
|
-
try {
|
|
5142
|
-
const factories = await buildProviderFactoriesFromRegistry({
|
|
5143
|
-
registry: modelsRegistry,
|
|
5144
|
-
log: logger
|
|
5145
|
-
});
|
|
5146
|
-
for (const f of factories) providerRegistry.register(f);
|
|
5147
|
-
} catch (err) {
|
|
5148
|
-
process.stderr.write(
|
|
5149
|
-
`Failed to load models.dev registry: ${err instanceof Error ? err.message : err}
|
|
5150
|
-
Try \`wstack models refresh\` once you have network access, or run with --no-features.
|
|
5151
|
-
`
|
|
5152
|
-
);
|
|
5153
|
-
await reader.close();
|
|
5154
|
-
return 2;
|
|
5155
|
-
}
|
|
5156
|
-
}
|
|
5157
5355
|
const toolRegistry = new ToolRegistry();
|
|
5158
5356
|
toolRegistry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
5159
5357
|
toolRegistry.registerDefault(
|
|
@@ -5196,7 +5394,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5196
5394
|
const dumpMetrics = () => {
|
|
5197
5395
|
if (!metricsSink) return;
|
|
5198
5396
|
try {
|
|
5199
|
-
const out =
|
|
5397
|
+
const out = path14.join(wpaths.projectSessions, "metrics.json");
|
|
5200
5398
|
const snap = metricsSink.snapshot();
|
|
5201
5399
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
5202
5400
|
} catch {
|
|
@@ -5281,27 +5479,6 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5281
5479
|
process.stderr.write(color.red(` \u2717 ${p.description}
|
|
5282
5480
|
`));
|
|
5283
5481
|
});
|
|
5284
|
-
const providerConfig = config.providers?.[config.provider] ?? {
|
|
5285
|
-
type: config.provider,
|
|
5286
|
-
apiKey: config.apiKey,
|
|
5287
|
-
baseUrl: config.baseUrl
|
|
5288
|
-
};
|
|
5289
|
-
let provider;
|
|
5290
|
-
try {
|
|
5291
|
-
const cfgWithType = { ...providerConfig, type: config.provider };
|
|
5292
|
-
if (config.features.modelsRegistry && providerRegistry.has(config.provider)) {
|
|
5293
|
-
provider = providerRegistry.create(cfgWithType);
|
|
5294
|
-
} else {
|
|
5295
|
-
provider = makeProviderFromConfig(config.provider, cfgWithType);
|
|
5296
|
-
}
|
|
5297
|
-
} catch (err) {
|
|
5298
|
-
process.stderr.write(
|
|
5299
|
-
`Failed to create provider: ${err instanceof Error ? err.message : err}
|
|
5300
|
-
`
|
|
5301
|
-
);
|
|
5302
|
-
await reader.close();
|
|
5303
|
-
return 2;
|
|
5304
|
-
}
|
|
5305
5482
|
const promptBuilder = container.resolve(TOKENS.SystemPromptBuilder);
|
|
5306
5483
|
const systemPrompt = await promptBuilder.build({
|
|
5307
5484
|
cwd,
|
|
@@ -5311,59 +5488,29 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5311
5488
|
model: config.model
|
|
5312
5489
|
});
|
|
5313
5490
|
const sessionStore = container.resolve(TOKENS.SessionStore);
|
|
5314
|
-
|
|
5315
|
-
const
|
|
5316
|
-
|
|
5317
|
-
|
|
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)
|
|
5318
5504
|
});
|
|
5319
|
-
|
|
5320
|
-
const abandoned = await recoveryLock.checkAbandoned();
|
|
5321
|
-
if (abandoned && abandoned.messageCount > 0) {
|
|
5322
|
-
const choice = await promptRecovery(reader, renderer, abandoned, !!flags["recover"]);
|
|
5323
|
-
if (choice === "resume") {
|
|
5324
|
-
resumeId = abandoned.sessionId;
|
|
5325
|
-
} else if (choice === "delete") {
|
|
5326
|
-
await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
|
|
5327
|
-
await recoveryLock.clear();
|
|
5328
|
-
} else {
|
|
5329
|
-
await recoveryLock.clear();
|
|
5330
|
-
}
|
|
5331
|
-
} else if (abandoned) {
|
|
5332
|
-
await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
|
|
5333
|
-
await recoveryLock.clear();
|
|
5334
|
-
}
|
|
5335
|
-
}
|
|
5336
|
-
let session;
|
|
5337
|
-
let restoredMessages = [];
|
|
5338
|
-
if (resumeId) {
|
|
5339
|
-
try {
|
|
5340
|
-
const resumed = await sessionStore.resume(resumeId);
|
|
5341
|
-
session = resumed.writer;
|
|
5342
|
-
restoredMessages = resumed.data.messages;
|
|
5343
|
-
renderer.writeInfo(
|
|
5344
|
-
`Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`
|
|
5345
|
-
);
|
|
5346
|
-
} catch (err) {
|
|
5347
|
-
renderer.writeError(`Resume failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5348
|
-
return 2;
|
|
5349
|
-
}
|
|
5350
|
-
} else {
|
|
5351
|
-
session = await sessionStore.create({
|
|
5352
|
-
id: "",
|
|
5353
|
-
title: "",
|
|
5354
|
-
model: config.model,
|
|
5355
|
-
provider: config.provider
|
|
5356
|
-
});
|
|
5357
|
-
}
|
|
5505
|
+
const session = sessResult.session;
|
|
5358
5506
|
sessionRef.current = session;
|
|
5359
|
-
|
|
5360
|
-
const
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
const queueStore =
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
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;
|
|
5367
5514
|
const stats = new SessionStats(events, tokenCounter);
|
|
5368
5515
|
const errorRing = [];
|
|
5369
5516
|
events.on("error", (e) => {
|
|
@@ -5373,150 +5520,15 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5373
5520
|
errorRing.push({ ts: (/* @__PURE__ */ new Date()).toISOString(), phase: e.phase, code, message });
|
|
5374
5521
|
if (errorRing.length > 5) errorRing.shift();
|
|
5375
5522
|
});
|
|
5376
|
-
const
|
|
5377
|
-
const context = new Context({
|
|
5378
|
-
systemPrompt,
|
|
5379
|
-
provider,
|
|
5380
|
-
session,
|
|
5381
|
-
signal: ctxSignal,
|
|
5382
|
-
tokenCounter,
|
|
5383
|
-
cwd,
|
|
5384
|
-
projectRoot,
|
|
5385
|
-
model: config.model
|
|
5386
|
-
});
|
|
5387
|
-
if (restoredMessages.length > 0) {
|
|
5388
|
-
context.state.replaceMessages(restoredMessages);
|
|
5389
|
-
}
|
|
5390
|
-
const todosCheckpointPath = path13.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
5391
|
-
if (resumeId) {
|
|
5392
|
-
try {
|
|
5393
|
-
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
5394
|
-
if (restoredTodos && restoredTodos.length > 0) {
|
|
5395
|
-
context.state.replaceTodos(restoredTodos);
|
|
5396
|
-
renderer.writeInfo(
|
|
5397
|
-
`Restored ${restoredTodos.length} todo${restoredTodos.length === 1 ? "" : "s"} from previous run.`
|
|
5398
|
-
);
|
|
5399
|
-
}
|
|
5400
|
-
} catch {
|
|
5401
|
-
}
|
|
5402
|
-
}
|
|
5403
|
-
attachTodosCheckpoint(
|
|
5404
|
-
context.state,
|
|
5405
|
-
todosCheckpointPath,
|
|
5406
|
-
session.id
|
|
5407
|
-
);
|
|
5408
|
-
const planPath = path13.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
5409
|
-
context.state.setMeta("plan.path", planPath);
|
|
5410
|
-
if (resumeId) {
|
|
5411
|
-
try {
|
|
5412
|
-
const fleetRoot2 = path13.join(wpaths.projectSessions, session.id);
|
|
5413
|
-
const dirState = await loadDirectorState(path13.join(fleetRoot2, "director-state.json"));
|
|
5414
|
-
if (dirState) {
|
|
5415
|
-
const tCounts = {};
|
|
5416
|
-
for (const t of dirState.tasks) {
|
|
5417
|
-
tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
5418
|
-
}
|
|
5419
|
-
const summary = Object.entries(tCounts).map(([k, v]) => `${v} ${k}`).join(", ");
|
|
5420
|
-
renderer.writeInfo(
|
|
5421
|
-
`Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`
|
|
5422
|
-
);
|
|
5423
|
-
}
|
|
5424
|
-
} catch {
|
|
5425
|
-
}
|
|
5426
|
-
try {
|
|
5427
|
-
const plan = await loadPlan(planPath);
|
|
5428
|
-
if (plan && plan.items.length > 0) {
|
|
5429
|
-
const open = plan.items.filter((p) => p.status !== "done").length;
|
|
5430
|
-
const done = plan.items.length - open;
|
|
5431
|
-
renderer.writeInfo(
|
|
5432
|
-
`Plan: ${plan.items.length} item${plan.items.length === 1 ? "" : "s"} (${open} open, ${done} done). Use /plan to review.`
|
|
5433
|
-
);
|
|
5434
|
-
}
|
|
5435
|
-
} catch {
|
|
5436
|
-
}
|
|
5437
|
-
}
|
|
5438
|
-
const pipelines = createDefaultPipelines();
|
|
5439
|
-
const installBoundary = (p) => {
|
|
5440
|
-
p.setErrorHandler((ev) => {
|
|
5441
|
-
const fromPlugin = !!ev.owner && ev.owner !== "core";
|
|
5442
|
-
logger.error(
|
|
5443
|
-
`Pipeline middleware "${ev.middleware}" crashed (owner=${ev.owner ?? "unknown"}); ${fromPlugin ? "swallowed" : "rethrown"}`,
|
|
5444
|
-
ev.err
|
|
5445
|
-
);
|
|
5446
|
-
events.emit("error", {
|
|
5447
|
-
err: ev.err instanceof Error ? ev.err : new Error(String(ev.err)),
|
|
5448
|
-
phase: `pipeline:${ev.middleware}`
|
|
5449
|
-
});
|
|
5450
|
-
return fromPlugin ? "swallow" : "rethrow";
|
|
5451
|
-
});
|
|
5452
|
-
};
|
|
5453
|
-
installBoundary(pipelines.request);
|
|
5454
|
-
installBoundary(pipelines.response);
|
|
5455
|
-
installBoundary(pipelines.toolCall);
|
|
5456
|
-
installBoundary(pipelines.userInput);
|
|
5457
|
-
installBoundary(pipelines.assistantOutput);
|
|
5458
|
-
installBoundary(pipelines.contextWindow);
|
|
5523
|
+
const pipelines = setupPipelines({ events, logger });
|
|
5459
5524
|
const compactor = container.resolve(TOKENS.Compactor);
|
|
5460
|
-
const
|
|
5461
|
-
() => void 0
|
|
5462
|
-
);
|
|
5463
|
-
const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
|
|
5525
|
+
const effectiveMaxContext = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
|
|
5464
5526
|
const updateSpinnerContext = () => {
|
|
5465
5527
|
if (effectiveMaxContext > 0 && lastInputTokens > 0) {
|
|
5466
5528
|
spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
|
|
5467
|
-
} else
|
|
5468
|
-
spinner.setContext(void 0);
|
|
5469
|
-
}
|
|
5529
|
+
} else spinner.setContext(void 0);
|
|
5470
5530
|
};
|
|
5471
|
-
|
|
5472
|
-
const autoCompactor = new AutoCompactionMiddleware(
|
|
5473
|
-
compactor,
|
|
5474
|
-
effectiveMaxContext,
|
|
5475
|
-
(ctx2) => {
|
|
5476
|
-
const msgs = ctx2.messages;
|
|
5477
|
-
let total = 0;
|
|
5478
|
-
for (const m of msgs) {
|
|
5479
|
-
if (typeof m.content === "string") total += Math.ceil(m.content.length / 4);
|
|
5480
|
-
else if (Array.isArray(m.content)) {
|
|
5481
|
-
for (const b of m.content) {
|
|
5482
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
5483
|
-
else if (b.type === "tool_use" || b.type === "tool_result") {
|
|
5484
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
5485
|
-
}
|
|
5486
|
-
}
|
|
5487
|
-
}
|
|
5488
|
-
}
|
|
5489
|
-
return total;
|
|
5490
|
-
},
|
|
5491
|
-
{
|
|
5492
|
-
warn: config.context.warnThreshold,
|
|
5493
|
-
soft: config.context.softThreshold,
|
|
5494
|
-
hard: config.context.hardThreshold
|
|
5495
|
-
},
|
|
5496
|
-
{
|
|
5497
|
-
aggressiveOn: "soft",
|
|
5498
|
-
failureMode: "throw_on_hard",
|
|
5499
|
-
events
|
|
5500
|
-
}
|
|
5501
|
-
);
|
|
5502
|
-
pipelines.contextWindow.use({
|
|
5503
|
-
name: "AutoCompaction",
|
|
5504
|
-
handler: autoCompactor.handler()
|
|
5505
|
-
});
|
|
5506
|
-
}
|
|
5507
|
-
const agent = new Agent({
|
|
5508
|
-
container,
|
|
5509
|
-
tools: toolRegistry,
|
|
5510
|
-
providers: providerRegistry,
|
|
5511
|
-
events,
|
|
5512
|
-
pipelines,
|
|
5513
|
-
context,
|
|
5514
|
-
maxIterations: config.tools.maxIterations,
|
|
5515
|
-
iterationTimeoutMs: config.tools.iterationTimeoutMs,
|
|
5516
|
-
executionStrategy: config.tools.defaultExecutionStrategy,
|
|
5517
|
-
perIterationOutputCapBytes: config.tools.perIterationOutputCapBytes,
|
|
5518
|
-
confirmAwaiter: makeConfirmAwaiter(reader)
|
|
5519
|
-
});
|
|
5531
|
+
const agent = createAgent({ container, tools: toolRegistry, providers: providerRegistry, events, pipelines, context, config, confirmAwaiter: makeConfirmAwaiter(reader) });
|
|
5520
5532
|
const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger });
|
|
5521
5533
|
if (config.features.mcp) {
|
|
5522
5534
|
for (const cfg of Object.values(config.mcpServers ?? {})) {
|
|
@@ -5589,12 +5601,12 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5589
5601
|
};
|
|
5590
5602
|
const directorMode = flags["director"] === true;
|
|
5591
5603
|
let director = null;
|
|
5592
|
-
const fleetRoot = directorMode ?
|
|
5593
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
5594
|
-
const sharedScratchpadPath = directorMode ?
|
|
5595
|
-
const subagentSessionsRoot = directorMode ?
|
|
5596
|
-
const stateCheckpointPath = directorMode ?
|
|
5597
|
-
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);
|
|
5598
5610
|
const multiAgentHost = new MultiAgentHost(
|
|
5599
5611
|
{
|
|
5600
5612
|
container,
|
|
@@ -5746,7 +5758,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5746
5758
|
return `Unknown fleet action: ${action}`;
|
|
5747
5759
|
},
|
|
5748
5760
|
onFleetLog: async (subagentId, mode) => {
|
|
5749
|
-
const subagentsRoot =
|
|
5761
|
+
const subagentsRoot = path14.join(fleetRootForPromotion, "subagents");
|
|
5750
5762
|
let runDirs;
|
|
5751
5763
|
try {
|
|
5752
5764
|
runDirs = await fs3.readdir(subagentsRoot);
|
|
@@ -5755,7 +5767,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5755
5767
|
}
|
|
5756
5768
|
const found = [];
|
|
5757
5769
|
for (const runId of runDirs) {
|
|
5758
|
-
const runDir =
|
|
5770
|
+
const runDir = path14.join(subagentsRoot, runId);
|
|
5759
5771
|
let files;
|
|
5760
5772
|
try {
|
|
5761
5773
|
files = await fs3.readdir(runDir);
|
|
@@ -5764,7 +5776,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5764
5776
|
}
|
|
5765
5777
|
for (const f of files) {
|
|
5766
5778
|
if (!f.endsWith(".jsonl")) continue;
|
|
5767
|
-
const full =
|
|
5779
|
+
const full = path14.join(runDir, f);
|
|
5768
5780
|
try {
|
|
5769
5781
|
const stat2 = await fs3.stat(full);
|
|
5770
5782
|
found.push({
|
|
@@ -5861,7 +5873,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5861
5873
|
}
|
|
5862
5874
|
const dir = await multiAgentHost.ensureDirector();
|
|
5863
5875
|
if (!dir) return "Director is not available.";
|
|
5864
|
-
const dirStatePath =
|
|
5876
|
+
const dirStatePath = path14.join(fleetRootForPromotion, "director-state.json");
|
|
5865
5877
|
const prior = await loadDirectorState(dirStatePath);
|
|
5866
5878
|
if (!prior) {
|
|
5867
5879
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -5932,9 +5944,9 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5932
5944
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
5933
5945
|
toolRegistry.register(tool);
|
|
5934
5946
|
}
|
|
5935
|
-
const mp =
|
|
5936
|
-
const sp =
|
|
5937
|
-
const ss =
|
|
5947
|
+
const mp = path14.join(fleetRootForPromotion, "fleet.json");
|
|
5948
|
+
const sp = path14.join(fleetRootForPromotion, "shared");
|
|
5949
|
+
const ss = path14.join(fleetRootForPromotion, "subagents");
|
|
5938
5950
|
const lines = [
|
|
5939
5951
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
5940
5952
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -5977,6 +5989,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5977
5989
|
onStats: () => stats.format()
|
|
5978
5990
|
});
|
|
5979
5991
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
5992
|
+
const savedProviderCfg = config.providers?.[config.provider];
|
|
5980
5993
|
return execute({
|
|
5981
5994
|
agent,
|
|
5982
5995
|
events,
|
|
@@ -5998,6 +6011,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
5998
6011
|
queueStore,
|
|
5999
6012
|
context,
|
|
6000
6013
|
stats,
|
|
6014
|
+
detachTodosCheckpoint,
|
|
6001
6015
|
savedProviderCfg,
|
|
6002
6016
|
resolvedProvider: resolvedProvider ?? void 0,
|
|
6003
6017
|
getPickableProviders: () => buildPickableProviders(modelsRegistry, config),
|